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,316 @@
1
+ """Spatial transcriptomics deconvolution operator.
2
+
3
+ This module provides differentiable cell type deconvolution for
4
+ spatial transcriptomics data.
5
+
6
+ Key technique: Uses neural network to learn spot embeddings that account
7
+ for spatial context, then performs soft assignment to reference cell type
8
+ profiles using attention mechanisms.
9
+
10
+ Applications: Cell type mapping in spatial transcriptomics, tissue
11
+ composition analysis, spatial cell-cell interaction studies.
12
+
13
+ Inherits from TemperatureOperator to get:
14
+
15
+ - _temperature property for temperature-controlled smoothing
16
+ - soft_max() for logsumexp-based smooth maximum
17
+ - soft_argmax() for soft position selection
18
+ """
19
+
20
+ import logging
21
+ from dataclasses import dataclass
22
+ from typing import Any
23
+
24
+ import jax
25
+ import jax.numpy as jnp
26
+ from artifex.generative_models.core.base import MLP
27
+ from datarax.core.config import OperatorConfig
28
+ from flax import nnx
29
+ from jaxtyping import Array, Float, PyTree
30
+
31
+ from diffbio.core.base_operators import TemperatureOperator
32
+ from diffbio.utils.nn_utils import ARTIFEX_GELU_MLP_KWARGS, ARTIFEX_GELU_NO_OUTPUT_MLP_KWARGS
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class SpatialDeconvolutionConfig(OperatorConfig):
39
+ """Configuration for SpatialDeconvolution.
40
+
41
+ Attributes:
42
+ n_genes: Number of genes in expression profiles.
43
+ n_cell_types: Number of reference cell types.
44
+ hidden_dim: Hidden dimension for neural networks.
45
+ num_layers: Number of encoder layers.
46
+ spatial_hidden: Hidden dimension for spatial encoder.
47
+ dropout_rate: Dropout rate for regularization.
48
+ temperature: Temperature for softmax operations.
49
+ """
50
+
51
+ n_genes: int = 2000
52
+ n_cell_types: int = 10
53
+ hidden_dim: int = 128
54
+ num_layers: int = 2
55
+ spatial_hidden: int = 32
56
+ dropout_rate: float = 0.1
57
+ temperature: float = 1.0
58
+
59
+ def __post_init__(self) -> None:
60
+ """Validate spatial deconvolution configuration."""
61
+ super().__post_init__()
62
+ if self.num_layers < 1:
63
+ raise ValueError("SpatialDeconvolutionConfig.num_layers must be at least 1.")
64
+
65
+
66
+ class SpotEncoder(nnx.Module):
67
+ """Encoder for spatial spot expression profiles."""
68
+
69
+ def __init__(
70
+ self,
71
+ n_genes: int,
72
+ hidden_dim: int,
73
+ num_layers: int,
74
+ *,
75
+ rngs: nnx.Rngs,
76
+ ):
77
+ """Initialize the spot encoder.
78
+
79
+ Args:
80
+ n_genes: Number of genes.
81
+ hidden_dim: Hidden dimension.
82
+ num_layers: Number of layers.
83
+ rngs: Random number generators.
84
+ """
85
+ super().__init__()
86
+ self.backbone = MLP(
87
+ hidden_dims=[hidden_dim] * num_layers,
88
+ in_features=n_genes,
89
+ rngs=rngs,
90
+ **ARTIFEX_GELU_MLP_KWARGS,
91
+ )
92
+
93
+ def __call__(
94
+ self,
95
+ expression: Float[Array, "n_spots n_genes"],
96
+ ) -> Float[Array, "n_spots hidden_dim"]:
97
+ """Encode spot expression.
98
+
99
+ Args:
100
+ expression: Spot expression matrix.
101
+
102
+ Returns:
103
+ Spot embeddings.
104
+ """
105
+ backbone_output = self.backbone(expression)
106
+ if isinstance(backbone_output, tuple):
107
+ raise TypeError("Spatial deconvolution spot backbone must return a single tensor.")
108
+ return backbone_output
109
+
110
+
111
+ class SpatialEncoder(nnx.Module):
112
+ """Encoder for spatial coordinates."""
113
+
114
+ def __init__(
115
+ self,
116
+ hidden_dim: int,
117
+ *,
118
+ rngs: nnx.Rngs,
119
+ ):
120
+ """Initialize the spatial encoder.
121
+
122
+ Args:
123
+ hidden_dim: Hidden dimension.
124
+ rngs: Random number generators.
125
+ """
126
+ super().__init__()
127
+ self.backbone = MLP(
128
+ hidden_dims=[hidden_dim, hidden_dim],
129
+ in_features=2,
130
+ rngs=rngs,
131
+ **ARTIFEX_GELU_NO_OUTPUT_MLP_KWARGS,
132
+ )
133
+
134
+ def __call__(
135
+ self,
136
+ coordinates: Float[Array, "n_spots 2"],
137
+ ) -> Float[Array, "n_spots hidden_dim"]:
138
+ """Encode spatial coordinates.
139
+
140
+ Args:
141
+ coordinates: Spot coordinates (x, y).
142
+
143
+ Returns:
144
+ Spatial embeddings.
145
+ """
146
+ backbone_output = self.backbone(coordinates)
147
+ if isinstance(backbone_output, tuple):
148
+ raise TypeError("Spatial deconvolution spatial backbone must return a single tensor.")
149
+ return backbone_output
150
+
151
+
152
+ class SpatialDeconvolution(TemperatureOperator):
153
+ """Differentiable spatial transcriptomics deconvolution.
154
+
155
+ This operator performs cell type deconvolution of spatial
156
+ transcriptomics spots using reference single-cell profiles.
157
+ It incorporates spatial context through coordinate embeddings.
158
+
159
+ Algorithm:
160
+ 1. Encode spot expression profiles
161
+ 2. Encode spatial coordinates
162
+ 3. Combine expression and spatial features
163
+ 4. Compute attention to reference cell type profiles
164
+ 5. Apply softmax for cell type proportions
165
+ 6. Reconstruct expression from proportions
166
+
167
+ Inherits from TemperatureOperator to get:
168
+
169
+ - _temperature property for temperature-controlled smoothing
170
+ - soft_max() for logsumexp-based smooth maximum
171
+ - soft_argmax() for soft position selection
172
+
173
+ Args:
174
+ config: SpatialDeconvolutionConfig with model parameters.
175
+ rngs: Flax NNX random number generators.
176
+ name: Optional operator name.
177
+
178
+ Example:
179
+ ```python
180
+ config = SpatialDeconvolutionConfig(n_cell_types=10)
181
+ deconv = SpatialDeconvolution(config, rngs=nnx.Rngs(42))
182
+ data = {"spot_expression": spots, "reference_profiles": refs, "coordinates": coords}
183
+ result, state, meta = deconv.apply(data, {}, None)
184
+ ```
185
+ """
186
+
187
+ def __init__(
188
+ self,
189
+ config: SpatialDeconvolutionConfig,
190
+ *,
191
+ rngs: nnx.Rngs | None = None,
192
+ name: str | None = None,
193
+ ):
194
+ """Initialize the spatial deconvolution operator.
195
+
196
+ Args:
197
+ config: Deconvolution configuration.
198
+ rngs: Random number generators for initialization.
199
+ name: Optional operator name.
200
+ """
201
+ super().__init__(config, rngs=rngs, name=name)
202
+
203
+ if rngs is None:
204
+ rngs = nnx.Rngs(0)
205
+
206
+ self.hidden_dim = config.hidden_dim
207
+ # Temperature is now managed by TemperatureOperator via self._temperature
208
+
209
+ # Expression encoder
210
+ self.spot_encoder = SpotEncoder(
211
+ n_genes=config.n_genes,
212
+ hidden_dim=config.hidden_dim,
213
+ num_layers=config.num_layers,
214
+ rngs=rngs,
215
+ )
216
+
217
+ # Spatial encoder
218
+ self.spatial_encoder = SpatialEncoder(
219
+ hidden_dim=config.spatial_hidden,
220
+ rngs=rngs,
221
+ )
222
+
223
+ # Combine expression and spatial
224
+ self.combine_linear = nnx.Linear(
225
+ in_features=config.hidden_dim + config.spatial_hidden,
226
+ out_features=config.hidden_dim,
227
+ rngs=rngs,
228
+ )
229
+
230
+ # Reference profile encoder
231
+ self.ref_encoder = nnx.Linear(
232
+ in_features=config.n_genes,
233
+ out_features=config.hidden_dim,
234
+ rngs=rngs,
235
+ )
236
+
237
+ # Output projection for cell type scores
238
+ self.output_linear = nnx.Linear(
239
+ in_features=config.hidden_dim,
240
+ out_features=config.n_cell_types,
241
+ rngs=rngs,
242
+ )
243
+
244
+ def apply(
245
+ self,
246
+ data: PyTree,
247
+ state: PyTree,
248
+ metadata: dict[str, Any] | None,
249
+ random_params: Any = None,
250
+ stats: dict[str, Any] | None = None,
251
+ ) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
252
+ """Apply spatial deconvolution.
253
+
254
+ Args:
255
+ data: Dictionary containing:
256
+ - "spot_expression": Spot expression (n_spots, n_genes)
257
+ - "reference_profiles": Reference profiles (n_cell_types, n_genes)
258
+ - "coordinates": Spot coordinates (n_spots, 2)
259
+ state: Element state (passed through unchanged)
260
+ metadata: Element metadata (passed through unchanged)
261
+ random_params: Not used
262
+ stats: Not used
263
+
264
+ Returns:
265
+ Tuple of (transformed_data, state, metadata):
266
+ - transformed_data contains:
267
+
268
+ - "spot_expression": Original expression
269
+ - "reference_profiles": Original references
270
+ - "coordinates": Original coordinates
271
+ - "cell_proportions": Deconvolved proportions
272
+ - "reconstructed_expression": Reconstructed expression
273
+ - "spatial_embeddings": Spatial feature embeddings
274
+ - state is passed through unchanged
275
+ - metadata is passed through unchanged
276
+ """
277
+ spot_expression = data["spot_expression"]
278
+ reference_profiles = data["reference_profiles"]
279
+ coordinates = data["coordinates"]
280
+
281
+ # Encode spot expression
282
+ spot_emb = self.spot_encoder(spot_expression) # (n_spots, hidden_dim)
283
+
284
+ # Encode spatial coordinates
285
+ spatial_emb = self.spatial_encoder(coordinates) # (n_spots, spatial_hidden)
286
+
287
+ # Combine expression and spatial features
288
+ combined = jnp.concatenate([spot_emb, spatial_emb], axis=-1)
289
+ combined = nnx.gelu(self.combine_linear(combined)) # (n_spots, hidden_dim)
290
+
291
+ # Encode reference profiles
292
+ ref_emb = self.ref_encoder(reference_profiles) # (n_cell_types, hidden_dim)
293
+
294
+ # Compute attention scores (dot product similarity)
295
+ # (n_spots, hidden_dim) @ (hidden_dim, n_cell_types) -> (n_spots, n_cell_types)
296
+ scores = jnp.einsum("sh,ch->sc", combined, ref_emb)
297
+
298
+ # Cell type proportions via softmax
299
+ # Use inherited _temperature property from TemperatureOperator
300
+ cell_proportions = jax.nn.softmax(scores / self._temperature, axis=-1)
301
+
302
+ # Reconstruct expression: proportions @ reference_profiles
303
+ # (n_spots, n_cell_types) @ (n_cell_types, n_genes) -> (n_spots, n_genes)
304
+ reconstructed = jnp.einsum("sc,cg->sg", cell_proportions, reference_profiles)
305
+
306
+ # Build output
307
+ transformed_data = {
308
+ "spot_expression": spot_expression,
309
+ "reference_profiles": reference_profiles,
310
+ "coordinates": coordinates,
311
+ "cell_proportions": cell_proportions,
312
+ "reconstructed_expression": reconstructed,
313
+ "spatial_embeddings": combined,
314
+ }
315
+
316
+ return transformed_data, state, metadata