Skip to content

Commit 75b79da

Browse files
seohyonkimlazappimumichaercannood
authored
Adding new method: scMerge2 (#63)
* working unsupervised scmerge2. Need to clear out the comments * raise error for unmatched species * clean unsupervised scmerge2 * working semi-supervised scmerge2 * remove comments * Update src/methods/semisupervised_scmerge2/config.vsh.yaml Co-authored-by: Luke Zappia <lazappi@users.noreply.github.com> * change image * fixed scMerge2 * add method_types to config * add to changelog * merge the two scmerge2 components into one --------- Co-authored-by: Luke Zappia <lazappi@users.noreply.github.com> Co-authored-by: Michaela Müller <51025211+mumichae@users.noreply.github.com> Co-authored-by: Robrecht Cannoodt <rcannood@gmail.com>
1 parent f3a152c commit 75b79da

5 files changed

Lines changed: 149 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
* Added `method/limma_removebatcheffect` component (PR #79).
3131

32+
* Added `methods/scmerge2` component (PR #63).
33+
3234
## Minor changes
3335

3436
* Un-pin the scPRINT version and update parameters (PR #51)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
__merge__: /src/api/comp_method.yaml
2+
name: scmerge2
3+
label: scMerge2
4+
summary: "scMerge2 integrates single-cell RNA-seq datasets by removing unwanted variation estimated from stably expressed genes and pseudo-replicates."
5+
description: |
6+
scMerge2 corrects batch effects while preserving biological signal. It identifies a set of stably
7+
expressed genes (SEGs), which are assumed to remain consistent across datasets, and uses them as
8+
negative controls in a factor analysis model that estimates and removes unwanted variation.
9+
Pseudo-replicates, constructed from pseudo-bulk profiles of cells grouped within each batch, serve
10+
as anchors for the alignment.
11+
12+
Cell type labels are optional. When they are supplied the pseudo-replicates are built per cell
13+
type; when they are not, scMerge2 identifies the groupings itself with a mutual nearest cluster
14+
procedure. Both modes are exposed through `--cell_type_aware`.
15+
references:
16+
doi:
17+
- 10.1073/pnas.1820006116
18+
links:
19+
documentation: https://sydneybiox.github.io/scMerge/articles/scMerge2.html
20+
repository: https://github.com/SydneyBioX/scMerge
21+
info:
22+
method_types: [embedding]
23+
preferred_normalization: log_cp10k
24+
variants:
25+
scmerge2_unsupervised:
26+
scmerge2_semisupervised:
27+
cell_type_aware: true
28+
arguments:
29+
- name: --cell_type_aware
30+
type: boolean
31+
default: false
32+
description: |
33+
Build the pseudo-replicates per cell type, using obs['cell_type']. When false, scMerge2
34+
identifies the cell groupings itself.
35+
- name: --n_control_genes
36+
type: integer
37+
default: 1000
38+
description: Number of top-ranked stably expressed genes to use as negative controls.
39+
- name: --n_dim
40+
type: integer
41+
default: 50
42+
description: Number of principal components in the output embedding.
43+
resources:
44+
- type: r_script
45+
path: script.R
46+
engines:
47+
- type: docker
48+
image: openproblems/base_r:1
49+
setup:
50+
- type: apt
51+
packages: cmake
52+
- type: r
53+
bioc:
54+
- scMerge
55+
runners:
56+
- type: executable
57+
- type: nextflow
58+
directives:
59+
label: [hightime, highmem, midcpu]

src/methods/scmerge2/script.R

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
cat("Loading dependencies\n")
2+
requireNamespace("anndata", quietly = TRUE)
3+
library(Matrix, warn.conflicts = FALSE)
4+
requireNamespace("scMerge", quietly = TRUE)
5+
requireNamespace("BiocParallel", quietly = TRUE)
6+
requireNamespace("BiocSingular", quietly = TRUE)
7+
requireNamespace("methods", quietly = TRUE)
8+
9+
## VIASH START
10+
par <- list(
11+
input = "resources_test/task_batch_integration/cxg_immune_cell_atlas/dataset.h5ad",
12+
output = "output.h5ad",
13+
cell_type_aware = FALSE,
14+
n_control_genes = 1000L,
15+
n_dim = 50L
16+
)
17+
meta <- list(
18+
name = "scmerge2",
19+
cpus = 1L
20+
)
21+
## VIASH END
22+
23+
n_cpus <- if (is.null(meta$cpus)) 1L else meta$cpus
24+
bpparam <- BiocParallel::MulticoreParam(workers = n_cpus)
25+
26+
cat("Read input\n")
27+
adata <- anndata::read_h5ad(par$input)
28+
29+
# both scSEGIndex and scMerge2 want genes in the rows. scMerge2 only coerces a *dense* matrix to
30+
# CsparseMatrix, so a row-compressed one would slip past that check and break later on
31+
exprs_mat <- methods::as(Matrix::t(adata$layers[["normalized"]]), "CsparseMatrix")
32+
rownames(exprs_mat) <- as.character(adata$var_names)
33+
colnames(exprs_mat) <- as.character(adata$obs_names)
34+
35+
cat("Select stably expressed genes\n")
36+
seg_df <- scMerge::scSEGIndex(exprs_mat = exprs_mat, BPPARAM = bpparam)
37+
seg_df <- seg_df[order(seg_df$segIdx, decreasing = TRUE), , drop = FALSE]
38+
ctl <- rownames(seg_df)[seq_len(min(par$n_control_genes, nrow(seg_df)))]
39+
40+
cat("Run scMerge2\n")
41+
out <- scMerge::scMerge2(
42+
exprsMat = exprs_mat,
43+
batch = as.character(adata$obs$batch),
44+
cellTypes = if (par$cell_type_aware) as.character(adata$obs$cell_type) else NULL,
45+
ctl = ctl,
46+
use_bpparam = bpparam,
47+
use_bsparam = BiocSingular::RandomParam(),
48+
verbose = TRUE
49+
)
50+
51+
cat("Compute embedding\n")
52+
newY <- out$newY
53+
stopifnot(ncol(newY) == adata$n_obs)
54+
if (!is.null(colnames(newY))) {
55+
newY <- newY[, adata$obs_names, drop = FALSE]
56+
}
57+
# subtracting the estimated unwanted variation makes the corrected matrix dense, so let
58+
# BiocSingular stream it rather than materialising it in one go
59+
corrected <- t(newY)
60+
n_dim <- min(par$n_dim, min(dim(corrected)) - 1L)
61+
embedding <- BiocSingular::runPCA(
62+
corrected,
63+
rank = n_dim,
64+
center = TRUE,
65+
scale = FALSE,
66+
BSPARAM = BiocSingular::RandomParam(),
67+
BPPARAM = bpparam
68+
)$x
69+
rownames(embedding) <- adata$obs_names
70+
71+
cat("Store output\n")
72+
output <- anndata::AnnData(
73+
obs = adata$obs[, c()],
74+
var = adata$var[, c()],
75+
obsm = list(
76+
X_emb = embedding
77+
),
78+
uns = list(
79+
dataset_id = adata$uns[["dataset_id"]],
80+
normalization_id = adata$uns[["normalization_id"]],
81+
method_id = meta$name
82+
)
83+
)
84+
85+
cat("Write output to file\n")
86+
zzz <- output$write_h5ad(par$output, compression = "gzip")

src/workflows/run_benchmark/config.vsh.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ dependencies:
106106
- name: methods/scanorama_correct
107107
- name: methods/scanorama_integrate
108108
- name: methods/scanvi
109+
- name: methods/scmerge2
109110
- name: methods/scgpt_finetuned
110111
- name: methods/scgpt_zeroshot
111112
- name: methods/scimilarity

src/workflows/run_benchmark/main.nf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ methods = [
3636
scanorama_correct,
3737
scanorama_integrate,
3838
scanvi,
39+
scmerge2,
3940
scgpt_finetuned.run(
4041
args: [model: file("s3://openproblems-work/cache/scGPT_human.zip")]
4142
),

0 commit comments

Comments
 (0)