From b0b8899cc7fc3a6c3aa39b4bb9606459a7f8bf18 Mon Sep 17 00:00:00 2001 From: Robrecht Cannoodt Date: Fri, 21 Aug 2026 16:38:15 +0200 Subject: [PATCH] merge scanorama back into one component * both outputs come from a single `correct_scanpy(return_dimred=True)` run, so there is no reason to run scanorama twice * put the cells and genes back in the order of the input -- scanorama returns one object per batch with the genes sorted by name, so both axes were permuted with respect to the `obs` and `var` they were labelled with * dropped the `merge_adata()` helper along with the deprecated `AnnData.concatenate()` it wrapped --- CHANGELOG.md | 6 ++ .../config.vsh.yaml | 9 ++- .../script.py | 36 ++++----- src/methods/scanorama_correct/config.vsh.yaml | 37 --------- src/methods/scanorama_correct/script.py | 77 ------------------- src/workflows/run_benchmark/config.vsh.yaml | 3 +- src/workflows/run_benchmark/main.nf | 3 +- 7 files changed, 31 insertions(+), 140 deletions(-) rename src/methods/{scanorama_integrate => scanorama}/config.vsh.yaml (88%) rename src/methods/{scanorama_integrate => scanorama}/script.py (55%) delete mode 100644 src/methods/scanorama_correct/config.vsh.yaml delete mode 100644 src/methods/scanorama_correct/script.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c211678..1acea7f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ * Un-pin the scPRINT version and update parameters (PR #51) * Update scPRINT to better handle large datasets, including a new default model (PR #54) * Credit contributors missing from the authors list, and fix Martin Kim's orcid (PR #100). +* Merge `methods/scanorama_correct` and `methods/scanorama_integrate` back into a single `methods/scanorama` + component, reverting the split from PR #88. A single `correct_scanpy()` run returns both the corrected counts and + the embedding. ## Bug fixes @@ -53,6 +56,9 @@ * Fix `methods/pyliger` failing to build: louvain has no python 3.12 wheel and needs cmake to build igraph from source. * Bump `methods/cellplm`, `methods/condo`, `methods/drvi` and `metrics/bras` from base image `:1.0.0` to `:1`, so their `openproblems` is new enough for the component tests in `common`. +* Fix `methods/scanorama` scrambling its output: scanorama returns one object per batch with the genes sorted by + name, so both axes ended up permuted with respect to the `obs` and `var` they were labelled with. This affected + every metric, not just `hvg_overlap`. * Fix `methods/geneformer` failing to build: pip's `--filter=blob:none` clone of the huggingface repo no longer works, so clone it ourselves. Also needs `transformers<5`, which still has `SpecialTokensMixin`. * Fix `methods/cellplm` failing to build: drop the pytorch base image's broken `/usr/local/bin/cmake` shim, which diff --git a/src/methods/scanorama_integrate/config.vsh.yaml b/src/methods/scanorama/config.vsh.yaml similarity index 88% rename from src/methods/scanorama_integrate/config.vsh.yaml rename to src/methods/scanorama/config.vsh.yaml index a53ab6c5..74d6d2ea 100644 --- a/src/methods/scanorama_integrate/config.vsh.yaml +++ b/src/methods/scanorama/config.vsh.yaml @@ -1,6 +1,6 @@ __merge__: /src/api/comp_method.yaml -name: scanorama_integrate -label: Scanorama-Integrate +name: scanorama +label: Scanorama summary: Efficient integration of heterogeneous single-cell transcriptomes using Scanorama description: | Scanorama enables batch-correction and integration of heterogeneous scRNA-seq datasets. @@ -8,6 +8,9 @@ description: | including those for imputation and highly-variable gene filtering. The results from Scanorama integration and batch correction can then be used as input to other tools for scRNA-seq clustering, visualization, and analysis. + + A single run returns both the batch-corrected expression matrix and the integrated + embedding it is assembled from. references: # Hie, B., Bryson, B. & Berger, B. Efficient integration of heterogeneous single-cell # transcriptomes using Scanorama. Nat Biotechnol 37, 685–691 (2019). @@ -17,7 +20,7 @@ links: repository: https://github.com/brianhie/scanorama documentation: https://github.com/brianhie/scanorama#readme info: - method_types: [embedding] + method_types: [feature, embedding] preferred_normalization: log_cp10k arguments: - name: --dimred diff --git a/src/methods/scanorama_integrate/script.py b/src/methods/scanorama/script.py similarity index 55% rename from src/methods/scanorama_integrate/script.py rename to src/methods/scanorama/script.py index bbe6ca86..1222bc7c 100644 --- a/src/methods/scanorama_integrate/script.py +++ b/src/methods/scanorama/script.py @@ -1,7 +1,7 @@ import sys + import anndata as ad import scanorama -import numpy as np ## VIASH START par = { @@ -10,14 +10,14 @@ 'dimred': 100 } meta = { - 'name': 'scanorama-integrate', + 'name': 'scanorama', + 'resources_dir': 'src/utils' } ## VIASH END sys.path.append(meta["resources_dir"]) from read_anndata_partial import read_anndata - print('Read input', flush=True) adata = read_anndata( par['input'], @@ -28,32 +28,30 @@ ) print('Run scanorama', flush=True) -split = [] -batch_categories = adata.obs['batch'].cat.categories -for b in batch_categories: - split.append(adata[adata.obs['batch'] == b].copy()) -scanorama.integrate_scanpy(split, dimred=par["dimred"]) - -#From https://colab.research.google.com/drive/1CebA3Ow4jXITK0dW5el320KVTX_szhxG -result = np.zeros((adata.shape[0], split[0].obsm["X_scanorama"].shape[1])) -for i, b in enumerate(batch_categories): - result[adata.obs['batch'] == b] = split[i].obsm["X_scanorama"] +split = [adata[adata.obs['batch'] == batch].copy() for batch in adata.obs['batch'].cat.categories] +corrected = scanorama.correct_scanpy(split, return_dimred=True, dimred=par['dimred']) +# scanorama returns one object per batch, with the genes sorted by name -- put the +# cells and genes back in the order of the input before storing the output +corrected = ad.concat(corrected) +corrected = corrected[adata.obs_names, adata.var_names] print("Store output", flush=True) output = ad.AnnData( obs=adata.obs[[]], var=adata.var[[]], + layers={ + 'corrected_counts': corrected.X, + }, + obsm={ + 'X_emb': corrected.obsm['X_scanorama'], + }, uns={ 'dataset_id': adata.uns['dataset_id'], 'normalization_id': adata.uns['normalization_id'], 'method_id': meta['name'], - }, - obsm={ - 'X_emb': result - }, - shape=adata.shape, + } ) print("Write output to file", flush=True) -output.write(par['output'], compression='gzip') +output.write_h5ad(par['output'], compression='gzip') diff --git a/src/methods/scanorama_correct/config.vsh.yaml b/src/methods/scanorama_correct/config.vsh.yaml deleted file mode 100644 index c346d96a..00000000 --- a/src/methods/scanorama_correct/config.vsh.yaml +++ /dev/null @@ -1,37 +0,0 @@ -__merge__: /src/api/comp_method.yaml -name: scanorama_correct -label: Scanorama-Corrrect -summary: Efficient integration of heterogeneous single-cell transcriptomes using Scanorama -description: | - Scanorama enables batch-correction and integration of heterogeneous scRNA-seq datasets. - It is designed to be used in scRNA-seq pipelines downstream of noise-reduction methods, - including those for imputation and highly-variable gene filtering. The results from - Scanorama integration and batch correction can then be used as input to other tools - for scRNA-seq clustering, visualization, and analysis. -references: - # Hie, B., Bryson, B. & Berger, B. Efficient integration of heterogeneous single-cell - # transcriptomes using Scanorama. Nat Biotechnol 37, 685–691 (2019). - # https://doi.org/10.1038/s41587-019-0113-3 - doi: 10.1038/s41587-019-0113-3 -links: - repository: https://github.com/brianhie/scanorama - documentation: https://github.com/brianhie/scanorama#readme -info: - method_types: [feature] - preferred_normalization: log_cp10k -resources: - - type: python_script - path: script.py - - path: /src/utils/read_anndata_partial.py -engines: - - type: docker - image: openproblems/base_python:1 - setup: - - type: python - pypi: - - scanorama -runners: - - type: executable - - type: nextflow - directives: - label: [hightime, highmem, lowcpu] diff --git a/src/methods/scanorama_correct/script.py b/src/methods/scanorama_correct/script.py deleted file mode 100644 index e0831869..00000000 --- a/src/methods/scanorama_correct/script.py +++ /dev/null @@ -1,77 +0,0 @@ -import sys -import anndata as ad -import scanorama - -## VIASH START -par = { - 'input': 'resources_test/task_batch_integration/cxg_immune_cell_atlas/dataset.h5ad', - 'output': 'output.h5ad', -} -meta = { - 'name': 'scanorama-correct', -} -## VIASH END - -sys.path.append(meta["resources_dir"]) -from read_anndata_partial import read_anndata - - -# based on scib -# -> https://github.com/theislab/scib/blob/59ae6eee5e611d9d3db067685ec96c28804e9127/scib/utils.py#L51C1-L72C62 -def merge_adata(*adata_list, **kwargs): - """Merge adatas from list while remove duplicated ``obs`` and ``var`` columns - - :param adata_list: ``anndata`` objects to be concatenated - :param kwargs: arguments to be passed to ``anndata.AnnData.concatenate`` - """ - - if len(adata_list) == 1: - return adata_list[0] - - # Make sure that adatas do not contain duplicate columns - for _adata in adata_list: - for attr in ("obs", "var"): - df = getattr(_adata, attr) - dup_mask = df.columns.duplicated() - if dup_mask.any(): - print( - f"Deleting duplicated keys `{list(df.columns[dup_mask].unique())}` from `adata.{attr}`." - ) - setattr(_adata, attr, df.loc[:, ~dup_mask]) - - return ad.AnnData.concatenate(*adata_list, **kwargs) - - -print('Read input', flush=True) -adata = read_anndata( - par['input'], - X='layers/normalized', - obs='obs', - var='var', - uns='uns' -) - -print('Run scanorama', flush=True) -split = [] -batch_categories = adata.obs['batch'].cat.categories -for i in batch_categories: - split.append(adata[adata.obs['batch'] == i].copy()) -corrected = scanorama.correct_scanpy(split, return_dimred=False) -corrected = merge_adata(*corrected, batch_key='batch', batch_categories=batch_categories, index_unique=None) - -print("Store output", flush=True) -output = ad.AnnData( - obs=adata.obs[[]], - var=adata.var[[]], - uns={ - 'dataset_id': adata.uns['dataset_id'], - 'normalization_id': adata.uns['normalization_id'], - 'method_id': meta['name'], - }, - layers={ - 'corrected_counts': corrected.X, - } -) - -print("Write output to file", flush=True) -output.write(par['output'], compression='gzip') diff --git a/src/workflows/run_benchmark/config.vsh.yaml b/src/workflows/run_benchmark/config.vsh.yaml index ae2be6cc..ac1fe44c 100644 --- a/src/workflows/run_benchmark/config.vsh.yaml +++ b/src/workflows/run_benchmark/config.vsh.yaml @@ -104,8 +104,7 @@ dependencies: - name: methods/pyliger - name: methods/sca - name: methods/scalex - - name: methods/scanorama_correct - - name: methods/scanorama_integrate + - name: methods/scanorama - name: methods/scanvi - name: methods/scmerge2 - name: methods/scgpt_finetuned diff --git a/src/workflows/run_benchmark/main.nf b/src/workflows/run_benchmark/main.nf index 2f5bf7c9..970511ca 100644 --- a/src/workflows/run_benchmark/main.nf +++ b/src/workflows/run_benchmark/main.nf @@ -34,8 +34,7 @@ methods = [ pyliger, sca, scalex, - scanorama_correct, - scanorama_integrate, + scanorama, scanvi, scmerge2, scgpt_finetuned.run(