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.
Files changed (202) hide show
  1. diffbio/__init__.py +39 -0
  2. diffbio/configs.py +75 -0
  3. diffbio/constants.py +204 -0
  4. diffbio/core/__init__.py +127 -0
  5. diffbio/core/base_operators.py +612 -0
  6. diffbio/core/data_types.py +260 -0
  7. diffbio/core/gnn_components.py +629 -0
  8. diffbio/core/graph_utils.py +149 -0
  9. diffbio/core/neural_components.py +270 -0
  10. diffbio/core/optimal_transport.py +133 -0
  11. diffbio/core/soft_ops/__init__.py +216 -0
  12. diffbio/core/soft_ops/_projections_permutahedron.py +1864 -0
  13. diffbio/core/soft_ops/_projections_simplex.py +240 -0
  14. diffbio/core/soft_ops/_projections_transport.py +508 -0
  15. diffbio/core/soft_ops/_sorting_network.py +204 -0
  16. diffbio/core/soft_ops/_types.py +15 -0
  17. diffbio/core/soft_ops/_utils.py +342 -0
  18. diffbio/core/soft_ops/autograd_safe.py +120 -0
  19. diffbio/core/soft_ops/comparison.py +235 -0
  20. diffbio/core/soft_ops/elementwise.py +309 -0
  21. diffbio/core/soft_ops/logical.py +146 -0
  22. diffbio/core/soft_ops/quantile.py +376 -0
  23. diffbio/core/soft_ops/selection.py +236 -0
  24. diffbio/core/soft_ops/sorting.py +926 -0
  25. diffbio/core/soft_ops/straight_through.py +261 -0
  26. diffbio/core/uncertainty.py +279 -0
  27. diffbio/evaluation/__init__.py +42 -0
  28. diffbio/evaluation/adapters.py +409 -0
  29. diffbio/evaluation/graders.py +223 -0
  30. diffbio/evaluation/problem.py +157 -0
  31. diffbio/evaluation/runner.py +277 -0
  32. diffbio/losses/__init__.py +59 -0
  33. diffbio/losses/alignment_losses.py +222 -0
  34. diffbio/losses/biological_regularization.py +288 -0
  35. diffbio/losses/metric_losses.py +139 -0
  36. diffbio/losses/singlecell_losses.py +387 -0
  37. diffbio/losses/statistical_losses.py +345 -0
  38. diffbio/operators/__init__.py +60 -0
  39. diffbio/operators/_count_vae.py +197 -0
  40. diffbio/operators/_loss_balancing.py +65 -0
  41. diffbio/operators/_masked_gene_transformer.py +118 -0
  42. diffbio/operators/_transformer_validation.py +50 -0
  43. diffbio/operators/alignment/__init__.py +51 -0
  44. diffbio/operators/alignment/profile_hmm.py +350 -0
  45. diffbio/operators/alignment/scoring.py +127 -0
  46. diffbio/operators/alignment/smith_waterman.py +261 -0
  47. diffbio/operators/alignment/soft_msa.py +419 -0
  48. diffbio/operators/assembly/__init__.py +27 -0
  49. diffbio/operators/assembly/gnn_assembly.py +252 -0
  50. diffbio/operators/assembly/metagenomic_binning.py +296 -0
  51. diffbio/operators/crispr/__init__.py +17 -0
  52. diffbio/operators/crispr/guide_scoring.py +269 -0
  53. diffbio/operators/drug_discovery/__init__.py +133 -0
  54. diffbio/operators/drug_discovery/_graph_utils.py +142 -0
  55. diffbio/operators/drug_discovery/admet_predictor.py +285 -0
  56. diffbio/operators/drug_discovery/attentive_fp.py +411 -0
  57. diffbio/operators/drug_discovery/dti.py +261 -0
  58. diffbio/operators/drug_discovery/fingerprint.py +490 -0
  59. diffbio/operators/drug_discovery/maccs_keys.py +267 -0
  60. diffbio/operators/drug_discovery/message_passing.py +200 -0
  61. diffbio/operators/drug_discovery/primitives.py +242 -0
  62. diffbio/operators/drug_discovery/property_predictor.py +163 -0
  63. diffbio/operators/drug_discovery/similarity.py +193 -0
  64. diffbio/operators/epigenomics/__init__.py +35 -0
  65. diffbio/operators/epigenomics/chromatin_state.py +491 -0
  66. diffbio/operators/epigenomics/contextual.py +288 -0
  67. diffbio/operators/epigenomics/fno_peak_calling.py +153 -0
  68. diffbio/operators/epigenomics/peak_calling.py +555 -0
  69. diffbio/operators/foundation_models/__init__.py +119 -0
  70. diffbio/operators/foundation_models/adapters.py +114 -0
  71. diffbio/operators/foundation_models/contracts.py +245 -0
  72. diffbio/operators/foundation_models/embedding_probe.py +83 -0
  73. diffbio/operators/foundation_models/experimental.py +128 -0
  74. diffbio/operators/foundation_models/foundation_model.py +332 -0
  75. diffbio/operators/foundation_models/frozen.py +59 -0
  76. diffbio/operators/foundation_models/precomputed.py +270 -0
  77. diffbio/operators/foundation_models/transformer_encoder.py +564 -0
  78. diffbio/operators/mapping/__init__.py +17 -0
  79. diffbio/operators/mapping/neural_mapper.py +493 -0
  80. diffbio/operators/metabolomics/__init__.py +39 -0
  81. diffbio/operators/metabolomics/spectral_similarity.py +315 -0
  82. diffbio/operators/molecular_dynamics/__init__.py +51 -0
  83. diffbio/operators/molecular_dynamics/force_field.py +265 -0
  84. diffbio/operators/molecular_dynamics/integrator.py +304 -0
  85. diffbio/operators/molecular_dynamics/primitives.py +115 -0
  86. diffbio/operators/multiomics/__init__.py +38 -0
  87. diffbio/operators/multiomics/hic_contact.py +377 -0
  88. diffbio/operators/multiomics/multiomics_vae.py +325 -0
  89. diffbio/operators/multiomics/spatial_deconvolution.py +316 -0
  90. diffbio/operators/multiomics/spatial_gene_detection.py +493 -0
  91. diffbio/operators/normalization/__init__.py +42 -0
  92. diffbio/operators/normalization/embedding.py +222 -0
  93. diffbio/operators/normalization/phate.py +400 -0
  94. diffbio/operators/normalization/umap.py +261 -0
  95. diffbio/operators/normalization/vae_normalizer.py +258 -0
  96. diffbio/operators/population/__init__.py +17 -0
  97. diffbio/operators/population/ancestry_estimation.py +274 -0
  98. diffbio/operators/preprocessing/__init__.py +76 -0
  99. diffbio/operators/preprocessing/adapter_removal.py +311 -0
  100. diffbio/operators/preprocessing/duplicate_filter.py +317 -0
  101. diffbio/operators/preprocessing/error_correction.py +287 -0
  102. diffbio/operators/protein/__init__.py +31 -0
  103. diffbio/operators/protein/secondary_structure.py +509 -0
  104. diffbio/operators/quality_filter.py +128 -0
  105. diffbio/operators/rna_structure/__init__.py +35 -0
  106. diffbio/operators/rna_structure/rna_folding.py +509 -0
  107. diffbio/operators/rnaseq/__init__.py +23 -0
  108. diffbio/operators/rnaseq/motif_discovery.py +251 -0
  109. diffbio/operators/rnaseq/splicing_psi.py +216 -0
  110. diffbio/operators/singlecell/__init__.py +193 -0
  111. diffbio/operators/singlecell/ambient_removal.py +333 -0
  112. diffbio/operators/singlecell/archetypes.py +191 -0
  113. diffbio/operators/singlecell/batch_correction.py +288 -0
  114. diffbio/operators/singlecell/cell_annotation.py +519 -0
  115. diffbio/operators/singlecell/communication.py +704 -0
  116. diffbio/operators/singlecell/differential_distribution.py +243 -0
  117. diffbio/operators/singlecell/doublet_detection.py +657 -0
  118. diffbio/operators/singlecell/downsampling.py +166 -0
  119. diffbio/operators/singlecell/enhanced_batch_correction.py +519 -0
  120. diffbio/operators/singlecell/grn_inference.py +336 -0
  121. diffbio/operators/singlecell/imputation.py +429 -0
  122. diffbio/operators/singlecell/knockdown_filter.py +176 -0
  123. diffbio/operators/singlecell/ot_trajectory.py +277 -0
  124. diffbio/operators/singlecell/simulation.py +444 -0
  125. diffbio/operators/singlecell/sindy_grn.py +247 -0
  126. diffbio/operators/singlecell/soft_clustering.py +211 -0
  127. diffbio/operators/singlecell/spatial_domains.py +677 -0
  128. diffbio/operators/singlecell/switch_de.py +184 -0
  129. diffbio/operators/singlecell/trajectory.py +447 -0
  130. diffbio/operators/singlecell/velocity.py +361 -0
  131. diffbio/operators/statistical/__init__.py +35 -0
  132. diffbio/operators/statistical/em_quantification.py +260 -0
  133. diffbio/operators/statistical/hmm.py +234 -0
  134. diffbio/operators/statistical/nb_glm.py +272 -0
  135. diffbio/operators/variant/__init__.py +64 -0
  136. diffbio/operators/variant/classifier.py +333 -0
  137. diffbio/operators/variant/cnn_classifier.py +255 -0
  138. diffbio/operators/variant/cnv_segmentation.py +678 -0
  139. diffbio/operators/variant/deepvariant_pileup.py +426 -0
  140. diffbio/operators/variant/pileup.py +240 -0
  141. diffbio/operators/variant/quality_recalibration.py +274 -0
  142. diffbio/pipelines/__init__.py +65 -0
  143. diffbio/pipelines/differential_expression.py +279 -0
  144. diffbio/pipelines/enhanced_variant_calling.py +326 -0
  145. diffbio/pipelines/perturbation.py +407 -0
  146. diffbio/pipelines/preprocessing.py +267 -0
  147. diffbio/pipelines/single_cell.py +366 -0
  148. diffbio/pipelines/variant_calling.py +490 -0
  149. diffbio/samplers/__init__.py +9 -0
  150. diffbio/samplers/perturbation_sampler.py +142 -0
  151. diffbio/sequences/__init__.py +34 -0
  152. diffbio/sequences/dna.py +239 -0
  153. diffbio/sources/__init__.py +149 -0
  154. diffbio/sources/_anndata_shared.py +89 -0
  155. diffbio/sources/_batch_iteration.py +37 -0
  156. diffbio/sources/_benchmark_source.py +152 -0
  157. diffbio/sources/_indexed_batch_source.py +38 -0
  158. diffbio/sources/_utils.py +45 -0
  159. diffbio/sources/anndata_interop.py +387 -0
  160. diffbio/sources/anndata_source.py +361 -0
  161. diffbio/sources/archive_ii.py +174 -0
  162. diffbio/sources/balifam.py +207 -0
  163. diffbio/sources/bam.py +265 -0
  164. diffbio/sources/bengrn_ground_truth.py +306 -0
  165. diffbio/sources/contextual_epigenomics.py +242 -0
  166. diffbio/sources/dti.py +359 -0
  167. diffbio/sources/embeddings.py +203 -0
  168. diffbio/sources/encode_peaks.py +223 -0
  169. diffbio/sources/fasta.py +226 -0
  170. diffbio/sources/immune_human.py +172 -0
  171. diffbio/sources/indexed_embeddings.py +128 -0
  172. diffbio/sources/indexed_view.py +191 -0
  173. diffbio/sources/molnet.py +493 -0
  174. diffbio/sources/multiomics.py +279 -0
  175. diffbio/sources/pancreas.py +108 -0
  176. diffbio/sources/perturbation/__init__.py +69 -0
  177. diffbio/sources/perturbation/_types.py +51 -0
  178. diffbio/sources/perturbation/_utils.py +125 -0
  179. diffbio/sources/perturbation/concat_source.py +115 -0
  180. diffbio/sources/perturbation/control_mapping.py +215 -0
  181. diffbio/sources/perturbation/experiment_config.py +261 -0
  182. diffbio/sources/perturbation/h5_metadata_cache.py +218 -0
  183. diffbio/sources/perturbation/output_space.py +52 -0
  184. diffbio/sources/perturbation/perturbation_source.py +513 -0
  185. diffbio/sources/seqfish.py +145 -0
  186. diffbio/sources/sequence_foundation.py +68 -0
  187. diffbio/sources/singlecell_foundation.py +68 -0
  188. diffbio/splitters/__init__.py +63 -0
  189. diffbio/splitters/base.py +251 -0
  190. diffbio/splitters/molecular.py +330 -0
  191. diffbio/splitters/perturbation.py +199 -0
  192. diffbio/splitters/random.py +217 -0
  193. diffbio/splitters/sequence.py +201 -0
  194. diffbio/utils/__init__.py +55 -0
  195. diffbio/utils/dependency_runtime.py +115 -0
  196. diffbio/utils/nn_utils.py +157 -0
  197. diffbio/utils/quality.py +45 -0
  198. diffbio/utils/training.py +585 -0
  199. diffbio-0.1.0.dist-info/METADATA +480 -0
  200. diffbio-0.1.0.dist-info/RECORD +202 -0
  201. diffbio-0.1.0.dist-info/WHEEL +4 -0
  202. 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)