sampledisco 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sampledisco-0.3.0/src/sampledisco.egg-info → sampledisco-0.4.0}/PKG-INFO +45 -39
- {sampledisco-0.3.0 → sampledisco-0.4.0}/README.md +43 -37
- {sampledisco-0.3.0 → sampledisco-0.4.0}/pyproject.toml +1 -1
- {sampledisco-0.3.0 → sampledisco-0.4.0}/requirements.txt +3 -3
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/cli.py +14 -1
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/config/config_demo.yaml +6 -3
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/parameter_selection/autotune.py +16 -10
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/cell_type_cpu.py +6 -5
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/cell_type_gpu.py +6 -5
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_embedding/__init__.py +5 -1
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_embedding/blocks.py +420 -48
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_embedding/sample_embedding.py +57 -52
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_embedding/sample_embedding_gpu.py +72 -65
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/wrapper/atac_wrapper.py +5 -2
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/wrapper/multiomics_wrapper.py +11 -3
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/wrapper/rna_wrapper.py +5 -2
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/wrapper/wrapper.py +37 -15
- {sampledisco-0.3.0 → sampledisco-0.4.0/src/sampledisco.egg-info}/PKG-INFO +45 -39
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco.egg-info/SOURCES.txt +4 -1
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco.egg-info/requires.txt +1 -1
- sampledisco-0.4.0/tests/test_config_validation.py +54 -0
- sampledisco-0.4.0/tests/test_inplace_uns_and_equal_weight.py +186 -0
- sampledisco-0.4.0/tests/test_sample_embedding_equivalence.py +148 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/LICENSE +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/setup.cfg +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/ATAC_ArchR.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/ATAC_RNA_harmony.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/ATAC_gene_activity.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/ATAC_peak_annotation.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/RNA_name_convertor.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/pseudo_correlation.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/test_pseudo_correlation.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/gene_activity/validation.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/parameter_selection/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/ATAC_cell_type.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/ATAC_cell_type_gpu.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/atac_preprocess_cpu.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/atac_preprocess_gpu.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/multi_omics_batch_correction.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/multi_omics_cell_type_cpu.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/multi_omics_cell_type_gpu.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/multi_omics_glue.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/multi_omics_merge.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/rna_preprocess_cpu.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/preparation/rna_preprocess_gpu.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_association/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_association/association.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_clustering/RAISIN.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_clustering/RAISIN_TEST.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_clustering/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_clustering/cluster.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_clustering/cluster_severity_reconcile.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_clustering/proportion_test.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_distance/ChiSquare.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_distance/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_distance/distance_test.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_distance/jensenshannon.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_distance/sample_distance.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_trajectory/CCA.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_trajectory/CCA_test.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_trajectory/TSCAN.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_trajectory/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_trajectory/trajectory_DGE_visualization.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/sample_trajectory/trajectory_diff_gene.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/Grouping.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/batch_regress.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/embedding_keys.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/harmony_compat.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/imbalance_cell_type_handler.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/inspector.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/limma.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/merge_sample_meta.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/random_seed.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/safe_save.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/slim_adata.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/subsample.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/subsample_generator.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/tf_idf.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/utils/unify_optimal.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/visualization/ATAC_visualization.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/visualization/DEG_visualization.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/visualization/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/visualization/multi_omics_visualization.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/visualization/visualization_embedding.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/visualization/visualization_helper.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/visualization/visualization_other.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco/wrapper/__init__.py +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco.egg-info/dependency_links.txt +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco.egg-info/entry_points.txt +0 -0
- {sampledisco-0.3.0 → sampledisco-0.4.0}/src/sampledisco.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sampledisco
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Cross-omics, cross-condition sample embedding for single-cell data (RNA + ATAC)
|
|
5
5
|
Author: Hongkai Ji
|
|
6
6
|
Author-email: Harry Jiang <hjiang55@jh.edu>
|
|
@@ -28,7 +28,7 @@ Requires-Dist: patsy>=0.5
|
|
|
28
28
|
Requires-Dist: scanpy>=1.11
|
|
29
29
|
Requires-Dist: anndata>=0.10
|
|
30
30
|
Requires-Dist: muon>=0.1
|
|
31
|
-
Requires-Dist: harmonypy
|
|
31
|
+
Requires-Dist: harmonypy==0.2.0
|
|
32
32
|
Requires-Dist: python-igraph>=0.10
|
|
33
33
|
Requires-Dist: leidenalg>=0.9
|
|
34
34
|
Requires-Dist: networkx>=3.0
|
|
@@ -51,7 +51,7 @@ A cross-omics, cross-condition **sample embedding** tool for single-cell data.
|
|
|
51
51
|
|
|
52
52
|
SampleDisco takes a cell-level embedding (from any standard scRNA / scATAC / multi-omics integration method) and lifts it to a **sample-level embedding** that captures both cell-type composition and the per-cell-type state of each sample. Every downstream analysis — sample-to-sample distance, clustering, trajectory inference, phenotype association — then runs on that single shared sample embedding, regardless of modality.
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Documentation and tutorial: <https://j041120h.github.io/SampleDisco_tutorial/>
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
@@ -79,46 +79,50 @@ The four blocks are inverse-variance weighted, Frobenius-stacked, PCA-reduced to
|
|
|
79
79
|
|
|
80
80
|
```
|
|
81
81
|
code/
|
|
82
|
-
├──
|
|
83
|
-
├── config/
|
|
84
|
-
├──
|
|
85
|
-
│ ├──
|
|
86
|
-
│ ├──
|
|
87
|
-
│ ├──
|
|
88
|
-
│
|
|
89
|
-
├──
|
|
90
|
-
│ ├──
|
|
91
|
-
│
|
|
92
|
-
│ ├──
|
|
93
|
-
│ ├──
|
|
94
|
-
│ ├──
|
|
95
|
-
│ ├──
|
|
96
|
-
│ ├──
|
|
97
|
-
│
|
|
98
|
-
├──
|
|
99
|
-
│ ├──
|
|
100
|
-
│
|
|
101
|
-
│
|
|
102
|
-
├──
|
|
103
|
-
│
|
|
104
|
-
|
|
105
|
-
├──
|
|
106
|
-
|
|
107
|
-
├──
|
|
108
|
-
├──
|
|
109
|
-
├──
|
|
110
|
-
├──
|
|
111
|
-
|
|
82
|
+
├── pyproject.toml # package metadata; `pip install -e .` installs the `sampledisco` command
|
|
83
|
+
├── config/ # 10 YAML configs for the paper's datasets: covid / blood (1M-scBloodNL) / eye / heart / ENCODE / healthy-aging PBMC / long_covid / unpaired (+ RNA-tuned) / default
|
|
84
|
+
├── src/sampledisco/ # the package
|
|
85
|
+
│ ├── cli.py # `sampledisco` command: `-m complex --config <yaml>`, `--init-config <path>`
|
|
86
|
+
│ ├── config/config_demo.yaml # demo config written by `sampledisco --init-config`
|
|
87
|
+
│ ├── wrapper/ # Orchestration
|
|
88
|
+
│ │ ├── wrapper.py # Master wrapper; gates RNA + ATAC + multiomics + shared downstream
|
|
89
|
+
│ │ ├── rna_wrapper.py
|
|
90
|
+
│ │ ├── atac_wrapper.py
|
|
91
|
+
│ │ └── multiomics_wrapper.py
|
|
92
|
+
│ ├── preparation/ # Preprocessing
|
|
93
|
+
│ │ ├── rna_preprocess_{cpu,gpu}.py # QC → HVG → PCA → dual Harmony → Z_comp + Z_rmd
|
|
94
|
+
│ │ ├── atac_preprocess_{cpu,gpu}.py # QC → TF-IDF → HVF → LSI → dual Harmony → Z_comp + Z_rmd
|
|
95
|
+
│ │ ├── cell_type_{cpu,gpu}.py # Leiden clustering on Z_comp (RNA or ATAC)
|
|
96
|
+
│ │ ├── ATAC_cell_type{,_gpu}.py # ATAC-specific cell typing variants
|
|
97
|
+
│ │ ├── multi_omics_glue.py # scGLUE integration (cross-modality VAE + guidance graph)
|
|
98
|
+
│ │ ├── multi_omics_batch_correction.py # Harmony post-pass on X_glue → Z_comp
|
|
99
|
+
│ │ ├── multi_omics_merge.py # post-GLUE merge + per-modality preprocess/slimming
|
|
100
|
+
│ │ └── multi_omics_cell_type_{cpu,gpu}.py # RNA-Leiden + k-NN label transfer to ATAC
|
|
101
|
+
│ ├── sample_embedding/ # Core method
|
|
102
|
+
│ │ ├── blocks.py # composition, RMD, weighting, Frobenius stack, final PCA + Harmony
|
|
103
|
+
│ │ ├── sample_embedding.py # CPU pipeline
|
|
104
|
+
│ │ └── sample_embedding_gpu.py # GPU pipeline (cuML + cupy)
|
|
105
|
+
│ ├── parameter_selection/
|
|
106
|
+
│ │ └── autotune.py # Bayesian GP sweep over RMD α; adaptive proxy ensemble
|
|
107
|
+
│ ├── sample_distance/ # Pairwise sample distances (DR / EMD / chi-square / JS)
|
|
108
|
+
│ ├── sample_clustering/ # Hierarchical (HRA / HRC / NN / UPGMA / consensus), K-means, proportion test, RAISIN
|
|
109
|
+
│ ├── sample_trajectory/ # CCA (supervised) and TSCAN (unsupervised) + GAM-based trajectory DGE
|
|
110
|
+
│ ├── sample_association/ # Per-PC variance explained vs sample-level covariates (permutation FDR)
|
|
111
|
+
│ ├── visualization/ # Embedding plots, dendrograms, DGE volcanos, modality-aware multi-omics scatters
|
|
112
|
+
│ ├── utils/ # Shared helpers: seed, safe h5ad I/O, limma, TF-IDF, batch regress, Grouping
|
|
113
|
+
│ └── gene_activity/ # ATAC peak → gene activity inference + RNA-ATAC validation
|
|
114
|
+
├── Benchmark_covid/, Benchmark_multiomics/ # benchmark drivers used for the paper
|
|
115
|
+
└── claude/ # one-off run scripts (rerun launchers, monitored runs, parameter sweeps)
|
|
112
116
|
```
|
|
113
117
|
|
|
114
118
|
---
|
|
115
119
|
|
|
116
120
|
## Usage
|
|
117
121
|
|
|
118
|
-
###
|
|
122
|
+
### Run — YAML-driven
|
|
119
123
|
|
|
120
124
|
```bash
|
|
121
|
-
|
|
125
|
+
sampledisco -m complex --config config/config.yaml # same as: python -m sampledisco.cli -m complex --config ...
|
|
122
126
|
```
|
|
123
127
|
|
|
124
128
|
The YAML drives every flag and parameter for all three pipelines:
|
|
@@ -128,14 +132,16 @@ The YAML drives every flag and parameter for all three pipelines:
|
|
|
128
132
|
- **Per-modality downstream gates** (Phase 2): `*_sample_distance_calculation`, `*_trajectory_analysis`, `*_trajectory_dge`, `*_sample_cluster`, `*_proportion_test`, `*_cluster_dge`, `*_visualize_data`, `*_dimension_association_analysis`
|
|
129
133
|
- **Multi-omics-specific**: `multiomics_run_glue_*`, `multiomics_treat_sample_as_batch`, `multiomics_run_glue_twice_for_sample_removal`
|
|
130
134
|
|
|
131
|
-
The
|
|
135
|
+
The 10 ready-to-use configs in `config/` are point-in-time snapshots for the datasets used in the paper; copy one and adjust paths / column names for your own data.
|
|
132
136
|
|
|
133
|
-
###
|
|
137
|
+
### Starter config
|
|
134
138
|
|
|
135
139
|
```bash
|
|
136
|
-
|
|
140
|
+
sampledisco --init-config my_config.yaml
|
|
137
141
|
```
|
|
138
142
|
|
|
143
|
+
writes the demo config (tuned for the Zenodo demo dataset) to `my_config.yaml`. The YAML is validated against every `wrapper()` parameter, so edit values rather than deleting keys.
|
|
144
|
+
|
|
139
145
|
---
|
|
140
146
|
|
|
141
147
|
## Inputs
|
|
@@ -182,7 +188,7 @@ detects and uses them at runtime. There is no separate "GPU build" of SampleDisc
|
|
|
182
188
|
### 1. Core install (CPU)
|
|
183
189
|
|
|
184
190
|
```bash
|
|
185
|
-
pip install sampledisco #
|
|
191
|
+
pip install sampledisco # or `pip install -e .` from a clone
|
|
186
192
|
```
|
|
187
193
|
|
|
188
194
|
### 2. System prerequisite — bedtools
|
|
@@ -4,7 +4,7 @@ A cross-omics, cross-condition **sample embedding** tool for single-cell data.
|
|
|
4
4
|
|
|
5
5
|
SampleDisco takes a cell-level embedding (from any standard scRNA / scATAC / multi-omics integration method) and lifts it to a **sample-level embedding** that captures both cell-type composition and the per-cell-type state of each sample. Every downstream analysis — sample-to-sample distance, clustering, trajectory inference, phenotype association — then runs on that single shared sample embedding, regardless of modality.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Documentation and tutorial: <https://j041120h.github.io/SampleDisco_tutorial/>
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -32,46 +32,50 @@ The four blocks are inverse-variance weighted, Frobenius-stacked, PCA-reduced to
|
|
|
32
32
|
|
|
33
33
|
```
|
|
34
34
|
code/
|
|
35
|
-
├──
|
|
36
|
-
├── config/
|
|
37
|
-
├──
|
|
38
|
-
│ ├──
|
|
39
|
-
│ ├──
|
|
40
|
-
│ ├──
|
|
41
|
-
│
|
|
42
|
-
├──
|
|
43
|
-
│ ├──
|
|
44
|
-
│
|
|
45
|
-
│ ├──
|
|
46
|
-
│ ├──
|
|
47
|
-
│ ├──
|
|
48
|
-
│ ├──
|
|
49
|
-
│ ├──
|
|
50
|
-
│
|
|
51
|
-
├──
|
|
52
|
-
│ ├──
|
|
53
|
-
│
|
|
54
|
-
│
|
|
55
|
-
├──
|
|
56
|
-
│
|
|
57
|
-
|
|
58
|
-
├──
|
|
59
|
-
|
|
60
|
-
├──
|
|
61
|
-
├──
|
|
62
|
-
├──
|
|
63
|
-
├──
|
|
64
|
-
|
|
35
|
+
├── pyproject.toml # package metadata; `pip install -e .` installs the `sampledisco` command
|
|
36
|
+
├── config/ # 10 YAML configs for the paper's datasets: covid / blood (1M-scBloodNL) / eye / heart / ENCODE / healthy-aging PBMC / long_covid / unpaired (+ RNA-tuned) / default
|
|
37
|
+
├── src/sampledisco/ # the package
|
|
38
|
+
│ ├── cli.py # `sampledisco` command: `-m complex --config <yaml>`, `--init-config <path>`
|
|
39
|
+
│ ├── config/config_demo.yaml # demo config written by `sampledisco --init-config`
|
|
40
|
+
│ ├── wrapper/ # Orchestration
|
|
41
|
+
│ │ ├── wrapper.py # Master wrapper; gates RNA + ATAC + multiomics + shared downstream
|
|
42
|
+
│ │ ├── rna_wrapper.py
|
|
43
|
+
│ │ ├── atac_wrapper.py
|
|
44
|
+
│ │ └── multiomics_wrapper.py
|
|
45
|
+
│ ├── preparation/ # Preprocessing
|
|
46
|
+
│ │ ├── rna_preprocess_{cpu,gpu}.py # QC → HVG → PCA → dual Harmony → Z_comp + Z_rmd
|
|
47
|
+
│ │ ├── atac_preprocess_{cpu,gpu}.py # QC → TF-IDF → HVF → LSI → dual Harmony → Z_comp + Z_rmd
|
|
48
|
+
│ │ ├── cell_type_{cpu,gpu}.py # Leiden clustering on Z_comp (RNA or ATAC)
|
|
49
|
+
│ │ ├── ATAC_cell_type{,_gpu}.py # ATAC-specific cell typing variants
|
|
50
|
+
│ │ ├── multi_omics_glue.py # scGLUE integration (cross-modality VAE + guidance graph)
|
|
51
|
+
│ │ ├── multi_omics_batch_correction.py # Harmony post-pass on X_glue → Z_comp
|
|
52
|
+
│ │ ├── multi_omics_merge.py # post-GLUE merge + per-modality preprocess/slimming
|
|
53
|
+
│ │ └── multi_omics_cell_type_{cpu,gpu}.py # RNA-Leiden + k-NN label transfer to ATAC
|
|
54
|
+
│ ├── sample_embedding/ # Core method
|
|
55
|
+
│ │ ├── blocks.py # composition, RMD, weighting, Frobenius stack, final PCA + Harmony
|
|
56
|
+
│ │ ├── sample_embedding.py # CPU pipeline
|
|
57
|
+
│ │ └── sample_embedding_gpu.py # GPU pipeline (cuML + cupy)
|
|
58
|
+
│ ├── parameter_selection/
|
|
59
|
+
│ │ └── autotune.py # Bayesian GP sweep over RMD α; adaptive proxy ensemble
|
|
60
|
+
│ ├── sample_distance/ # Pairwise sample distances (DR / EMD / chi-square / JS)
|
|
61
|
+
│ ├── sample_clustering/ # Hierarchical (HRA / HRC / NN / UPGMA / consensus), K-means, proportion test, RAISIN
|
|
62
|
+
│ ├── sample_trajectory/ # CCA (supervised) and TSCAN (unsupervised) + GAM-based trajectory DGE
|
|
63
|
+
│ ├── sample_association/ # Per-PC variance explained vs sample-level covariates (permutation FDR)
|
|
64
|
+
│ ├── visualization/ # Embedding plots, dendrograms, DGE volcanos, modality-aware multi-omics scatters
|
|
65
|
+
│ ├── utils/ # Shared helpers: seed, safe h5ad I/O, limma, TF-IDF, batch regress, Grouping
|
|
66
|
+
│ └── gene_activity/ # ATAC peak → gene activity inference + RNA-ATAC validation
|
|
67
|
+
├── Benchmark_covid/, Benchmark_multiomics/ # benchmark drivers used for the paper
|
|
68
|
+
└── claude/ # one-off run scripts (rerun launchers, monitored runs, parameter sweeps)
|
|
65
69
|
```
|
|
66
70
|
|
|
67
71
|
---
|
|
68
72
|
|
|
69
73
|
## Usage
|
|
70
74
|
|
|
71
|
-
###
|
|
75
|
+
### Run — YAML-driven
|
|
72
76
|
|
|
73
77
|
```bash
|
|
74
|
-
|
|
78
|
+
sampledisco -m complex --config config/config.yaml # same as: python -m sampledisco.cli -m complex --config ...
|
|
75
79
|
```
|
|
76
80
|
|
|
77
81
|
The YAML drives every flag and parameter for all three pipelines:
|
|
@@ -81,14 +85,16 @@ The YAML drives every flag and parameter for all three pipelines:
|
|
|
81
85
|
- **Per-modality downstream gates** (Phase 2): `*_sample_distance_calculation`, `*_trajectory_analysis`, `*_trajectory_dge`, `*_sample_cluster`, `*_proportion_test`, `*_cluster_dge`, `*_visualize_data`, `*_dimension_association_analysis`
|
|
82
86
|
- **Multi-omics-specific**: `multiomics_run_glue_*`, `multiomics_treat_sample_as_batch`, `multiomics_run_glue_twice_for_sample_removal`
|
|
83
87
|
|
|
84
|
-
The
|
|
88
|
+
The 10 ready-to-use configs in `config/` are point-in-time snapshots for the datasets used in the paper; copy one and adjust paths / column names for your own data.
|
|
85
89
|
|
|
86
|
-
###
|
|
90
|
+
### Starter config
|
|
87
91
|
|
|
88
92
|
```bash
|
|
89
|
-
|
|
93
|
+
sampledisco --init-config my_config.yaml
|
|
90
94
|
```
|
|
91
95
|
|
|
96
|
+
writes the demo config (tuned for the Zenodo demo dataset) to `my_config.yaml`. The YAML is validated against every `wrapper()` parameter, so edit values rather than deleting keys.
|
|
97
|
+
|
|
92
98
|
---
|
|
93
99
|
|
|
94
100
|
## Inputs
|
|
@@ -135,7 +141,7 @@ detects and uses them at runtime. There is no separate "GPU build" of SampleDisc
|
|
|
135
141
|
### 1. Core install (CPU)
|
|
136
142
|
|
|
137
143
|
```bash
|
|
138
|
-
pip install sampledisco #
|
|
144
|
+
pip install sampledisco # or `pip install -e .` from a clone
|
|
139
145
|
```
|
|
140
146
|
|
|
141
147
|
### 2. System prerequisite — bedtools
|
|
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
|
|
|
9
9
|
|
|
10
10
|
[project]
|
|
11
11
|
name = "sampledisco"
|
|
12
|
-
version = "0.
|
|
12
|
+
version = "0.4.0"
|
|
13
13
|
description = "Cross-omics, cross-condition sample embedding for single-cell data (RNA + ATAC)"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.10"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# SampleDisco core (CPU) dependencies
|
|
2
|
-
# The deep-learning stack (
|
|
1
|
+
# SampleDisco core (CPU) dependencies. PyTorch is installed as a dependency of harmonypy 0.2.0.
|
|
2
|
+
# The deep-learning stack (scglue, harmony-pytorch, a CUDA build of torch) and RAPIDS GPU
|
|
3
3
|
# acceleration are NOT installed here — they're OS/driver-specific and you
|
|
4
4
|
# install them yourself (see the Installation page). SampleDisco falls back to
|
|
5
5
|
# the CPU path when they are absent.
|
|
@@ -14,7 +14,7 @@ patsy>=0.5
|
|
|
14
14
|
scanpy>=1.11
|
|
15
15
|
anndata>=0.10
|
|
16
16
|
muon>=0.1
|
|
17
|
-
harmonypy
|
|
17
|
+
harmonypy==0.2.0 # the version used for the published results; it requires torch
|
|
18
18
|
python-igraph>=0.10
|
|
19
19
|
leidenalg>=0.9
|
|
20
20
|
networkx>=3.0
|
|
@@ -35,14 +35,27 @@ def load_config(config_path):
|
|
|
35
35
|
return yaml.safe_load(f)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
# Keys added after 0.2.0. Configs written before them may omit these keys, which
|
|
39
|
+
# then take their wrapper() default; every other key stays required.
|
|
40
|
+
OPTIONAL_KEYS = frozenset(f"{m}_save_cell_adata_after_embedding"
|
|
41
|
+
for m in ("rna", "atac", "multiomics"))
|
|
42
|
+
|
|
43
|
+
|
|
38
44
|
def validate_config(config, func):
|
|
39
45
|
valid_params = inspect.signature(func).parameters
|
|
40
46
|
for key in config:
|
|
41
47
|
if key not in valid_params:
|
|
42
48
|
raise ValueError(f"Unexpected parameter in config: '{key}'")
|
|
43
49
|
for key in valid_params:
|
|
44
|
-
if key not in config:
|
|
50
|
+
if key not in config and key not in OPTIONAL_KEYS:
|
|
45
51
|
raise ValueError(f"Missing required parameter in config: '{key}'")
|
|
52
|
+
from sampledisco.sample_embedding.blocks import check_rmd_weight
|
|
53
|
+
for key in config:
|
|
54
|
+
if key.endswith("_sample_embedding_rmd_weight"):
|
|
55
|
+
try:
|
|
56
|
+
check_rmd_weight(config[key])
|
|
57
|
+
except ValueError as e:
|
|
58
|
+
raise ValueError(f"Invalid value for '{key}': {e}") from None
|
|
46
59
|
|
|
47
60
|
|
|
48
61
|
def write_template(dest):
|
|
@@ -86,9 +86,10 @@ rna_sample_embedding_rmd_dim: 8
|
|
|
86
86
|
rna_sample_embedding_use_clr: false
|
|
87
87
|
rna_sample_embedding_use_rmd: true
|
|
88
88
|
rna_sample_embedding_block_weights:
|
|
89
|
-
rna_sample_embedding_rmd_weight: 0.60
|
|
89
|
+
rna_sample_embedding_rmd_weight: "equal" # RMD block weight α: "equal" = same energy as the three composition blocks together (α² = w_A1² + w_A2² + w_A3²); a number fixes α (0.60 = previous default)
|
|
90
90
|
rna_sample_embedding_pca_components: 10
|
|
91
91
|
rna_sample_embedding_batch_method: "harmony"
|
|
92
|
+
rna_save_cell_adata_after_embedding: true # store .uns['X_DR_sample'] in the cell-level h5ad (updated in place; false = keep only the CSV)
|
|
92
93
|
|
|
93
94
|
# --- Autotune Parameters ---
|
|
94
95
|
rna_autotune_enable: false
|
|
@@ -207,9 +208,10 @@ atac_sample_embedding_rmd_dim: 8
|
|
|
207
208
|
atac_sample_embedding_use_clr: false
|
|
208
209
|
atac_sample_embedding_use_rmd: true
|
|
209
210
|
atac_sample_embedding_block_weights:
|
|
210
|
-
atac_sample_embedding_rmd_weight: 0.60
|
|
211
|
+
atac_sample_embedding_rmd_weight: "equal" # RMD block weight α: "equal" = same energy as the three composition blocks together (α² = w_A1² + w_A2² + w_A3²); a number fixes α (0.60 = previous default)
|
|
211
212
|
atac_sample_embedding_pca_components: 10
|
|
212
213
|
atac_sample_embedding_batch_method: "harmony"
|
|
214
|
+
atac_save_cell_adata_after_embedding: true # store .uns['X_DR_sample'] in the cell-level h5ad (updated in place; false = keep only the CSV)
|
|
213
215
|
|
|
214
216
|
# --- Autotune Parameters ---
|
|
215
217
|
atac_autotune_enable: false
|
|
@@ -375,9 +377,10 @@ multiomics_sample_embedding_rmd_dim: 8
|
|
|
375
377
|
multiomics_sample_embedding_use_clr: false
|
|
376
378
|
multiomics_sample_embedding_use_rmd: true
|
|
377
379
|
multiomics_sample_embedding_block_weights:
|
|
378
|
-
multiomics_sample_embedding_rmd_weight: 0.60
|
|
380
|
+
multiomics_sample_embedding_rmd_weight: "equal" # RMD block weight α: "equal" = same energy as the three composition blocks together (α² = w_A1² + w_A2² + w_A3²); a number fixes α (0.60 = previous default)
|
|
379
381
|
multiomics_sample_embedding_pca_components: 10
|
|
380
382
|
multiomics_sample_embedding_batch_method: "harmony"
|
|
383
|
+
multiomics_save_cell_adata_after_embedding: true # store .uns['X_DR_sample'] in the cell-level h5ad (updated in place; false = keep only the CSV)
|
|
381
384
|
|
|
382
385
|
# --- Autotune Parameters ---
|
|
383
386
|
multiomics_autotune_enable: false
|
|
@@ -25,7 +25,6 @@ from typing import Callable, Dict, List, Optional, Tuple, Union
|
|
|
25
25
|
|
|
26
26
|
import numpy as np
|
|
27
27
|
import pandas as pd
|
|
28
|
-
import scanpy as sc
|
|
29
28
|
from anndata import AnnData
|
|
30
29
|
from sklearn.cluster import MiniBatchKMeans
|
|
31
30
|
from sklearn.cross_decomposition import CCA
|
|
@@ -44,7 +43,10 @@ from sampledisco.sample_embedding.blocks import (
|
|
|
44
43
|
composition_per_unit,
|
|
45
44
|
derive_weights,
|
|
46
45
|
loo_rmd,
|
|
46
|
+
resolve_rmd_weight,
|
|
47
|
+
save_embedding_to_h5ad,
|
|
47
48
|
soft_assign,
|
|
49
|
+
warn_if_stale_embedding,
|
|
48
50
|
)
|
|
49
51
|
from sampledisco.utils.embedding_keys import resolve_comp_key, resolve_rmd_key
|
|
50
52
|
|
|
@@ -606,6 +608,7 @@ def run_autotune(
|
|
|
606
608
|
verbose: bool = True,
|
|
607
609
|
tune_on_modality: Optional[str] = None,
|
|
608
610
|
cluster_emb_key: Optional[str] = None,
|
|
611
|
+
save_cell_adata: bool = True,
|
|
609
612
|
) -> Dict:
|
|
610
613
|
"""Run autotune and return the best params + final sample-AnnData.
|
|
611
614
|
|
|
@@ -660,11 +663,12 @@ def run_autotune(
|
|
|
660
663
|
f"scoring on {int(score_mask.sum())}/{blocks['n_units']} units")
|
|
661
664
|
|
|
662
665
|
if not score_meta["has_batch"] and not score_meta["has_grouping"]:
|
|
666
|
+
alpha = resolve_rmd_weight("equal", blocks["K_c"], blocks["K_med"], blocks["K_fine"])
|
|
663
667
|
if verbose:
|
|
664
|
-
print("[autotune] no batch and no grouping column → using
|
|
665
|
-
"no search performed.")
|
|
668
|
+
print(f"[autotune] no batch and no grouping column → using the default "
|
|
669
|
+
f"rmd_weight='equal' (α={alpha}); no search performed.")
|
|
666
670
|
weights = derive_weights(blocks["K_c"], blocks["K_med"], blocks["K_fine"],
|
|
667
|
-
rmd_weight=
|
|
671
|
+
rmd_weight=alpha, n_blocks=4)
|
|
668
672
|
final_emb = build_emb_from_blocks(
|
|
669
673
|
[blocks["A1"], blocks["A2"], blocks["A3"], blocks["RMD"]],
|
|
670
674
|
weights,
|
|
@@ -675,13 +679,13 @@ def run_autotune(
|
|
|
675
679
|
seed=seed, verbose=verbose,
|
|
676
680
|
)
|
|
677
681
|
return _finalize(adata, blocks, final_emb, weights,
|
|
678
|
-
best_params={"rmd_weight":
|
|
682
|
+
best_params={"rmd_weight": alpha},
|
|
679
683
|
best_score=float("nan"),
|
|
680
684
|
trace=[], search=search, scoring=scoring,
|
|
681
685
|
scope=scope, alpha_bounds=alpha_bounds,
|
|
682
686
|
pca_components=pca_components,
|
|
683
687
|
batch_method=batch_method,
|
|
684
|
-
output_dir=output_dir, save=save,
|
|
688
|
+
output_dir=output_dir, save=save, save_cell_adata=save_cell_adata,
|
|
685
689
|
t_start=t0, verbose=verbose,
|
|
686
690
|
tune_on_modality=tune_on_modality,
|
|
687
691
|
score_n_units=int(score_mask.sum()))
|
|
@@ -737,7 +741,7 @@ def run_autotune(
|
|
|
737
741
|
scope=scope, alpha_bounds=alpha_bounds,
|
|
738
742
|
pca_components=pca_components,
|
|
739
743
|
batch_method=batch_method,
|
|
740
|
-
output_dir=output_dir, save=save,
|
|
744
|
+
output_dir=output_dir, save=save, save_cell_adata=save_cell_adata,
|
|
741
745
|
t_start=t0, verbose=verbose,
|
|
742
746
|
tune_on_modality=tune_on_modality,
|
|
743
747
|
score_n_units=int(score_mask.sum()))
|
|
@@ -869,7 +873,7 @@ def _finalize(adata, blocks, final_emb, weights, *,
|
|
|
869
873
|
search, scoring, scope, alpha_bounds,
|
|
870
874
|
pca_components, batch_method,
|
|
871
875
|
output_dir, save, t_start, verbose,
|
|
872
|
-
tune_on_modality=None, score_n_units=None):
|
|
876
|
+
tune_on_modality=None, score_n_units=None, save_cell_adata=True):
|
|
873
877
|
"""Write the autotuned embedding into the cell-level adata and persist artifacts."""
|
|
874
878
|
elapsed = time.time() - t_start
|
|
875
879
|
adata.uns["X_DR_sample"] = final_emb.copy()
|
|
@@ -905,9 +909,11 @@ def _finalize(adata, blocks, final_emb, weights, *,
|
|
|
905
909
|
final_emb.to_csv(emb_csv)
|
|
906
910
|
|
|
907
911
|
preprocessed_h5 = os.path.join(output_dir, "preprocess", "adata_preprocessed.h5ad")
|
|
908
|
-
if
|
|
912
|
+
if not save_cell_adata:
|
|
913
|
+
warn_if_stale_embedding(preprocessed_h5, emb_csv)
|
|
914
|
+
if save_cell_adata and os.path.exists(preprocessed_h5):
|
|
909
915
|
try:
|
|
910
|
-
|
|
916
|
+
save_embedding_to_h5ad(preprocessed_h5, adata)
|
|
911
917
|
except Exception as exc:
|
|
912
918
|
if verbose:
|
|
913
919
|
print(f"[autotune] WARNING: could not re-save "
|
|
@@ -94,20 +94,21 @@ def cell_types(
|
|
|
94
94
|
if verbose:
|
|
95
95
|
print(f"{indent}[cell_types] Found {num_clusters_found} clusters")
|
|
96
96
|
|
|
97
|
+
# Recursive results fall through (no early return) so the depth-0
|
|
98
|
+
# call still runs its UMAP / CSV / save block below.
|
|
99
|
+
new_resolution = leiden_cluster_resolution + RESOLUTION_STEP
|
|
97
100
|
if num_clusters_found >= n_target_clusters:
|
|
98
101
|
if num_clusters_found > n_target_clusters and verbose:
|
|
99
102
|
print(f"{indent}[cell_types] Over-shot target; recursing with dendrogram aggregation")
|
|
100
103
|
|
|
101
|
-
|
|
104
|
+
adata = cell_types(
|
|
102
105
|
anndata_cell=adata, cell_type_column="cell_type",
|
|
103
106
|
existing_cell_types=True, n_target_clusters=n_target_clusters, umap=False, save=False,
|
|
104
107
|
cell_embedding_column=cell_embedding_column, cell_embedding_num_PCs=cell_embedding_num_PCs,
|
|
105
108
|
verbose=verbose, umap_plots=False, _recursion_depth=_recursion_depth + 1,
|
|
106
109
|
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if new_resolution <= MAX_RESOLUTION:
|
|
110
|
-
return cell_types(
|
|
110
|
+
elif new_resolution <= MAX_RESOLUTION:
|
|
111
|
+
adata = cell_types(
|
|
111
112
|
anndata_cell=adata, cell_type_column=cell_type_column,
|
|
112
113
|
existing_cell_types=False, n_target_clusters=n_target_clusters, umap=False, save=False,
|
|
113
114
|
leiden_cluster_resolution=new_resolution, cell_embedding_column=cell_embedding_column,
|
|
@@ -98,20 +98,21 @@ def cell_types_gpu(
|
|
|
98
98
|
if verbose:
|
|
99
99
|
print(f"{indent}[cell_types] Found {num_clusters_found} clusters")
|
|
100
100
|
|
|
101
|
+
# Recursive results fall through (no early return) so the
|
|
102
|
+
# depth-0 call still runs its UMAP / CSV / save block below.
|
|
103
|
+
new_resolution = leiden_cluster_resolution + RESOLUTION_STEP
|
|
101
104
|
if num_clusters_found >= n_target_clusters:
|
|
102
105
|
if num_clusters_found > n_target_clusters and verbose:
|
|
103
106
|
print(f"{indent}[cell_types] Over-shot target; recursing with dendrogram aggregation")
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
adata = cell_types_gpu(
|
|
106
109
|
anndata_cell=adata, cell_type_column="cell_type",
|
|
107
110
|
existing_cell_types=True, n_target_clusters=n_target_clusters, umap=False, save=False,
|
|
108
111
|
cell_embedding_column=cell_embedding_column, cell_embedding_num_PCs=cell_embedding_num_PCs,
|
|
109
112
|
verbose=verbose, umap_plots=False, _recursion_depth=_recursion_depth + 1,
|
|
110
113
|
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if new_resolution <= MAX_RESOLUTION:
|
|
114
|
-
return cell_types_gpu(
|
|
114
|
+
elif new_resolution <= MAX_RESOLUTION:
|
|
115
|
+
adata = cell_types_gpu(
|
|
115
116
|
anndata_cell=adata, cell_type_column=cell_type_column,
|
|
116
117
|
existing_cell_types=False, n_target_clusters=n_target_clusters, umap=False, save=False,
|
|
117
118
|
leiden_cluster_resolution=new_resolution, cell_embedding_column=cell_embedding_column,
|
|
@@ -37,13 +37,14 @@ def compute_sample_embedding(
|
|
|
37
37
|
use_clr: bool = False,
|
|
38
38
|
use_rmd: bool = True,
|
|
39
39
|
block_weights: Optional[List[float]] = None,
|
|
40
|
-
rmd_weight: float =
|
|
40
|
+
rmd_weight: Union[float, str] = "equal",
|
|
41
41
|
pca_components: int = 10,
|
|
42
42
|
batch_method: str = "harmony",
|
|
43
43
|
save: bool = True,
|
|
44
44
|
verbose: bool = True,
|
|
45
45
|
seed: int = 42,
|
|
46
46
|
cluster_emb_key: Optional[str] = None,
|
|
47
|
+
save_cell_adata: bool = True,
|
|
47
48
|
) -> AnnData:
|
|
48
49
|
"""Dispatch to the CPU or GPU implementation.
|
|
49
50
|
|
|
@@ -53,6 +54,8 @@ def compute_sample_embedding(
|
|
|
53
54
|
around the module import is not enough (the import succeeds and the crash
|
|
54
55
|
lands mid-run). We probe for the stack up front and also guard the call.
|
|
55
56
|
"""
|
|
57
|
+
from sampledisco.sample_embedding.blocks import check_rmd_weight
|
|
58
|
+
check_rmd_weight(rmd_weight) # before the GPU attempt, so a bad value is not reported as "GPU unavailable"
|
|
56
59
|
kwargs = dict(
|
|
57
60
|
sample_col=sample_col,
|
|
58
61
|
celltype_col=celltype_col,
|
|
@@ -73,6 +76,7 @@ def compute_sample_embedding(
|
|
|
73
76
|
save=save,
|
|
74
77
|
verbose=verbose,
|
|
75
78
|
seed=seed,
|
|
79
|
+
save_cell_adata=save_cell_adata,
|
|
76
80
|
)
|
|
77
81
|
|
|
78
82
|
if use_gpu and not _gpu_stack_available():
|