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,361 @@
|
|
|
1
|
+
"""AnnData (.h5ad) data source for single-cell genomics.
|
|
2
|
+
|
|
3
|
+
This module provides AnnDataSource for loading single-cell RNA-seq data from
|
|
4
|
+
.h5ad files (AnnData format) and converting them to JAX-compatible data dicts
|
|
5
|
+
suitable for DiffBio operators.
|
|
6
|
+
|
|
7
|
+
Follows the datarax eager-loading pattern (same as HFEagerSource): all data is
|
|
8
|
+
loaded to JAX arrays at init, then iteration/batching uses pure JAX operations
|
|
9
|
+
with O(1) memory shuffling via Grain's index_shuffle.
|
|
10
|
+
|
|
11
|
+
Handles both dense and sparse count matrices, cell/gene metadata, and
|
|
12
|
+
optional embeddings (PCA, UMAP, etc.).
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
- https://anndata.readthedocs.io/
|
|
16
|
+
- Wolf et al. "SCANPY: large-scale single-cell gene expression data analysis"
|
|
17
|
+
Genome Biology, 2018.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Ownership note: DiffBio owns the AnnData biological schema adapter; batching
|
|
21
|
+
# and iteration stay on Datarax eager-source primitives.
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
from collections.abc import Callable, Iterator
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
import jax
|
|
31
|
+
import jax.numpy as jnp
|
|
32
|
+
import numpy as np
|
|
33
|
+
from flax import nnx
|
|
34
|
+
|
|
35
|
+
from datarax.core.config import StructuralConfig
|
|
36
|
+
from datarax.core.data_source import DataSourceModule
|
|
37
|
+
from datarax.sources._eager_source_ops import eager_get_batch, eager_iter, eager_reset
|
|
38
|
+
|
|
39
|
+
from diffbio.sources._anndata_shared import (
|
|
40
|
+
build_anndata_data,
|
|
41
|
+
extract_anndata_annotations,
|
|
42
|
+
initialize_eager_source_state,
|
|
43
|
+
read_h5ad,
|
|
44
|
+
to_dense_array,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class AnnDataSourceConfig(StructuralConfig):
|
|
52
|
+
"""Configuration for AnnDataSource.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
file_path: Path to the .h5ad file (string or Path object).
|
|
56
|
+
backed: Whether to open in backed mode (memory-mapped).
|
|
57
|
+
shuffle: Whether to shuffle during iteration.
|
|
58
|
+
seed: Integer seed for Grain's index_shuffle.
|
|
59
|
+
split: Optional split name for pipeline integration.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
file_path: str | None = None
|
|
63
|
+
backed: bool = False
|
|
64
|
+
shuffle: bool = False
|
|
65
|
+
seed: int = 42
|
|
66
|
+
split: str | None = None
|
|
67
|
+
|
|
68
|
+
def __post_init__(self) -> None:
|
|
69
|
+
"""Validate configuration after initialization."""
|
|
70
|
+
if self.shuffle:
|
|
71
|
+
object.__setattr__(self, "stochastic", True)
|
|
72
|
+
if self.stream_name is None:
|
|
73
|
+
object.__setattr__(self, "stream_name", "shuffle")
|
|
74
|
+
else:
|
|
75
|
+
object.__setattr__(self, "stochastic", False)
|
|
76
|
+
|
|
77
|
+
super().__post_init__()
|
|
78
|
+
|
|
79
|
+
if self.file_path is None:
|
|
80
|
+
raise ValueError("file_path is required for AnnDataSourceConfig")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AnnDataSource(DataSourceModule):
|
|
84
|
+
"""Eager-loading AnnData source for single-cell RNA-seq data.
|
|
85
|
+
|
|
86
|
+
Loads all data from .h5ad files to JAX arrays at initialization, then
|
|
87
|
+
provides pure JAX iteration, batching, and indexed access. Follows the
|
|
88
|
+
same eager-loading pattern as datarax's HFEagerSource.
|
|
89
|
+
|
|
90
|
+
Provides:
|
|
91
|
+
- Full dataset loading via ``load()``
|
|
92
|
+
- Per-cell indexed access via ``__getitem__``
|
|
93
|
+
- Iteration via ``__iter__`` with optional O(1) memory shuffling
|
|
94
|
+
- Batch retrieval via ``get_batch(batch_size)``
|
|
95
|
+
- Automatic sparse-to-dense conversion
|
|
96
|
+
- JAX array output for count matrices and embeddings
|
|
97
|
+
|
|
98
|
+
Output dictionary keys:
|
|
99
|
+
- ``counts``: Dense JAX array of shape (n_cells, n_genes) from ``.X``
|
|
100
|
+
- ``obs``: Dict of cell metadata columns from ``.obs``
|
|
101
|
+
- ``var``: Dict of gene metadata columns from ``.var``
|
|
102
|
+
- ``obsm``: Dict of embedding JAX arrays from ``.obsm`` (empty if absent)
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
```python
|
|
106
|
+
config = AnnDataSourceConfig(file_path="pbmc3k.h5ad")
|
|
107
|
+
source = AnnDataSource(config)
|
|
108
|
+
print(len(source)) # 2700
|
|
109
|
+
print(source.load()["counts"].shape) # (2700, 32738)
|
|
110
|
+
|
|
111
|
+
for cell in source:
|
|
112
|
+
print(cell["counts"].shape) # (32738,)
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
batch = source.get_batch(32)
|
|
116
|
+
print(batch["counts"].shape) # (32, 32738)
|
|
117
|
+
```
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
# Annotate data storage for Flax NNX (prevents parameter tracking)
|
|
121
|
+
data: dict[str, Any] = nnx.data()
|
|
122
|
+
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
config: AnnDataSourceConfig,
|
|
126
|
+
*,
|
|
127
|
+
rngs: nnx.Rngs | None = None,
|
|
128
|
+
name: str | None = None,
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Initialize AnnDataSource from a .h5ad file.
|
|
131
|
+
|
|
132
|
+
Loads all data to JAX arrays at construction time.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
config: AnnDataSourceConfig with file path and options.
|
|
136
|
+
rngs: Optional RNG state for shuffling.
|
|
137
|
+
name: Optional module name.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
FileNotFoundError: If the file does not exist.
|
|
141
|
+
ImportError: If anndata is not installed.
|
|
142
|
+
"""
|
|
143
|
+
if name is None:
|
|
144
|
+
name = f"AnnDataSource({config.file_path})"
|
|
145
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
146
|
+
|
|
147
|
+
adata = read_h5ad(config)
|
|
148
|
+
|
|
149
|
+
# Convert count matrix to JAX array
|
|
150
|
+
counts = jnp.array(to_dense_array(adata.X))
|
|
151
|
+
obs, var, obsm = extract_anndata_annotations(adata)
|
|
152
|
+
|
|
153
|
+
self._initialize_loaded_source(
|
|
154
|
+
config=config,
|
|
155
|
+
adata=adata,
|
|
156
|
+
data=build_anndata_data(counts=counts, obs=obs, var=var, obsm=obsm),
|
|
157
|
+
length=adata.n_obs,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# =================================================================
|
|
161
|
+
# Public API: load / info
|
|
162
|
+
# =================================================================
|
|
163
|
+
|
|
164
|
+
def load(self) -> dict[str, Any]:
|
|
165
|
+
"""Return the full dataset as a dictionary of JAX arrays and metadata.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary with keys ``counts``, ``obs``, ``var``, ``obsm``.
|
|
169
|
+
"""
|
|
170
|
+
return dict(self.data)
|
|
171
|
+
|
|
172
|
+
def get_dataset_info(self) -> dict[str, int]:
|
|
173
|
+
"""Return cached dataset metadata.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Dict with ``n_genes`` and ``n_cells``.
|
|
177
|
+
"""
|
|
178
|
+
return self._dataset_info
|
|
179
|
+
|
|
180
|
+
def _initialize_loaded_source(
|
|
181
|
+
self,
|
|
182
|
+
*,
|
|
183
|
+
config: AnnDataSourceConfig,
|
|
184
|
+
adata: Any,
|
|
185
|
+
data: dict[str, Any],
|
|
186
|
+
length: int,
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Initialize common eager-source state after AnnData loading."""
|
|
189
|
+
initialize_eager_source_state(
|
|
190
|
+
self,
|
|
191
|
+
data=data,
|
|
192
|
+
length=length,
|
|
193
|
+
seed=config.seed,
|
|
194
|
+
shuffle=config.shuffle,
|
|
195
|
+
dataset_name=str(config.file_path),
|
|
196
|
+
split_name=config.split,
|
|
197
|
+
dataset_info={
|
|
198
|
+
"n_genes": adata.n_vars,
|
|
199
|
+
"n_cells": adata.n_obs,
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _iter_with_builder(
|
|
204
|
+
self,
|
|
205
|
+
build_element: Callable[[dict[str, Any], int], dict[str, Any]],
|
|
206
|
+
) -> Iterator[dict[str, Any]]:
|
|
207
|
+
"""Iterate using the standard eager-source bookkeeping state."""
|
|
208
|
+
return eager_iter(
|
|
209
|
+
self.data,
|
|
210
|
+
self.length,
|
|
211
|
+
self.index,
|
|
212
|
+
self.epoch,
|
|
213
|
+
self.shuffle,
|
|
214
|
+
self._seed,
|
|
215
|
+
build_element,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def _get_batch_with_gather(
|
|
219
|
+
self,
|
|
220
|
+
batch_size: int,
|
|
221
|
+
key: jax.Array | None,
|
|
222
|
+
gather_fn: Callable[[dict[str, Any], Any], dict[str, Any]],
|
|
223
|
+
) -> dict[str, Any]:
|
|
224
|
+
"""Collect a batch using the standard eager-source bookkeeping state."""
|
|
225
|
+
return eager_get_batch(
|
|
226
|
+
self.data,
|
|
227
|
+
self.length,
|
|
228
|
+
self.index,
|
|
229
|
+
self.epoch,
|
|
230
|
+
self.shuffle,
|
|
231
|
+
self._seed,
|
|
232
|
+
batch_size,
|
|
233
|
+
key,
|
|
234
|
+
gather_fn,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# =================================================================
|
|
238
|
+
# DataSourceModule protocol: __len__, __iter__, __next__, __getitem__
|
|
239
|
+
# =================================================================
|
|
240
|
+
|
|
241
|
+
def __len__(self) -> int:
|
|
242
|
+
"""Return the number of cells in the dataset."""
|
|
243
|
+
return self.length
|
|
244
|
+
|
|
245
|
+
def __iter__(self) -> Iterator[dict[str, Any]]:
|
|
246
|
+
"""Iterate over cells with optional O(1) memory shuffling.
|
|
247
|
+
|
|
248
|
+
Yields:
|
|
249
|
+
Per-cell dictionaries with ``counts``, ``obs``, ``obsm`` keys.
|
|
250
|
+
"""
|
|
251
|
+
return self._iter_with_builder(_build_cell_element)
|
|
252
|
+
|
|
253
|
+
def __next__(self) -> dict[str, Any]:
|
|
254
|
+
"""Get the next cell element (required by DataSourceModule).
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
StopIteration: When iteration is exhausted.
|
|
258
|
+
"""
|
|
259
|
+
raise StopIteration
|
|
260
|
+
|
|
261
|
+
def __getitem__(self, idx: int) -> dict[str, Any]:
|
|
262
|
+
"""Get data for a single cell by index.
|
|
263
|
+
|
|
264
|
+
Supports negative indexing.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
idx: Cell index (supports negative indexing).
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Dictionary with ``counts``, ``obs``, ``obsm`` keys.
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
IndexError: If idx is out of bounds.
|
|
274
|
+
"""
|
|
275
|
+
if idx < 0:
|
|
276
|
+
idx = self.length + idx
|
|
277
|
+
if idx < 0 or idx >= self.length:
|
|
278
|
+
raise IndexError(f"Cell index {idx} out of range for dataset with {self.length} cells")
|
|
279
|
+
return _build_cell_element(self.data, idx)
|
|
280
|
+
|
|
281
|
+
# =================================================================
|
|
282
|
+
# Batch retrieval
|
|
283
|
+
# =================================================================
|
|
284
|
+
|
|
285
|
+
def get_batch(self, batch_size: int, key: jax.Array | None = None) -> dict[str, Any]:
|
|
286
|
+
"""Get a batch of cells.
|
|
287
|
+
|
|
288
|
+
Stateful (advances internal index) when called without ``key``.
|
|
289
|
+
Stateless (random sampling) when called with ``key``.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
batch_size: Number of cells per batch.
|
|
293
|
+
key: Optional RNG key for stateless random sampling.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Dictionary with batched arrays.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def _gather(data: dict[str, Any], indices: jax.Array) -> dict[str, Any]:
|
|
300
|
+
counts = data["counts"][indices]
|
|
301
|
+
obs = {col: np.asarray(arr)[np.array(indices)] for col, arr in data["obs"].items()}
|
|
302
|
+
obsm: dict[str, jnp.ndarray] = {}
|
|
303
|
+
for emb_name, emb_arr in data["obsm"].items():
|
|
304
|
+
obsm[emb_name] = emb_arr[indices]
|
|
305
|
+
return {"counts": counts, "obs": obs, "obsm": obsm}
|
|
306
|
+
|
|
307
|
+
return self._get_batch_with_gather(batch_size, key, _gather)
|
|
308
|
+
|
|
309
|
+
# =================================================================
|
|
310
|
+
# State management
|
|
311
|
+
# =================================================================
|
|
312
|
+
|
|
313
|
+
def reset(self, seed: int | None = None) -> None:
|
|
314
|
+
"""Reset source to the beginning.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
seed: Unused (uses config seed).
|
|
318
|
+
"""
|
|
319
|
+
del seed
|
|
320
|
+
eager_reset(self.index, self.epoch, self._cache)
|
|
321
|
+
|
|
322
|
+
def set_shuffle(self, shuffle: bool) -> None:
|
|
323
|
+
"""Enable or disable shuffling.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
shuffle: Whether to shuffle data during iteration.
|
|
327
|
+
"""
|
|
328
|
+
self.shuffle = shuffle
|
|
329
|
+
|
|
330
|
+
def __repr__(self) -> str:
|
|
331
|
+
"""Return string representation."""
|
|
332
|
+
return (
|
|
333
|
+
f"AnnDataSource("
|
|
334
|
+
f"dataset={self.dataset_name}, "
|
|
335
|
+
f"length={self.length}, "
|
|
336
|
+
f"shuffle={self.shuffle}, "
|
|
337
|
+
f"epoch={self.epoch.get_value()})"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# =====================================================================
|
|
342
|
+
# Module-level helpers (kept outside the class)
|
|
343
|
+
# =====================================================================
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _build_cell_element(data: dict[str, Any], idx: int) -> dict[str, Any]:
|
|
347
|
+
"""Build a per-cell dictionary from the full dataset at a given index.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
data: The full data dict with ``counts``, ``obs``, ``obsm`` keys.
|
|
351
|
+
idx: Cell index.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Per-cell dictionary with scalar obs values and 1D arrays.
|
|
355
|
+
"""
|
|
356
|
+
cell_counts = data["counts"][idx]
|
|
357
|
+
cell_obs = {col: arr[idx] for col, arr in data["obs"].items()}
|
|
358
|
+
cell_obsm: dict[str, jnp.ndarray] = {}
|
|
359
|
+
for emb_name, emb_arr in data["obsm"].items():
|
|
360
|
+
cell_obsm[emb_name] = emb_arr[idx]
|
|
361
|
+
return {"counts": cell_counts, "obs": cell_obs, "obsm": cell_obsm}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""ArchiveII RNA secondary structure DataSource.
|
|
2
|
+
|
|
3
|
+
Loads RNA sequences with known secondary structures from the ArchiveII
|
|
4
|
+
benchmark dataset (Sloma & Mathews, 2016). Structures are provided in
|
|
5
|
+
dot-bracket notation (DBN) and represent experimentally validated RNA
|
|
6
|
+
secondary structures from crystal structures and NMR.
|
|
7
|
+
|
|
8
|
+
The dataset is read from CSV files produced by RNAFoldAssess, with
|
|
9
|
+
columns: name, sequence, ground_truth_type, ground_truth_data.
|
|
10
|
+
|
|
11
|
+
Only rows with ``ground_truth_type == "DBN"`` are loaded; rows without
|
|
12
|
+
structure annotations are silently skipped.
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
Sloma, M. F. & Mathews, D. H. (2016). Exact calculation of loop
|
|
16
|
+
formation probability identifies folding motifs in RNA secondary
|
|
17
|
+
structures. RNA 22, 1808-1818.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import csv
|
|
23
|
+
import logging
|
|
24
|
+
from collections.abc import Iterator
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from datarax.core.config import StructuralConfig
|
|
30
|
+
from datarax.core.data_source import DataSourceModule
|
|
31
|
+
from flax import nnx
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
_DEFAULT_DATA_DIR = "/media/mahdi/ssd23/Works/RNAFoldAssess/tutorial/processed_data"
|
|
36
|
+
_STRUCTURE_FILENAME = "example_data_structure.csv"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True, kw_only=True)
|
|
40
|
+
class ArchiveIIConfig(StructuralConfig):
|
|
41
|
+
"""Configuration for ArchiveIISource.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
data_dir: Directory containing the ArchiveII CSV file.
|
|
45
|
+
filename: Name of the CSV file with structure annotations.
|
|
46
|
+
max_sequences: Maximum number of sequences to load.
|
|
47
|
+
None means load all available sequences.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
data_dir: str = _DEFAULT_DATA_DIR
|
|
51
|
+
filename: str = _STRUCTURE_FILENAME
|
|
52
|
+
max_sequences: int | None = None
|
|
53
|
+
|
|
54
|
+
def __post_init__(self) -> None:
|
|
55
|
+
"""Validate that the data file exists."""
|
|
56
|
+
super().__post_init__()
|
|
57
|
+
path = Path(self.data_dir) / self.filename
|
|
58
|
+
if not path.exists():
|
|
59
|
+
raise FileNotFoundError(
|
|
60
|
+
f"ArchiveII data not found: {path}. "
|
|
61
|
+
f"Expected a CSV with columns: name, sequence, "
|
|
62
|
+
f"ground_truth_type, ground_truth_data."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _parse_csv(
|
|
67
|
+
csv_path: Path,
|
|
68
|
+
max_sequences: int | None,
|
|
69
|
+
) -> list[dict[str, str]]:
|
|
70
|
+
"""Parse ArchiveII CSV and return DBN-annotated entries.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
csv_path: Path to the CSV file.
|
|
74
|
+
max_sequences: Maximum entries to return. None for all.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of dicts with keys: name, sequence, structure.
|
|
78
|
+
"""
|
|
79
|
+
entries: list[dict[str, str]] = []
|
|
80
|
+
with csv_path.open(encoding="utf-8") as fh:
|
|
81
|
+
reader = csv.DictReader(fh)
|
|
82
|
+
for row in reader:
|
|
83
|
+
gt_type = row.get("ground_truth_type", "").strip()
|
|
84
|
+
gt_data = row.get("ground_truth_data", "").strip()
|
|
85
|
+
if gt_type != "DBN" or not gt_data:
|
|
86
|
+
continue
|
|
87
|
+
sequence = row["sequence"].strip().upper()
|
|
88
|
+
name = row["name"].strip()
|
|
89
|
+
entries.append(
|
|
90
|
+
{
|
|
91
|
+
"name": name,
|
|
92
|
+
"sequence": sequence,
|
|
93
|
+
"structure": gt_data,
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
if max_sequences is not None and len(entries) >= max_sequences:
|
|
97
|
+
break
|
|
98
|
+
return entries
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ArchiveIISource(DataSourceModule):
|
|
102
|
+
"""DataSource for ArchiveII RNA secondary structures.
|
|
103
|
+
|
|
104
|
+
Loads RNA sequences and their known dot-bracket notation (DBN)
|
|
105
|
+
structures from a CSV file. Only entries with ground_truth_type
|
|
106
|
+
``"DBN"`` are included.
|
|
107
|
+
|
|
108
|
+
Each entry is a dict with keys:
|
|
109
|
+
- ``name``: Sequence identifier (e.g. ``"1KXK_chain_0"``)
|
|
110
|
+
- ``sequence``: RNA sequence string (e.g. ``"GUCUACC..."``)
|
|
111
|
+
- ``structure``: DBN string (e.g. ``"....(((...)))..."``)
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
```python
|
|
115
|
+
config = ArchiveIIConfig(max_sequences=10)
|
|
116
|
+
source = ArchiveIISource(config)
|
|
117
|
+
data = source.load()
|
|
118
|
+
print(data["n_sequences"])
|
|
119
|
+
print(data["entries"][0]["name"])
|
|
120
|
+
```
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
data: dict[str, Any] = nnx.data()
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
config: ArchiveIIConfig,
|
|
128
|
+
*,
|
|
129
|
+
rngs: nnx.Rngs | None = None,
|
|
130
|
+
name: str | None = None,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Load ArchiveII RNA structures from CSV.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
config: Configuration with data directory and limits.
|
|
136
|
+
rngs: Optional RNG state (unused, for interface compat).
|
|
137
|
+
name: Optional module name.
|
|
138
|
+
"""
|
|
139
|
+
super().__init__(config, rngs=rngs, name=name or "ArchiveIISource")
|
|
140
|
+
csv_path = Path(config.data_dir) / config.filename
|
|
141
|
+
entries = _parse_csv(csv_path, config.max_sequences)
|
|
142
|
+
|
|
143
|
+
if not entries:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"No DBN-annotated sequences found in {csv_path}. "
|
|
146
|
+
f"Ensure the CSV has rows with "
|
|
147
|
+
f"ground_truth_type='DBN'."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
self.data = {
|
|
151
|
+
"entries": entries,
|
|
152
|
+
"n_sequences": len(entries),
|
|
153
|
+
}
|
|
154
|
+
logger.info(
|
|
155
|
+
"Loaded ArchiveII: %d sequences from %s",
|
|
156
|
+
len(entries),
|
|
157
|
+
csv_path,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def load(self) -> dict[str, Any]:
|
|
161
|
+
"""Return the full dataset as a dictionary.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Dict with keys: entries (list of dicts), n_sequences.
|
|
165
|
+
"""
|
|
166
|
+
return self.data
|
|
167
|
+
|
|
168
|
+
def __len__(self) -> int:
|
|
169
|
+
"""Return the number of loaded sequences."""
|
|
170
|
+
return self.data["n_sequences"]
|
|
171
|
+
|
|
172
|
+
def __iter__(self) -> Iterator[dict[str, str]]:
|
|
173
|
+
"""Iterate over individual RNA entries."""
|
|
174
|
+
yield from self.data["entries"]
|