diffbio 0.1.0__py3-none-any.whl
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.
- diffbio/__init__.py +39 -0
- diffbio/configs.py +75 -0
- diffbio/constants.py +204 -0
- diffbio/core/__init__.py +127 -0
- diffbio/core/base_operators.py +612 -0
- diffbio/core/data_types.py +260 -0
- diffbio/core/gnn_components.py +629 -0
- diffbio/core/graph_utils.py +149 -0
- diffbio/core/neural_components.py +270 -0
- diffbio/core/optimal_transport.py +133 -0
- diffbio/core/soft_ops/__init__.py +216 -0
- diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
- diffbio/core/soft_ops/_projections_simplex.py +240 -0
- diffbio/core/soft_ops/_projections_transport.py +508 -0
- diffbio/core/soft_ops/_sorting_network.py +204 -0
- diffbio/core/soft_ops/_types.py +15 -0
- diffbio/core/soft_ops/_utils.py +342 -0
- diffbio/core/soft_ops/autograd_safe.py +120 -0
- diffbio/core/soft_ops/comparison.py +235 -0
- diffbio/core/soft_ops/elementwise.py +309 -0
- diffbio/core/soft_ops/logical.py +146 -0
- diffbio/core/soft_ops/quantile.py +376 -0
- diffbio/core/soft_ops/selection.py +236 -0
- diffbio/core/soft_ops/sorting.py +926 -0
- diffbio/core/soft_ops/straight_through.py +261 -0
- diffbio/core/uncertainty.py +279 -0
- diffbio/evaluation/__init__.py +42 -0
- diffbio/evaluation/adapters.py +409 -0
- diffbio/evaluation/graders.py +223 -0
- diffbio/evaluation/problem.py +157 -0
- diffbio/evaluation/runner.py +277 -0
- diffbio/losses/__init__.py +59 -0
- diffbio/losses/alignment_losses.py +222 -0
- diffbio/losses/biological_regularization.py +288 -0
- diffbio/losses/metric_losses.py +139 -0
- diffbio/losses/singlecell_losses.py +387 -0
- diffbio/losses/statistical_losses.py +345 -0
- diffbio/operators/__init__.py +60 -0
- diffbio/operators/_count_vae.py +197 -0
- diffbio/operators/_loss_balancing.py +65 -0
- diffbio/operators/_masked_gene_transformer.py +118 -0
- diffbio/operators/_transformer_validation.py +50 -0
- diffbio/operators/alignment/__init__.py +51 -0
- diffbio/operators/alignment/profile_hmm.py +350 -0
- diffbio/operators/alignment/scoring.py +127 -0
- diffbio/operators/alignment/smith_waterman.py +261 -0
- diffbio/operators/alignment/soft_msa.py +419 -0
- diffbio/operators/assembly/__init__.py +27 -0
- diffbio/operators/assembly/gnn_assembly.py +252 -0
- diffbio/operators/assembly/metagenomic_binning.py +296 -0
- diffbio/operators/crispr/__init__.py +17 -0
- diffbio/operators/crispr/guide_scoring.py +269 -0
- diffbio/operators/drug_discovery/__init__.py +133 -0
- diffbio/operators/drug_discovery/_graph_utils.py +142 -0
- diffbio/operators/drug_discovery/admet_predictor.py +285 -0
- diffbio/operators/drug_discovery/attentive_fp.py +411 -0
- diffbio/operators/drug_discovery/dti.py +261 -0
- diffbio/operators/drug_discovery/fingerprint.py +490 -0
- diffbio/operators/drug_discovery/maccs_keys.py +267 -0
- diffbio/operators/drug_discovery/message_passing.py +200 -0
- diffbio/operators/drug_discovery/primitives.py +242 -0
- diffbio/operators/drug_discovery/property_predictor.py +163 -0
- diffbio/operators/drug_discovery/similarity.py +193 -0
- diffbio/operators/epigenomics/__init__.py +35 -0
- diffbio/operators/epigenomics/chromatin_state.py +491 -0
- diffbio/operators/epigenomics/contextual.py +288 -0
- diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
- diffbio/operators/epigenomics/peak_calling.py +555 -0
- diffbio/operators/foundation_models/__init__.py +119 -0
- diffbio/operators/foundation_models/adapters.py +114 -0
- diffbio/operators/foundation_models/contracts.py +245 -0
- diffbio/operators/foundation_models/embedding_probe.py +83 -0
- diffbio/operators/foundation_models/experimental.py +128 -0
- diffbio/operators/foundation_models/foundation_model.py +332 -0
- diffbio/operators/foundation_models/frozen.py +59 -0
- diffbio/operators/foundation_models/precomputed.py +270 -0
- diffbio/operators/foundation_models/transformer_encoder.py +564 -0
- diffbio/operators/mapping/__init__.py +17 -0
- diffbio/operators/mapping/neural_mapper.py +493 -0
- diffbio/operators/metabolomics/__init__.py +39 -0
- diffbio/operators/metabolomics/spectral_similarity.py +315 -0
- diffbio/operators/molecular_dynamics/__init__.py +51 -0
- diffbio/operators/molecular_dynamics/force_field.py +265 -0
- diffbio/operators/molecular_dynamics/integrator.py +304 -0
- diffbio/operators/molecular_dynamics/primitives.py +115 -0
- diffbio/operators/multiomics/__init__.py +38 -0
- diffbio/operators/multiomics/hic_contact.py +377 -0
- diffbio/operators/multiomics/multiomics_vae.py +325 -0
- diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
- diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
- diffbio/operators/normalization/__init__.py +42 -0
- diffbio/operators/normalization/embedding.py +222 -0
- diffbio/operators/normalization/phate.py +400 -0
- diffbio/operators/normalization/umap.py +261 -0
- diffbio/operators/normalization/vae_normalizer.py +258 -0
- diffbio/operators/population/__init__.py +17 -0
- diffbio/operators/population/ancestry_estimation.py +274 -0
- diffbio/operators/preprocessing/__init__.py +76 -0
- diffbio/operators/preprocessing/adapter_removal.py +311 -0
- diffbio/operators/preprocessing/duplicate_filter.py +317 -0
- diffbio/operators/preprocessing/error_correction.py +287 -0
- diffbio/operators/protein/__init__.py +31 -0
- diffbio/operators/protein/secondary_structure.py +509 -0
- diffbio/operators/quality_filter.py +128 -0
- diffbio/operators/rna_structure/__init__.py +35 -0
- diffbio/operators/rna_structure/rna_folding.py +509 -0
- diffbio/operators/rnaseq/__init__.py +23 -0
- diffbio/operators/rnaseq/motif_discovery.py +251 -0
- diffbio/operators/rnaseq/splicing_psi.py +216 -0
- diffbio/operators/singlecell/__init__.py +193 -0
- diffbio/operators/singlecell/ambient_removal.py +333 -0
- diffbio/operators/singlecell/archetypes.py +191 -0
- diffbio/operators/singlecell/batch_correction.py +288 -0
- diffbio/operators/singlecell/cell_annotation.py +519 -0
- diffbio/operators/singlecell/communication.py +704 -0
- diffbio/operators/singlecell/differential_distribution.py +243 -0
- diffbio/operators/singlecell/doublet_detection.py +657 -0
- diffbio/operators/singlecell/downsampling.py +166 -0
- diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
- diffbio/operators/singlecell/grn_inference.py +336 -0
- diffbio/operators/singlecell/imputation.py +429 -0
- diffbio/operators/singlecell/knockdown_filter.py +176 -0
- diffbio/operators/singlecell/ot_trajectory.py +277 -0
- diffbio/operators/singlecell/simulation.py +444 -0
- diffbio/operators/singlecell/sindy_grn.py +247 -0
- diffbio/operators/singlecell/soft_clustering.py +211 -0
- diffbio/operators/singlecell/spatial_domains.py +677 -0
- diffbio/operators/singlecell/switch_de.py +184 -0
- diffbio/operators/singlecell/trajectory.py +447 -0
- diffbio/operators/singlecell/velocity.py +361 -0
- diffbio/operators/statistical/__init__.py +35 -0
- diffbio/operators/statistical/em_quantification.py +260 -0
- diffbio/operators/statistical/hmm.py +234 -0
- diffbio/operators/statistical/nb_glm.py +272 -0
- diffbio/operators/variant/__init__.py +64 -0
- diffbio/operators/variant/classifier.py +333 -0
- diffbio/operators/variant/cnn_classifier.py +255 -0
- diffbio/operators/variant/cnv_segmentation.py +678 -0
- diffbio/operators/variant/deepvariant_pileup.py +426 -0
- diffbio/operators/variant/pileup.py +240 -0
- diffbio/operators/variant/quality_recalibration.py +274 -0
- diffbio/pipelines/__init__.py +65 -0
- diffbio/pipelines/differential_expression.py +279 -0
- diffbio/pipelines/enhanced_variant_calling.py +326 -0
- diffbio/pipelines/perturbation.py +407 -0
- diffbio/pipelines/preprocessing.py +267 -0
- diffbio/pipelines/single_cell.py +366 -0
- diffbio/pipelines/variant_calling.py +490 -0
- diffbio/samplers/__init__.py +9 -0
- diffbio/samplers/perturbation_sampler.py +142 -0
- diffbio/sequences/__init__.py +34 -0
- diffbio/sequences/dna.py +239 -0
- diffbio/sources/__init__.py +149 -0
- diffbio/sources/_anndata_shared.py +89 -0
- diffbio/sources/_batch_iteration.py +37 -0
- diffbio/sources/_benchmark_source.py +152 -0
- diffbio/sources/_indexed_batch_source.py +38 -0
- diffbio/sources/_utils.py +45 -0
- diffbio/sources/anndata_interop.py +387 -0
- diffbio/sources/anndata_source.py +361 -0
- diffbio/sources/archive_ii.py +174 -0
- diffbio/sources/balifam.py +207 -0
- diffbio/sources/bam.py +265 -0
- diffbio/sources/bengrn_ground_truth.py +306 -0
- diffbio/sources/contextual_epigenomics.py +242 -0
- diffbio/sources/dti.py +359 -0
- diffbio/sources/embeddings.py +203 -0
- diffbio/sources/encode_peaks.py +223 -0
- diffbio/sources/fasta.py +226 -0
- diffbio/sources/immune_human.py +172 -0
- diffbio/sources/indexed_embeddings.py +128 -0
- diffbio/sources/indexed_view.py +191 -0
- diffbio/sources/molnet.py +493 -0
- diffbio/sources/multiomics.py +279 -0
- diffbio/sources/pancreas.py +108 -0
- diffbio/sources/perturbation/__init__.py +69 -0
- diffbio/sources/perturbation/_types.py +51 -0
- diffbio/sources/perturbation/_utils.py +125 -0
- diffbio/sources/perturbation/concat_source.py +115 -0
- diffbio/sources/perturbation/control_mapping.py +215 -0
- diffbio/sources/perturbation/experiment_config.py +261 -0
- diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
- diffbio/sources/perturbation/output_space.py +52 -0
- diffbio/sources/perturbation/perturbation_source.py +513 -0
- diffbio/sources/seqfish.py +145 -0
- diffbio/sources/sequence_foundation.py +68 -0
- diffbio/sources/singlecell_foundation.py +68 -0
- diffbio/splitters/__init__.py +63 -0
- diffbio/splitters/base.py +251 -0
- diffbio/splitters/molecular.py +330 -0
- diffbio/splitters/perturbation.py +199 -0
- diffbio/splitters/random.py +217 -0
- diffbio/splitters/sequence.py +201 -0
- diffbio/utils/__init__.py +55 -0
- diffbio/utils/dependency_runtime.py +115 -0
- diffbio/utils/nn_utils.py +157 -0
- diffbio/utils/quality.py +45 -0
- diffbio/utils/training.py +585 -0
- diffbio-0.1.0.dist-info/METADATA +480 -0
- diffbio-0.1.0.dist-info/RECORD +202 -0
- diffbio-0.1.0.dist-info/WHEEL +4 -0
- diffbio-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""End-to-end differentiable single-cell analysis pipeline.
|
|
2
|
+
|
|
3
|
+
This module provides a complete single-cell RNA-seq analysis pipeline that composes:
|
|
4
|
+
1. Ambient RNA removal - CellBender-style decontamination
|
|
5
|
+
2. VAE normalization - scVI-style count normalization
|
|
6
|
+
3. Batch correction - Harmony-style integration
|
|
7
|
+
4. Dimensionality reduction - Parametric UMAP
|
|
8
|
+
5. Clustering - Soft k-means clustering
|
|
9
|
+
|
|
10
|
+
The pipeline is fully differentiable, enabling gradient-based optimization
|
|
11
|
+
of all analysis components jointly.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import jax
|
|
19
|
+
import jax.numpy as jnp
|
|
20
|
+
from datarax.core.config import OperatorConfig
|
|
21
|
+
from datarax.core.operator import OperatorModule
|
|
22
|
+
from flax import nnx
|
|
23
|
+
from jaxtyping import Array, Float
|
|
24
|
+
|
|
25
|
+
from diffbio.operators.normalization import (
|
|
26
|
+
DifferentiableUMAP,
|
|
27
|
+
UMAPConfig,
|
|
28
|
+
VAENormalizer,
|
|
29
|
+
VAENormalizerConfig,
|
|
30
|
+
)
|
|
31
|
+
from diffbio.operators.singlecell import (
|
|
32
|
+
AmbientRemovalConfig,
|
|
33
|
+
BatchCorrectionConfig,
|
|
34
|
+
DifferentiableAmbientRemoval,
|
|
35
|
+
DifferentiableHarmony,
|
|
36
|
+
SoftClusteringConfig,
|
|
37
|
+
SoftKMeansClustering,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class SingleCellPipelineConfig(OperatorConfig):
|
|
45
|
+
# pylint: disable=too-many-instance-attributes
|
|
46
|
+
"""Configuration for the single-cell analysis pipeline.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
n_genes: Number of genes in the expression matrix.
|
|
50
|
+
n_clusters: Number of clusters for soft k-means.
|
|
51
|
+
latent_dim: Dimension of the VAE latent space.
|
|
52
|
+
hidden_dims: Hidden layer dimensions for VAE.
|
|
53
|
+
umap_n_components: Number of UMAP output dimensions.
|
|
54
|
+
batch_correction_clusters: Number of clusters for Harmony.
|
|
55
|
+
batch_correction_iterations: Number of Harmony iterations.
|
|
56
|
+
clustering_temperature: Temperature for soft clustering.
|
|
57
|
+
enable_ambient_removal: Whether to enable ambient RNA removal.
|
|
58
|
+
enable_batch_correction: Whether to enable batch correction.
|
|
59
|
+
enable_dim_reduction: Whether to enable UMAP dimensionality reduction.
|
|
60
|
+
enable_clustering: Whether to enable soft clustering.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
n_genes: int = 2000
|
|
64
|
+
n_clusters: int = 10
|
|
65
|
+
latent_dim: int = 64
|
|
66
|
+
hidden_dims: tuple[int, ...] = (128, 64)
|
|
67
|
+
umap_n_components: int = 2
|
|
68
|
+
batch_correction_clusters: int = 100
|
|
69
|
+
batch_correction_iterations: int = 10
|
|
70
|
+
clustering_temperature: float = 1.0
|
|
71
|
+
enable_ambient_removal: bool = True
|
|
72
|
+
enable_batch_correction: bool = True
|
|
73
|
+
enable_dim_reduction: bool = True
|
|
74
|
+
enable_clustering: bool = True
|
|
75
|
+
|
|
76
|
+
def __post_init__(self) -> None:
|
|
77
|
+
"""Set non-default stochastic fields."""
|
|
78
|
+
object.__setattr__(self, "stochastic", True)
|
|
79
|
+
if self.stream_name is None:
|
|
80
|
+
object.__setattr__(self, "stream_name", "sample")
|
|
81
|
+
super().__post_init__()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SingleCellPipeline(OperatorModule):
|
|
85
|
+
"""End-to-end differentiable single-cell analysis pipeline.
|
|
86
|
+
|
|
87
|
+
This pipeline processes single-cell RNA-seq data through multiple analysis steps:
|
|
88
|
+
|
|
89
|
+
Input data structure:
|
|
90
|
+
- counts: Float[Array, "n_cells n_genes"] - Raw count matrix
|
|
91
|
+
- ambient_profile: Float[Array, "n_genes"] - Ambient expression profile
|
|
92
|
+
- batch_labels: Int[Array, "n_cells"] - Batch assignments
|
|
93
|
+
|
|
94
|
+
Output data structure (adds):
|
|
95
|
+
- decontaminated_counts: Ambient-removed counts (if enabled)
|
|
96
|
+
- normalized: VAE-normalized expression
|
|
97
|
+
- latent: Latent space representation
|
|
98
|
+
- corrected_embeddings: Batch-corrected embeddings (if enabled)
|
|
99
|
+
- embeddings_2d: 2D UMAP embeddings (if enabled)
|
|
100
|
+
- cluster_assignments: Soft cluster assignments
|
|
101
|
+
|
|
102
|
+
The pipeline is fully differentiable, supporting gradient-based training
|
|
103
|
+
to optimize all components jointly for tasks like:
|
|
104
|
+
- Supervised cell type classification
|
|
105
|
+
- Semi-supervised clustering
|
|
106
|
+
- Multi-task learning across batches
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
```python
|
|
110
|
+
config = SingleCellPipelineConfig(n_genes=2000, n_clusters=10)
|
|
111
|
+
pipeline = SingleCellPipeline(config, rngs=nnx.Rngs(42))
|
|
112
|
+
result, state, meta = pipeline.apply(data, {}, None)
|
|
113
|
+
clusters = result["cluster_assignments"]
|
|
114
|
+
```
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
config: SingleCellPipelineConfig,
|
|
120
|
+
*,
|
|
121
|
+
rngs: nnx.Rngs,
|
|
122
|
+
name: str | None = None,
|
|
123
|
+
):
|
|
124
|
+
"""Initialize the single-cell analysis pipeline.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
config: Pipeline configuration.
|
|
128
|
+
rngs: Random number generators for parameter initialization.
|
|
129
|
+
name: Optional name for the pipeline.
|
|
130
|
+
"""
|
|
131
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
132
|
+
|
|
133
|
+
# 1. Ambient RNA removal (optional)
|
|
134
|
+
self.ambient_removal = (
|
|
135
|
+
DifferentiableAmbientRemoval(
|
|
136
|
+
AmbientRemovalConfig(
|
|
137
|
+
n_genes=config.n_genes,
|
|
138
|
+
latent_dim=config.latent_dim,
|
|
139
|
+
hidden_dims=list(config.hidden_dims),
|
|
140
|
+
),
|
|
141
|
+
rngs=rngs,
|
|
142
|
+
)
|
|
143
|
+
if config.enable_ambient_removal
|
|
144
|
+
else None
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# 2. VAE normalization (always enabled - core component)
|
|
148
|
+
self.vae_normalizer = VAENormalizer(
|
|
149
|
+
VAENormalizerConfig(
|
|
150
|
+
n_genes=config.n_genes,
|
|
151
|
+
latent_dim=config.latent_dim,
|
|
152
|
+
hidden_dims=list(config.hidden_dims),
|
|
153
|
+
),
|
|
154
|
+
rngs=rngs,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# 3. Batch correction (optional)
|
|
158
|
+
self.batch_correction = (
|
|
159
|
+
DifferentiableHarmony(
|
|
160
|
+
BatchCorrectionConfig(
|
|
161
|
+
n_clusters=config.batch_correction_clusters,
|
|
162
|
+
n_features=config.latent_dim, # Must match latent dimension
|
|
163
|
+
n_iterations=config.batch_correction_iterations,
|
|
164
|
+
),
|
|
165
|
+
rngs=rngs,
|
|
166
|
+
)
|
|
167
|
+
if config.enable_batch_correction
|
|
168
|
+
else None
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# 4. Dimensionality reduction (optional)
|
|
172
|
+
self.dim_reduction = (
|
|
173
|
+
DifferentiableUMAP(
|
|
174
|
+
UMAPConfig(
|
|
175
|
+
input_features=config.latent_dim,
|
|
176
|
+
n_components=config.umap_n_components,
|
|
177
|
+
),
|
|
178
|
+
rngs=rngs,
|
|
179
|
+
)
|
|
180
|
+
if config.enable_dim_reduction
|
|
181
|
+
else None
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# 5. Clustering (optional but typically used)
|
|
185
|
+
self.clustering = (
|
|
186
|
+
SoftKMeansClustering(
|
|
187
|
+
SoftClusteringConfig(
|
|
188
|
+
n_clusters=config.n_clusters,
|
|
189
|
+
n_features=config.latent_dim,
|
|
190
|
+
temperature=config.clustering_temperature,
|
|
191
|
+
),
|
|
192
|
+
rngs=rngs,
|
|
193
|
+
)
|
|
194
|
+
if config.enable_clustering
|
|
195
|
+
else None
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def apply(
|
|
199
|
+
self,
|
|
200
|
+
data: dict[str, Array],
|
|
201
|
+
state: dict[str, Any],
|
|
202
|
+
metadata: dict[str, Any] | None,
|
|
203
|
+
random_params: Any = None, # noqa: ARG002
|
|
204
|
+
stats: dict[str, Any] | None = None, # noqa: ARG002
|
|
205
|
+
) -> tuple[dict[str, Array], dict[str, Any], dict[str, Any] | None]:
|
|
206
|
+
"""Apply the full single-cell analysis pipeline.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
data: Input data containing:
|
|
210
|
+
- counts: Float[Array, "n_cells n_genes"]
|
|
211
|
+
- ambient_profile: Float[Array, "n_genes"]
|
|
212
|
+
- batch_labels: Int[Array, "n_cells"]
|
|
213
|
+
state: Element state (passed through).
|
|
214
|
+
metadata: Element metadata (passed through).
|
|
215
|
+
random_params: Random parameters for stochastic operations.
|
|
216
|
+
stats: Optional statistics dict.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Tuple of (output_data, state, metadata) where output_data contains
|
|
220
|
+
all input keys plus analysis outputs.
|
|
221
|
+
"""
|
|
222
|
+
counts = data["counts"]
|
|
223
|
+
n_cells = counts.shape[0]
|
|
224
|
+
|
|
225
|
+
# Step 1: Ambient RNA removal (optional)
|
|
226
|
+
if self.ambient_removal is not None:
|
|
227
|
+
ambient_data = {
|
|
228
|
+
"counts": counts,
|
|
229
|
+
"ambient_profile": data["ambient_profile"],
|
|
230
|
+
}
|
|
231
|
+
ambient_result, _, _ = self.ambient_removal.apply(ambient_data, {}, None)
|
|
232
|
+
decontaminated = ambient_result["decontaminated_counts"]
|
|
233
|
+
else:
|
|
234
|
+
decontaminated = counts
|
|
235
|
+
|
|
236
|
+
# Step 2: VAE normalization (per-cell using vmap for efficiency)
|
|
237
|
+
# The VAE normalizer expects single-cell input with library_size
|
|
238
|
+
def normalize_cell(cell_counts: Float[Array, "n_genes"]) -> dict[str, Array]:
|
|
239
|
+
# Compute library size (total counts per cell)
|
|
240
|
+
library_size = cell_counts.sum()
|
|
241
|
+
vae_data = {"counts": cell_counts, "library_size": library_size}
|
|
242
|
+
result, _, _ = self.vae_normalizer.apply(vae_data, {}, None)
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
# Use vmap for batch processing
|
|
246
|
+
vmap_normalize = jax.vmap(normalize_cell)
|
|
247
|
+
normalized_results = vmap_normalize(decontaminated)
|
|
248
|
+
|
|
249
|
+
normalized = normalized_results["normalized"]
|
|
250
|
+
latent = normalized_results["latent_z"] # VAENormalizer outputs latent_z
|
|
251
|
+
|
|
252
|
+
# Step 3: Batch correction (optional)
|
|
253
|
+
if self.batch_correction is not None:
|
|
254
|
+
batch_data = {
|
|
255
|
+
"embeddings": latent,
|
|
256
|
+
"batch_labels": data["batch_labels"],
|
|
257
|
+
}
|
|
258
|
+
batch_result, _, _ = self.batch_correction.apply(batch_data, {}, None)
|
|
259
|
+
corrected_embeddings = batch_result["corrected_embeddings"]
|
|
260
|
+
else:
|
|
261
|
+
corrected_embeddings = latent
|
|
262
|
+
|
|
263
|
+
# Step 4: Dimensionality reduction (optional)
|
|
264
|
+
if self.dim_reduction is not None:
|
|
265
|
+
umap_data = {"features": corrected_embeddings}
|
|
266
|
+
umap_result, _, _ = self.dim_reduction.apply(umap_data, {}, None)
|
|
267
|
+
embeddings_2d = umap_result["embedding"] # UMAP outputs singular "embedding"
|
|
268
|
+
else:
|
|
269
|
+
# Use first 2 dimensions of latent if no UMAP
|
|
270
|
+
embeddings_2d = corrected_embeddings[:, : self.config.umap_n_components]
|
|
271
|
+
|
|
272
|
+
# Step 5: Clustering (optional)
|
|
273
|
+
if self.clustering is not None:
|
|
274
|
+
cluster_data = {"embeddings": corrected_embeddings}
|
|
275
|
+
cluster_result, _, _ = self.clustering.apply(cluster_data, {}, None)
|
|
276
|
+
cluster_assignments = cluster_result["cluster_assignments"]
|
|
277
|
+
else:
|
|
278
|
+
# Return uniform assignments if no clustering
|
|
279
|
+
n_clusters = self.config.n_clusters
|
|
280
|
+
cluster_assignments = jnp.ones((n_cells, n_clusters)) / n_clusters
|
|
281
|
+
|
|
282
|
+
# Build output preserving input keys
|
|
283
|
+
output_data = {
|
|
284
|
+
**data,
|
|
285
|
+
"normalized": normalized,
|
|
286
|
+
"latent": latent,
|
|
287
|
+
"corrected_embeddings": corrected_embeddings,
|
|
288
|
+
"embeddings_2d": embeddings_2d,
|
|
289
|
+
"cluster_assignments": cluster_assignments,
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# Add optional outputs
|
|
293
|
+
if self.ambient_removal is not None:
|
|
294
|
+
output_data["decontaminated_counts"] = decontaminated
|
|
295
|
+
|
|
296
|
+
return output_data, state, metadata
|
|
297
|
+
|
|
298
|
+
def to_dag(self) -> Any:
|
|
299
|
+
"""Build a datarax DAG representation of this pipeline.
|
|
300
|
+
|
|
301
|
+
Returns a ``Sequential`` node graph suitable for execution via
|
|
302
|
+
``datarax.dag.DAGExecutor``. Only enabled stages are included.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
A datarax ``Sequential`` node containing the pipeline stages.
|
|
306
|
+
"""
|
|
307
|
+
from datarax.dag import Node, OperatorNode, Sequential # noqa: PLC0415
|
|
308
|
+
|
|
309
|
+
stages: list[Node] = []
|
|
310
|
+
|
|
311
|
+
if self.ambient_removal is not None:
|
|
312
|
+
stages.append(OperatorNode(self.ambient_removal))
|
|
313
|
+
|
|
314
|
+
stages.append(OperatorNode(self.vae_normalizer))
|
|
315
|
+
|
|
316
|
+
if self.batch_correction is not None:
|
|
317
|
+
stages.append(OperatorNode(self.batch_correction))
|
|
318
|
+
|
|
319
|
+
if self.dim_reduction is not None:
|
|
320
|
+
stages.append(OperatorNode(self.dim_reduction))
|
|
321
|
+
|
|
322
|
+
if self.clustering is not None:
|
|
323
|
+
stages.append(OperatorNode(self.clustering))
|
|
324
|
+
|
|
325
|
+
return Sequential(stages)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def create_single_cell_pipeline(
|
|
329
|
+
n_genes: int = 2000,
|
|
330
|
+
n_clusters: int = 10,
|
|
331
|
+
latent_dim: int = 64,
|
|
332
|
+
umap_n_components: int = 2,
|
|
333
|
+
enable_ambient_removal: bool = True,
|
|
334
|
+
enable_batch_correction: bool = True,
|
|
335
|
+
enable_dim_reduction: bool = True,
|
|
336
|
+
enable_clustering: bool = True,
|
|
337
|
+
seed: int = 42,
|
|
338
|
+
) -> SingleCellPipeline:
|
|
339
|
+
"""Factory function to create a single-cell analysis pipeline.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
n_genes: Number of genes in the expression matrix.
|
|
343
|
+
n_clusters: Number of clusters for soft k-means.
|
|
344
|
+
latent_dim: Dimension of the VAE latent space.
|
|
345
|
+
umap_n_components: Number of UMAP output dimensions.
|
|
346
|
+
enable_ambient_removal: Whether to enable ambient RNA removal.
|
|
347
|
+
enable_batch_correction: Whether to enable batch correction.
|
|
348
|
+
enable_dim_reduction: Whether to enable UMAP.
|
|
349
|
+
enable_clustering: Whether to enable soft clustering.
|
|
350
|
+
seed: Random seed.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Configured SingleCellPipeline instance.
|
|
354
|
+
"""
|
|
355
|
+
config = SingleCellPipelineConfig(
|
|
356
|
+
n_genes=n_genes,
|
|
357
|
+
n_clusters=n_clusters,
|
|
358
|
+
latent_dim=latent_dim,
|
|
359
|
+
umap_n_components=umap_n_components,
|
|
360
|
+
enable_ambient_removal=enable_ambient_removal,
|
|
361
|
+
enable_batch_correction=enable_batch_correction,
|
|
362
|
+
enable_dim_reduction=enable_dim_reduction,
|
|
363
|
+
enable_clustering=enable_clustering,
|
|
364
|
+
)
|
|
365
|
+
rngs = nnx.Rngs(seed)
|
|
366
|
+
return SingleCellPipeline(config, rngs=rngs)
|