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,279 @@
|
|
|
1
|
+
"""Shared multi-omics provenance and embedding-artifact sources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Mapping
|
|
9
|
+
|
|
10
|
+
import jax.numpy as jnp
|
|
11
|
+
from flax import nnx
|
|
12
|
+
|
|
13
|
+
from diffbio.sources.indexed_embeddings import IndexedEmbeddingSource, IndexedEmbeddingSourceConfig
|
|
14
|
+
|
|
15
|
+
MULTIOMICS_DATASET_PROVENANCE_KEYS = (
|
|
16
|
+
"dataset_name",
|
|
17
|
+
"source_type",
|
|
18
|
+
"modalities",
|
|
19
|
+
"curation_status",
|
|
20
|
+
"biological_validation",
|
|
21
|
+
"promotion_eligible",
|
|
22
|
+
"source_path",
|
|
23
|
+
)
|
|
24
|
+
MULTIOMICS_ARTIFACT_METADATA_KEYS = (
|
|
25
|
+
"artifact_id",
|
|
26
|
+
"artifact_type",
|
|
27
|
+
"modalities",
|
|
28
|
+
"embedding_source",
|
|
29
|
+
"foundation_source_name",
|
|
30
|
+
"promotion_eligible",
|
|
31
|
+
)
|
|
32
|
+
_SUPPORTED_MULTIOMICS_MODALITIES = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"rna",
|
|
35
|
+
"atac",
|
|
36
|
+
"protein",
|
|
37
|
+
"spatial",
|
|
38
|
+
"metabolomics",
|
|
39
|
+
"mass_spectrometry",
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class MultiOmicsEmbeddingSourceConfig(IndexedEmbeddingSourceConfig):
|
|
46
|
+
"""Configuration for sample-indexed multi-omics embedding artifacts."""
|
|
47
|
+
|
|
48
|
+
row_id_key: str = "sample_ids"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MultiOmicsEmbeddingSource(IndexedEmbeddingSource):
|
|
52
|
+
"""Indexed embedding source specialized for multi-omics sample artifacts."""
|
|
53
|
+
|
|
54
|
+
config: MultiOmicsEmbeddingSourceConfig # pyright: ignore[reportIncompatibleVariableOverride]
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_path(
|
|
58
|
+
cls,
|
|
59
|
+
path: Path | str,
|
|
60
|
+
*,
|
|
61
|
+
rngs: nnx.Rngs | None = None,
|
|
62
|
+
) -> MultiOmicsEmbeddingSource:
|
|
63
|
+
"""Build the canonical sample-indexed source for a multi-omics artifact."""
|
|
64
|
+
return cls(MultiOmicsEmbeddingSourceConfig(file_path=str(path)), rngs=rngs)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def sample_ids(self) -> tuple[str, ...] | None:
|
|
68
|
+
"""Tuple of persisted sample identifiers, if present."""
|
|
69
|
+
return self.row_ids
|
|
70
|
+
|
|
71
|
+
def align_to_reference_sample_ids(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
reference_sample_ids: Sequence[str],
|
|
75
|
+
require_sample_ids: bool = True,
|
|
76
|
+
) -> jnp.ndarray:
|
|
77
|
+
"""Align imported embeddings to a reference multi-omics sample order."""
|
|
78
|
+
return self.align_to_reference_ids(
|
|
79
|
+
reference_ids=tuple(reference_sample_ids),
|
|
80
|
+
require_row_ids=require_sample_ids,
|
|
81
|
+
artifact_label="Multi-omics",
|
|
82
|
+
id_display_name="Sample ID",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class MetabolomicsEmbeddingSourceConfig(IndexedEmbeddingSourceConfig):
|
|
88
|
+
"""Configuration for spectrum-indexed metabolomics embedding artifacts."""
|
|
89
|
+
|
|
90
|
+
row_id_key: str = "spectrum_ids"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MetabolomicsEmbeddingSource(IndexedEmbeddingSource):
|
|
94
|
+
"""Indexed embedding source specialized for metabolomics spectrum artifacts."""
|
|
95
|
+
|
|
96
|
+
config: MetabolomicsEmbeddingSourceConfig # pyright: ignore[reportIncompatibleVariableOverride]
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_path(
|
|
100
|
+
cls,
|
|
101
|
+
path: Path | str,
|
|
102
|
+
*,
|
|
103
|
+
rngs: nnx.Rngs | None = None,
|
|
104
|
+
) -> MetabolomicsEmbeddingSource:
|
|
105
|
+
"""Build the canonical spectrum-indexed source for a metabolomics artifact."""
|
|
106
|
+
return cls(MetabolomicsEmbeddingSourceConfig(file_path=str(path)), rngs=rngs)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def spectrum_ids(self) -> tuple[str, ...] | None:
|
|
110
|
+
"""Tuple of persisted spectrum identifiers, if present."""
|
|
111
|
+
return self.row_ids
|
|
112
|
+
|
|
113
|
+
def align_to_reference_spectrum_ids(
|
|
114
|
+
self,
|
|
115
|
+
*,
|
|
116
|
+
reference_spectrum_ids: Sequence[str],
|
|
117
|
+
require_spectrum_ids: bool = True,
|
|
118
|
+
) -> jnp.ndarray:
|
|
119
|
+
"""Align imported embeddings to a reference spectrum order."""
|
|
120
|
+
return self.align_to_reference_ids(
|
|
121
|
+
reference_ids=tuple(reference_spectrum_ids),
|
|
122
|
+
require_row_ids=require_spectrum_ids,
|
|
123
|
+
artifact_label="Metabolomics",
|
|
124
|
+
id_display_name="Spectrum ID",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def build_multiomics_dataset_provenance(
|
|
129
|
+
*,
|
|
130
|
+
dataset_name: str,
|
|
131
|
+
source_type: str,
|
|
132
|
+
modalities: Sequence[str],
|
|
133
|
+
curation_status: str,
|
|
134
|
+
biological_validation: str,
|
|
135
|
+
promotion_eligible: bool,
|
|
136
|
+
source_path: str | None = None,
|
|
137
|
+
) -> dict[str, Any]:
|
|
138
|
+
"""Build canonical provenance for benchmarked multi-omics datasets."""
|
|
139
|
+
provenance: dict[str, Any] = {
|
|
140
|
+
"dataset_name": dataset_name,
|
|
141
|
+
"source_type": source_type,
|
|
142
|
+
"modalities": list(modalities),
|
|
143
|
+
"curation_status": curation_status,
|
|
144
|
+
"biological_validation": biological_validation,
|
|
145
|
+
"promotion_eligible": promotion_eligible,
|
|
146
|
+
"source_path": source_path,
|
|
147
|
+
}
|
|
148
|
+
return validate_multiomics_dataset_provenance(provenance)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def validate_multiomics_dataset_provenance(
|
|
152
|
+
provenance: Mapping[str, Any],
|
|
153
|
+
) -> dict[str, Any]:
|
|
154
|
+
"""Validate and normalize a multi-omics dataset provenance payload."""
|
|
155
|
+
missing = [key for key in MULTIOMICS_DATASET_PROVENANCE_KEYS if key not in provenance]
|
|
156
|
+
if missing:
|
|
157
|
+
raise ValueError(f"multi-omics dataset_provenance is missing required keys: {missing}")
|
|
158
|
+
|
|
159
|
+
normalized = {key: provenance[key] for key in MULTIOMICS_DATASET_PROVENANCE_KEYS}
|
|
160
|
+
for key in (
|
|
161
|
+
"dataset_name",
|
|
162
|
+
"source_type",
|
|
163
|
+
"curation_status",
|
|
164
|
+
"biological_validation",
|
|
165
|
+
):
|
|
166
|
+
_require_non_empty_string(normalized[key], field_name=f"dataset_provenance.{key}")
|
|
167
|
+
|
|
168
|
+
normalized["modalities"] = _normalize_modalities(normalized["modalities"])
|
|
169
|
+
if not isinstance(normalized["promotion_eligible"], bool):
|
|
170
|
+
raise TypeError("multi-omics dataset_provenance.promotion_eligible must be a bool.")
|
|
171
|
+
if str(normalized["source_type"]).startswith("synthetic") and normalized["promotion_eligible"]:
|
|
172
|
+
raise ValueError("Synthetic multi-omics provenance cannot be promotion_eligible.")
|
|
173
|
+
if normalized["source_path"] is not None:
|
|
174
|
+
_require_non_empty_string(
|
|
175
|
+
normalized["source_path"],
|
|
176
|
+
field_name="dataset_provenance.source_path",
|
|
177
|
+
)
|
|
178
|
+
return normalized
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def build_multiomics_artifact_metadata(
|
|
182
|
+
*,
|
|
183
|
+
artifact_id: str,
|
|
184
|
+
artifact_type: str,
|
|
185
|
+
modalities: Sequence[str],
|
|
186
|
+
embedding_source: str,
|
|
187
|
+
foundation_source_name: str,
|
|
188
|
+
promotion_eligible: bool,
|
|
189
|
+
) -> dict[str, Any]:
|
|
190
|
+
"""Build canonical metadata for imported or benchmark-produced omics artifacts."""
|
|
191
|
+
metadata: dict[str, Any] = {
|
|
192
|
+
"artifact_id": artifact_id,
|
|
193
|
+
"artifact_type": artifact_type,
|
|
194
|
+
"modalities": list(modalities),
|
|
195
|
+
"embedding_source": embedding_source,
|
|
196
|
+
"foundation_source_name": foundation_source_name,
|
|
197
|
+
"promotion_eligible": promotion_eligible,
|
|
198
|
+
}
|
|
199
|
+
return validate_multiomics_artifact_metadata(metadata)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def validate_multiomics_artifact_metadata(
|
|
203
|
+
metadata: Mapping[str, Any],
|
|
204
|
+
) -> dict[str, Any]:
|
|
205
|
+
"""Validate and normalize multi-omics artifact metadata."""
|
|
206
|
+
missing = [key for key in MULTIOMICS_ARTIFACT_METADATA_KEYS if key not in metadata]
|
|
207
|
+
if missing:
|
|
208
|
+
raise ValueError(f"multi-omics artifact metadata is missing required keys: {missing}")
|
|
209
|
+
|
|
210
|
+
normalized = {key: metadata[key] for key in MULTIOMICS_ARTIFACT_METADATA_KEYS}
|
|
211
|
+
for key in ("artifact_id", "artifact_type", "embedding_source", "foundation_source_name"):
|
|
212
|
+
_require_non_empty_string(normalized[key], field_name=f"artifact_metadata.{key}")
|
|
213
|
+
normalized["modalities"] = _normalize_modalities(normalized["modalities"])
|
|
214
|
+
if not isinstance(normalized["promotion_eligible"], bool):
|
|
215
|
+
raise TypeError("multi-omics artifact metadata promotion_eligible must be a bool.")
|
|
216
|
+
return normalized
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def load_multiomics_embedding_source(
|
|
220
|
+
path: Path | str,
|
|
221
|
+
*,
|
|
222
|
+
rngs: nnx.Rngs | None = None,
|
|
223
|
+
) -> MultiOmicsEmbeddingSource:
|
|
224
|
+
"""Build the canonical sample-indexed multi-omics embedding source."""
|
|
225
|
+
return MultiOmicsEmbeddingSource.from_path(path, rngs=rngs)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def align_multiomics_embeddings(
|
|
229
|
+
*,
|
|
230
|
+
reference_sample_ids: Sequence[str],
|
|
231
|
+
artifact_path: Path | str,
|
|
232
|
+
require_sample_ids: bool = True,
|
|
233
|
+
) -> jnp.ndarray:
|
|
234
|
+
"""Align imported multi-omics embeddings to a reference sample order."""
|
|
235
|
+
return load_multiomics_embedding_source(artifact_path).align_to_reference_sample_ids(
|
|
236
|
+
reference_sample_ids=reference_sample_ids,
|
|
237
|
+
require_sample_ids=require_sample_ids,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def load_metabolomics_embedding_source(
|
|
242
|
+
path: Path | str,
|
|
243
|
+
*,
|
|
244
|
+
rngs: nnx.Rngs | None = None,
|
|
245
|
+
) -> MetabolomicsEmbeddingSource:
|
|
246
|
+
"""Build the canonical spectrum-indexed metabolomics embedding source."""
|
|
247
|
+
return MetabolomicsEmbeddingSource.from_path(path, rngs=rngs)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def align_metabolomics_embeddings(
|
|
251
|
+
*,
|
|
252
|
+
reference_spectrum_ids: Sequence[str],
|
|
253
|
+
artifact_path: Path | str,
|
|
254
|
+
require_spectrum_ids: bool = True,
|
|
255
|
+
) -> jnp.ndarray:
|
|
256
|
+
"""Align imported metabolomics embeddings to a reference spectrum order."""
|
|
257
|
+
return load_metabolomics_embedding_source(artifact_path).align_to_reference_spectrum_ids(
|
|
258
|
+
reference_spectrum_ids=reference_spectrum_ids,
|
|
259
|
+
require_spectrum_ids=require_spectrum_ids,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _normalize_modalities(raw_modalities: Any) -> list[str]:
|
|
264
|
+
"""Normalize and validate modality identifiers."""
|
|
265
|
+
if not isinstance(raw_modalities, Sequence) or isinstance(raw_modalities, str):
|
|
266
|
+
raise TypeError("modalities must be a non-empty sequence of strings.")
|
|
267
|
+
modalities = [str(modality) for modality in raw_modalities]
|
|
268
|
+
if not modalities:
|
|
269
|
+
raise ValueError("modalities must contain at least one modality.")
|
|
270
|
+
invalid = sorted(set(modalities) - _SUPPORTED_MULTIOMICS_MODALITIES)
|
|
271
|
+
if invalid:
|
|
272
|
+
raise ValueError(f"Unsupported multi-omics modalities: {invalid}")
|
|
273
|
+
return modalities
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _require_non_empty_string(value: Any, *, field_name: str) -> None:
|
|
277
|
+
"""Require one non-empty string field."""
|
|
278
|
+
if not isinstance(value, str) or not value:
|
|
279
|
+
raise TypeError(f"{field_name} must be a non-empty string.")
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Pancreas endocrinogenesis DataSource for trajectory benchmarks.
|
|
2
|
+
|
|
3
|
+
Loads the scVelo pancreas dataset (Bastidas-Ponce et al. 2019,
|
|
4
|
+
Bergen et al. 2020) with spliced/unspliced layers for RNA velocity
|
|
5
|
+
and precomputed PCA embeddings for pseudotime inference.
|
|
6
|
+
|
|
7
|
+
Dataset: 3,696 cells, 27,998 genes, 8 cell types, 5 coarse types.
|
|
8
|
+
Source: scVelo tutorial dataset.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import jax.numpy as jnp
|
|
19
|
+
import numpy as np
|
|
20
|
+
from flax import nnx
|
|
21
|
+
|
|
22
|
+
from datarax.core.config import StructuralConfig
|
|
23
|
+
|
|
24
|
+
from diffbio.sources._benchmark_source import (
|
|
25
|
+
BenchmarkDataSource,
|
|
26
|
+
encode_label_column,
|
|
27
|
+
)
|
|
28
|
+
from diffbio.sources._utils import to_dense_float32 as _to_dense
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
_FILENAME = "endocrinogenesis_day15.h5ad"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True, kw_only=True)
|
|
36
|
+
class PancreasConfig(StructuralConfig):
|
|
37
|
+
"""Configuration for PancreasSource.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
data_dir: Directory containing the downloaded h5ad file.
|
|
41
|
+
subsample: If set, randomly subsample this many cells.
|
|
42
|
+
cluster_key: Column in obs for cell type labels.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
data_dir: str = "/media/mahdi/ssd23/Data/scvelo"
|
|
46
|
+
subsample: int | None = None
|
|
47
|
+
cluster_key: str = "clusters"
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
"""Validate configuration."""
|
|
51
|
+
super().__post_init__()
|
|
52
|
+
path = Path(self.data_dir) / _FILENAME
|
|
53
|
+
if not path.exists():
|
|
54
|
+
raise FileNotFoundError(
|
|
55
|
+
f"Dataset not found: {path}. Download the scVelo "
|
|
56
|
+
f"pancreas dataset to {self.data_dir}/"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PancreasSource(BenchmarkDataSource):
|
|
61
|
+
"""DataSource for the scVelo pancreas endocrinogenesis dataset.
|
|
62
|
+
|
|
63
|
+
Provides counts, spliced/unspliced layers, PCA embeddings,
|
|
64
|
+
and cell type labels for trajectory and velocity benchmarks.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
config: PancreasConfig,
|
|
70
|
+
*,
|
|
71
|
+
rngs: nnx.Rngs | None = None,
|
|
72
|
+
name: str | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Load the pancreas dataset."""
|
|
75
|
+
super().__init__(config, rngs=rngs, name=name or "PancreasSource")
|
|
76
|
+
self.data = self._load(config)
|
|
77
|
+
self._log_loaded_summary(logger, "pancreas", ("n_cells", "n_genes", "n_types"))
|
|
78
|
+
|
|
79
|
+
def _load(self, config: PancreasConfig) -> dict[str, Any]:
|
|
80
|
+
"""Load and preprocess the h5ad file."""
|
|
81
|
+
adata, counts = self._load_benchmark_counts(config, _FILENAME, _to_dense)
|
|
82
|
+
|
|
83
|
+
# Spliced/unspliced for velocity
|
|
84
|
+
spliced = jnp.array(_to_dense(adata.layers["spliced"]))
|
|
85
|
+
unspliced = jnp.array(_to_dense(adata.layers["unspliced"]))
|
|
86
|
+
|
|
87
|
+
# Cell type labels
|
|
88
|
+
labels = encode_label_column(adata.obs[config.cluster_key])
|
|
89
|
+
|
|
90
|
+
# Embeddings
|
|
91
|
+
embeddings = jnp.array(
|
|
92
|
+
np.asarray(
|
|
93
|
+
adata.obsm.get("X_pca", np.zeros((adata.n_obs, 50))),
|
|
94
|
+
dtype=np.float32,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
"counts": counts,
|
|
100
|
+
"spliced": spliced,
|
|
101
|
+
"unspliced": unspliced,
|
|
102
|
+
"cell_type_labels": labels,
|
|
103
|
+
"embeddings": embeddings,
|
|
104
|
+
"gene_names": list(adata.var_names),
|
|
105
|
+
"n_cells": adata.n_obs,
|
|
106
|
+
"n_genes": adata.n_vars,
|
|
107
|
+
"n_types": int(len(np.unique(labels))),
|
|
108
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Perturbation-aware data loading for single-cell experiments.
|
|
2
|
+
|
|
3
|
+
This sub-package provides data sources, configuration, and utilities for
|
|
4
|
+
loading single-cell perturbation experiment data (e.g., CRISPR screens),
|
|
5
|
+
porting and adapting features from the cell-load library to the
|
|
6
|
+
JAX/datarax ecosystem.
|
|
7
|
+
|
|
8
|
+
Sources:
|
|
9
|
+
PerturbationAnnDataSource: Single-file perturbation-aware AnnData source
|
|
10
|
+
PerturbationConcatSource: Multi-dataset concatenation source
|
|
11
|
+
|
|
12
|
+
Configuration:
|
|
13
|
+
ExperimentConfig: TOML-based experiment configuration
|
|
14
|
+
PerturbationSourceConfig: Source configuration
|
|
15
|
+
|
|
16
|
+
Control Mapping:
|
|
17
|
+
RandomControlMapping: Random control cell mapping within cell type
|
|
18
|
+
BatchControlMapping: Batch-aware control cell mapping
|
|
19
|
+
|
|
20
|
+
Utilities:
|
|
21
|
+
H5MetadataCache: Singleton cache for H5 metadata
|
|
22
|
+
GlobalH5MetadataCache: Process-global cache manager
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from diffbio.sources.perturbation._types import MappingStrategy, OutputSpaceMode
|
|
26
|
+
from diffbio.sources.perturbation.concat_source import PerturbationConcatSource
|
|
27
|
+
from diffbio.sources.perturbation.control_mapping import (
|
|
28
|
+
BatchControlMapping,
|
|
29
|
+
ControlMappingConfig,
|
|
30
|
+
RandomControlMapping,
|
|
31
|
+
)
|
|
32
|
+
from diffbio.sources.perturbation.experiment_config import (
|
|
33
|
+
DatasetEntry,
|
|
34
|
+
ExperimentConfig,
|
|
35
|
+
FewshotEntry,
|
|
36
|
+
ZeroshotEntry,
|
|
37
|
+
load_experiment_config,
|
|
38
|
+
)
|
|
39
|
+
from diffbio.sources.perturbation.h5_metadata_cache import (
|
|
40
|
+
GlobalH5MetadataCache,
|
|
41
|
+
H5MetadataCache,
|
|
42
|
+
)
|
|
43
|
+
from diffbio.sources.perturbation.perturbation_source import (
|
|
44
|
+
PerturbationAnnDataSource,
|
|
45
|
+
PerturbationSourceConfig,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
# Types
|
|
50
|
+
"MappingStrategy",
|
|
51
|
+
"OutputSpaceMode",
|
|
52
|
+
# Sources
|
|
53
|
+
"PerturbationAnnDataSource",
|
|
54
|
+
"PerturbationSourceConfig",
|
|
55
|
+
"PerturbationConcatSource",
|
|
56
|
+
# Configuration
|
|
57
|
+
"ExperimentConfig",
|
|
58
|
+
"DatasetEntry",
|
|
59
|
+
"ZeroshotEntry",
|
|
60
|
+
"FewshotEntry",
|
|
61
|
+
"load_experiment_config",
|
|
62
|
+
# Control Mapping
|
|
63
|
+
"ControlMappingConfig",
|
|
64
|
+
"RandomControlMapping",
|
|
65
|
+
"BatchControlMapping",
|
|
66
|
+
# Cache
|
|
67
|
+
"H5MetadataCache",
|
|
68
|
+
"GlobalH5MetadataCache",
|
|
69
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Shared type aliases and enums for perturbation data loading.
|
|
2
|
+
|
|
3
|
+
Provides canonical string enums and type aliases used across the perturbation
|
|
4
|
+
sub-package: output space modes, mapping strategies, and cell/perturbation
|
|
5
|
+
label types.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from typing import TypeAlias
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Type aliases
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
CellIndex: TypeAlias = int
|
|
18
|
+
PerturbationLabel: TypeAlias = str
|
|
19
|
+
CellTypeLabel: TypeAlias = str
|
|
20
|
+
BatchLabel: TypeAlias = str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Enums
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OutputSpaceMode(StrEnum):
|
|
29
|
+
"""Output representation mode for perturbation data.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
GENE: Highly variable gene (HVG) subset of the count matrix.
|
|
33
|
+
ALL: Full gene expression matrix.
|
|
34
|
+
EMBEDDING: Pre-computed embedding only (no raw counts).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
GENE = "gene"
|
|
38
|
+
ALL = "all"
|
|
39
|
+
EMBEDDING = "embedding"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MappingStrategy(StrEnum):
|
|
43
|
+
"""Strategy for mapping perturbed cells to control cells.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
BATCH: Map within same batch and cell type.
|
|
47
|
+
RANDOM: Map to random control of same cell type.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
BATCH = "batch"
|
|
51
|
+
RANDOM = "random"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Shared utility functions for the perturbation sub-package.
|
|
2
|
+
|
|
3
|
+
Ports and adapts utility functions from cell-load's data_utils module to work
|
|
4
|
+
with JAX arrays and numpy instead of PyTorch tensors.
|
|
5
|
+
|
|
6
|
+
References:
|
|
7
|
+
- cell-load/src/cell_load/utils/data_utils.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import jax.numpy as jnp
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def safe_decode_array(arr: Any) -> np.ndarray:
|
|
20
|
+
"""Decode byte-string arrays to UTF-8 and cast all entries to Python str.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
arr: Array-like of bytes or other objects.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Numpy string array with all elements decoded to str.
|
|
27
|
+
"""
|
|
28
|
+
decoded: list[str] = []
|
|
29
|
+
for x in arr:
|
|
30
|
+
if isinstance(x, (bytes, bytearray)):
|
|
31
|
+
decoded.append(x.decode("utf-8", errors="ignore"))
|
|
32
|
+
else:
|
|
33
|
+
decoded.append(str(x))
|
|
34
|
+
return np.array(decoded, dtype=str)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def generate_onehot_map(keys: Iterable[str]) -> dict[str, jnp.ndarray]:
|
|
38
|
+
"""Build a map from each unique key to a one-hot JAX array.
|
|
39
|
+
|
|
40
|
+
Keys are sorted to ensure deterministic ordering across runs.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
keys: Iterable of hashable string items.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Dict mapping each unique key to a float32 one-hot JAX array
|
|
47
|
+
of length equal to the number of unique keys.
|
|
48
|
+
"""
|
|
49
|
+
unique_keys = sorted(set(keys))
|
|
50
|
+
n = len(unique_keys)
|
|
51
|
+
identity = jnp.eye(n, dtype=jnp.float32)
|
|
52
|
+
return {k: identity[i] for i, k in enumerate(unique_keys)}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def is_discrete_counts(x: jnp.ndarray, n_cells: int = 100) -> bool:
|
|
56
|
+
"""Detect if data appears to be raw integer counts.
|
|
57
|
+
|
|
58
|
+
Checks whether the row sums of the first ``n_cells`` rows are integers
|
|
59
|
+
(fractional part approximately zero).
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
x: Array of shape ``(n_cells, n_genes)``.
|
|
63
|
+
n_cells: Number of cells to sample for detection.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if data appears to be discrete/raw counts.
|
|
67
|
+
"""
|
|
68
|
+
top_n = min(x.shape[0], n_cells)
|
|
69
|
+
row_sums = x[:top_n].sum(axis=1)
|
|
70
|
+
frac_part = row_sums - jnp.floor(row_sums)
|
|
71
|
+
return bool(jnp.all(jnp.abs(frac_part) < 1e-7))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def is_log_transformed(x: jnp.ndarray) -> bool:
|
|
75
|
+
"""Detect if data is log-transformed by checking the global maximum.
|
|
76
|
+
|
|
77
|
+
Log1p-transformed data typically has a maximum below 15.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
x: Array of expression values.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if data appears to be log-transformed.
|
|
84
|
+
"""
|
|
85
|
+
return bool(x.max() < 15.0)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def split_perturbations_by_cell_fraction(
|
|
89
|
+
pert_groups: dict[str, np.ndarray],
|
|
90
|
+
val_fraction: float,
|
|
91
|
+
rng: np.random.Generator,
|
|
92
|
+
) -> tuple[list[str], list[str]]:
|
|
93
|
+
"""Partition perturbations so the val subset approximates a target cell fraction.
|
|
94
|
+
|
|
95
|
+
Uses a greedy algorithm: shuffles perturbations, then greedily assigns each
|
|
96
|
+
to the val subset if doing so brings the val cell count closer to the target.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
pert_groups: Dict mapping perturbation names to arrays of cell indices.
|
|
100
|
+
val_fraction: Target fraction of total cells to assign to validation.
|
|
101
|
+
rng: Numpy random generator for shuffling.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Tuple of (train_perturbation_names, val_perturbation_names).
|
|
105
|
+
"""
|
|
106
|
+
total_cells = sum(len(indices) for indices in pert_groups.values())
|
|
107
|
+
target_val_cells = val_fraction * total_cells
|
|
108
|
+
|
|
109
|
+
pert_size_list = [(p, len(pert_groups[p])) for p in pert_groups]
|
|
110
|
+
rng.shuffle(pert_size_list)
|
|
111
|
+
|
|
112
|
+
val_perts: list[str] = []
|
|
113
|
+
current_val_cells = 0
|
|
114
|
+
|
|
115
|
+
for pert, size in pert_size_list:
|
|
116
|
+
new_val_cells = current_val_cells + size
|
|
117
|
+
diff_if_add = abs(new_val_cells - target_val_cells)
|
|
118
|
+
diff_if_skip = abs(current_val_cells - target_val_cells)
|
|
119
|
+
|
|
120
|
+
if diff_if_add < diff_if_skip:
|
|
121
|
+
val_perts.append(pert)
|
|
122
|
+
current_val_cells = new_val_cells
|
|
123
|
+
|
|
124
|
+
train_perts = [p for p, _ in pert_size_list if p not in set(val_perts)]
|
|
125
|
+
return train_perts, val_perts
|