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,678 @@
|
|
|
1
|
+
"""Differentiable CNV Segmentation operator.
|
|
2
|
+
|
|
3
|
+
This module provides differentiable implementations of copy number
|
|
4
|
+
variation segmentation:
|
|
5
|
+
|
|
6
|
+
- ``DifferentiableCNVSegmentation``: Attention-based soft changepoint detection.
|
|
7
|
+
- ``EnhancedCNVSegmentation``: Multi-signal fusion (log-ratio + BAF + SNP
|
|
8
|
+
density), pyramidal smoothing (infercnvpy-style), STDDEV-based dynamic
|
|
9
|
+
thresholding, and HMM state-to-copy-number mapping.
|
|
10
|
+
|
|
11
|
+
Key techniques:
|
|
12
|
+
- Attention mechanism identifies segment boundaries softly.
|
|
13
|
+
- Pyramidal (triangular) convolution for robust spatial smoothing.
|
|
14
|
+
- Dynamic threshold = scale * std(smoothed_signal) filters noise.
|
|
15
|
+
- Learnable linear fusion of heterogeneous signals.
|
|
16
|
+
- Soft copy-number state posteriors via learned emission model.
|
|
17
|
+
|
|
18
|
+
Applications: CNV analysis, coverage depth segmentation, breakpoint detection.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
import jax
|
|
25
|
+
import jax.numpy as jnp
|
|
26
|
+
from datarax.core.config import OperatorConfig
|
|
27
|
+
from flax import nnx
|
|
28
|
+
from jaxtyping import Array, Float, PyTree
|
|
29
|
+
|
|
30
|
+
from diffbio.core import soft_ops
|
|
31
|
+
from diffbio.core.base_operators import TemperatureOperator
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class CNVSegmentationConfig(OperatorConfig):
|
|
36
|
+
"""Configuration for DifferentiableCNVSegmentation.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
max_segments: Maximum number of segments to detect.
|
|
40
|
+
hidden_dim: Hidden dimension for attention layers.
|
|
41
|
+
attention_heads: Number of attention heads.
|
|
42
|
+
temperature: Temperature for softmax operations.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
max_segments: int = 100
|
|
46
|
+
hidden_dim: int = 64
|
|
47
|
+
attention_heads: int = 4
|
|
48
|
+
temperature: float = 1.0
|
|
49
|
+
|
|
50
|
+
def __post_init__(self) -> None:
|
|
51
|
+
"""Validate segmentation hyperparameters."""
|
|
52
|
+
super().__post_init__()
|
|
53
|
+
|
|
54
|
+
if self.max_segments <= 0:
|
|
55
|
+
raise ValueError(f"max_segments must be positive, got {self.max_segments}")
|
|
56
|
+
if self.hidden_dim <= 0:
|
|
57
|
+
raise ValueError(f"hidden_dim must be positive, got {self.hidden_dim}")
|
|
58
|
+
if self.attention_heads <= 0:
|
|
59
|
+
raise ValueError(f"attention_heads must be positive, got {self.attention_heads}")
|
|
60
|
+
if self.temperature <= 0.0:
|
|
61
|
+
raise ValueError(f"temperature must be positive, got {self.temperature}")
|
|
62
|
+
if self.hidden_dim % self.attention_heads != 0:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
"hidden_dim must be divisible by attention_heads, got "
|
|
65
|
+
f"hidden_dim={self.hidden_dim}, attention_heads={self.attention_heads}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DifferentiableCNVSegmentation(TemperatureOperator):
|
|
70
|
+
"""Soft CNV segmentation using attention-based changepoint detection.
|
|
71
|
+
|
|
72
|
+
This operator identifies segment boundaries in coverage data using
|
|
73
|
+
attention mechanisms, replacing hard Circular Binary Segmentation
|
|
74
|
+
with differentiable soft assignments.
|
|
75
|
+
|
|
76
|
+
Algorithm:
|
|
77
|
+
1. Project coverage signal into hidden space
|
|
78
|
+
2. Use self-attention to identify changepoint positions
|
|
79
|
+
3. Compute soft segment assignments via attention
|
|
80
|
+
4. Compute segment means as weighted averages
|
|
81
|
+
|
|
82
|
+
Inherits from TemperatureOperator to get:
|
|
83
|
+
|
|
84
|
+
- _temperature property for temperature-controlled smoothing
|
|
85
|
+
- soft_max() for logsumexp-based smooth maximum
|
|
86
|
+
- soft_argmax() for soft position selection
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
config: CNVSegmentationConfig with model parameters.
|
|
90
|
+
rngs: Flax NNX random number generators.
|
|
91
|
+
name: Optional operator name.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
```python
|
|
95
|
+
config = CNVSegmentationConfig(max_segments=50)
|
|
96
|
+
segmenter = DifferentiableCNVSegmentation(config, rngs=nnx.Rngs(42))
|
|
97
|
+
data = {"coverage": coverage_signal} # (n_positions,)
|
|
98
|
+
result, state, meta = segmenter.apply(data, {}, None)
|
|
99
|
+
```
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
config: CNVSegmentationConfig # pyright: ignore[reportIncompatibleVariableOverride]
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
config: CNVSegmentationConfig,
|
|
107
|
+
*,
|
|
108
|
+
rngs: nnx.Rngs | None = None,
|
|
109
|
+
name: str | None = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Initialize the CNV segmentation operator.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
config: Segmentation configuration.
|
|
115
|
+
rngs: Random number generators for initialization.
|
|
116
|
+
name: Optional operator name.
|
|
117
|
+
"""
|
|
118
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
119
|
+
|
|
120
|
+
if rngs is None:
|
|
121
|
+
rngs = nnx.Rngs(0)
|
|
122
|
+
|
|
123
|
+
# Input projection: coverage value -> hidden
|
|
124
|
+
self.input_proj = nnx.Linear(1, config.hidden_dim, rngs=rngs)
|
|
125
|
+
|
|
126
|
+
# Positional encoding projection
|
|
127
|
+
self.pos_proj = nnx.Linear(1, config.hidden_dim, rngs=rngs)
|
|
128
|
+
|
|
129
|
+
# Attention projections (for boundary detection)
|
|
130
|
+
self.query_proj = nnx.Linear(config.hidden_dim, config.hidden_dim, rngs=rngs)
|
|
131
|
+
self.key_proj = nnx.Linear(config.hidden_dim, config.hidden_dim, rngs=rngs)
|
|
132
|
+
self.value_proj = nnx.Linear(config.hidden_dim, config.hidden_dim, rngs=rngs)
|
|
133
|
+
|
|
134
|
+
# Boundary detection head
|
|
135
|
+
self.boundary_head = nnx.Linear(config.hidden_dim, 1, rngs=rngs)
|
|
136
|
+
|
|
137
|
+
# Segment centroids (learnable)
|
|
138
|
+
key = rngs.params()
|
|
139
|
+
init_centroids = jax.random.normal(key, (config.max_segments, config.hidden_dim)) * 0.1
|
|
140
|
+
self.segment_centroids = nnx.Param(init_centroids)
|
|
141
|
+
|
|
142
|
+
def compute_embeddings(
|
|
143
|
+
self,
|
|
144
|
+
coverage: Float[Array, "n_positions"],
|
|
145
|
+
) -> Float[Array, "n_positions hidden_dim"]:
|
|
146
|
+
"""Compute position embeddings from coverage signal.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
coverage: Coverage values at each position.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Embedded representation of each position.
|
|
153
|
+
"""
|
|
154
|
+
n_positions = coverage.shape[0]
|
|
155
|
+
|
|
156
|
+
# Project coverage values
|
|
157
|
+
coverage_emb = self.input_proj(coverage[:, None]) # (n_positions, hidden_dim)
|
|
158
|
+
|
|
159
|
+
# Add positional encoding
|
|
160
|
+
positions = jnp.arange(n_positions, dtype=jnp.float32) / n_positions
|
|
161
|
+
pos_emb = self.pos_proj(positions[:, None]) # (n_positions, hidden_dim)
|
|
162
|
+
|
|
163
|
+
embeddings = coverage_emb + pos_emb
|
|
164
|
+
|
|
165
|
+
return embeddings
|
|
166
|
+
|
|
167
|
+
def compute_boundary_probs(
|
|
168
|
+
self,
|
|
169
|
+
embeddings: Float[Array, "n_positions hidden_dim"],
|
|
170
|
+
) -> Float[Array, "n_positions"]:
|
|
171
|
+
"""Compute soft boundary probabilities using self-attention.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
embeddings: Position embeddings.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Probability of being a segment boundary at each position.
|
|
178
|
+
"""
|
|
179
|
+
n_positions = embeddings.shape[0]
|
|
180
|
+
|
|
181
|
+
# Self-attention to detect changepoints
|
|
182
|
+
Q = self.query_proj(embeddings) # (n_positions, hidden_dim)
|
|
183
|
+
K = self.key_proj(embeddings) # (n_positions, hidden_dim)
|
|
184
|
+
V = self.value_proj(embeddings) # (n_positions, hidden_dim)
|
|
185
|
+
|
|
186
|
+
# Compute attention scores
|
|
187
|
+
head_dim = self.config.hidden_dim // self.config.attention_heads
|
|
188
|
+
scale = jnp.sqrt(head_dim).astype(embeddings.dtype)
|
|
189
|
+
|
|
190
|
+
# Reshape for multi-head attention
|
|
191
|
+
Q = Q.reshape(n_positions, self.config.attention_heads, head_dim)
|
|
192
|
+
K = K.reshape(n_positions, self.config.attention_heads, head_dim)
|
|
193
|
+
V = V.reshape(n_positions, self.config.attention_heads, head_dim)
|
|
194
|
+
|
|
195
|
+
# Attention: (n_positions, n_heads, n_positions)
|
|
196
|
+
attn_scores = jnp.einsum("nhd,mhd->nhm", Q, K) / scale
|
|
197
|
+
|
|
198
|
+
# Soft attention weights
|
|
199
|
+
attn_weights = jax.nn.softmax(attn_scores / self._temperature, axis=-1)
|
|
200
|
+
|
|
201
|
+
# Attend to values
|
|
202
|
+
attended = jnp.einsum("nhm,mhd->nhd", attn_weights, V)
|
|
203
|
+
attended = attended.reshape(n_positions, self.config.hidden_dim)
|
|
204
|
+
|
|
205
|
+
# Compute boundary probability from attended features
|
|
206
|
+
# Look at how much attention pattern changes
|
|
207
|
+
boundary_logits = self.boundary_head(attended).squeeze(-1) # (n_positions,)
|
|
208
|
+
|
|
209
|
+
# Boundaries at positions where signal changes
|
|
210
|
+
boundary_probs = jax.nn.sigmoid(boundary_logits)
|
|
211
|
+
|
|
212
|
+
return boundary_probs
|
|
213
|
+
|
|
214
|
+
def compute_segment_assignments(
|
|
215
|
+
self,
|
|
216
|
+
embeddings: Float[Array, "n_positions hidden_dim"],
|
|
217
|
+
) -> Float[Array, "n_positions max_segments"]:
|
|
218
|
+
"""Compute soft segment assignments via attention to centroids.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
embeddings: Position embeddings.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Soft assignment probability to each segment.
|
|
225
|
+
"""
|
|
226
|
+
centroids = self.segment_centroids[...] # (max_segments, hidden_dim)
|
|
227
|
+
|
|
228
|
+
# Compute similarity to segment centroids
|
|
229
|
+
# (n_positions, hidden_dim) x (hidden_dim, max_segments) -> (n_positions, max_segments)
|
|
230
|
+
similarities = jnp.einsum("nh,sh->ns", embeddings, centroids)
|
|
231
|
+
|
|
232
|
+
# Soft assignments via softmax
|
|
233
|
+
assignments = jax.nn.softmax(similarities / self._temperature, axis=-1)
|
|
234
|
+
|
|
235
|
+
return assignments
|
|
236
|
+
|
|
237
|
+
def compute_segment_means(
|
|
238
|
+
self,
|
|
239
|
+
coverage: Float[Array, "n_positions"],
|
|
240
|
+
assignments: Float[Array, "n_positions max_segments"],
|
|
241
|
+
) -> Float[Array, "max_segments"]:
|
|
242
|
+
"""Compute segment mean values.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
coverage: Coverage values.
|
|
246
|
+
assignments: Soft segment assignments.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Mean coverage for each segment.
|
|
250
|
+
"""
|
|
251
|
+
# Weighted sum of coverage / sum of weights
|
|
252
|
+
weighted_sum = jnp.einsum("n,ns->s", coverage, assignments) # (max_segments,)
|
|
253
|
+
weight_sum = jnp.sum(assignments, axis=0) + 1e-10 # (max_segments,)
|
|
254
|
+
|
|
255
|
+
segment_means = weighted_sum / weight_sum
|
|
256
|
+
|
|
257
|
+
return segment_means
|
|
258
|
+
|
|
259
|
+
def compute_smoothed_coverage(
|
|
260
|
+
self,
|
|
261
|
+
coverage: Float[Array, "n_positions"],
|
|
262
|
+
assignments: Float[Array, "n_positions max_segments"],
|
|
263
|
+
segment_means: Float[Array, "max_segments"],
|
|
264
|
+
) -> Float[Array, "n_positions"]:
|
|
265
|
+
"""Compute smoothed coverage from segment assignments.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
coverage: Original coverage values.
|
|
269
|
+
assignments: Soft segment assignments.
|
|
270
|
+
segment_means: Mean value for each segment.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Smoothed coverage (soft segmentation result).
|
|
274
|
+
"""
|
|
275
|
+
# Weighted combination of segment means
|
|
276
|
+
smoothed = jnp.einsum("ns,s->n", assignments, segment_means)
|
|
277
|
+
|
|
278
|
+
return smoothed
|
|
279
|
+
|
|
280
|
+
def apply(
|
|
281
|
+
self,
|
|
282
|
+
data: PyTree,
|
|
283
|
+
state: PyTree,
|
|
284
|
+
metadata: dict[str, Any] | None,
|
|
285
|
+
random_params: Any = None,
|
|
286
|
+
stats: dict[str, Any] | None = None,
|
|
287
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
288
|
+
"""Apply CNV segmentation to coverage data.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
data: Dictionary containing:
|
|
292
|
+
- "coverage": Coverage signal (n_positions,)
|
|
293
|
+
state: Element state (passed through unchanged)
|
|
294
|
+
metadata: Element metadata (passed through unchanged)
|
|
295
|
+
random_params: Not used
|
|
296
|
+
stats: Not used
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Tuple of (transformed_data, state, metadata):
|
|
300
|
+
- transformed_data contains:
|
|
301
|
+
|
|
302
|
+
- "coverage": Original coverage
|
|
303
|
+
- "boundary_probs": Soft boundary probabilities
|
|
304
|
+
- "segment_assignments": Soft segment memberships
|
|
305
|
+
- "segment_means": Mean value per segment
|
|
306
|
+
- "smoothed_coverage": Segmented/smoothed signal
|
|
307
|
+
- state is passed through unchanged
|
|
308
|
+
- metadata is passed through unchanged
|
|
309
|
+
"""
|
|
310
|
+
coverage = data["coverage"]
|
|
311
|
+
|
|
312
|
+
# Compute embeddings
|
|
313
|
+
embeddings = self.compute_embeddings(coverage)
|
|
314
|
+
|
|
315
|
+
# Detect boundaries
|
|
316
|
+
boundary_probs = self.compute_boundary_probs(embeddings)
|
|
317
|
+
|
|
318
|
+
# Soft segment assignments
|
|
319
|
+
segment_assignments = self.compute_segment_assignments(embeddings)
|
|
320
|
+
|
|
321
|
+
# Segment statistics
|
|
322
|
+
segment_means = self.compute_segment_means(coverage, segment_assignments)
|
|
323
|
+
|
|
324
|
+
# Smoothed signal
|
|
325
|
+
smoothed_coverage = self.compute_smoothed_coverage(
|
|
326
|
+
coverage, segment_assignments, segment_means
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Build output data
|
|
330
|
+
transformed_data = {
|
|
331
|
+
"coverage": coverage,
|
|
332
|
+
"boundary_probs": boundary_probs,
|
|
333
|
+
"segment_assignments": segment_assignments,
|
|
334
|
+
"segment_means": segment_means,
|
|
335
|
+
"smoothed_coverage": smoothed_coverage,
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return transformed_data, state, metadata
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# =========================================================================
|
|
342
|
+
# Enhanced CNV Segmentation
|
|
343
|
+
# =========================================================================
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@dataclass(frozen=True)
|
|
347
|
+
class EnhancedCNVSegmentationConfig(CNVSegmentationConfig):
|
|
348
|
+
"""Configuration for EnhancedCNVSegmentation.
|
|
349
|
+
|
|
350
|
+
Extends the base CNV segmentation with multi-signal fusion, pyramidal
|
|
351
|
+
smoothing, dynamic thresholding, and HMM copy-number state mapping.
|
|
352
|
+
|
|
353
|
+
Attributes:
|
|
354
|
+
max_segments: Maximum number of segments to detect.
|
|
355
|
+
hidden_dim: Hidden dimension for attention layers.
|
|
356
|
+
attention_heads: Number of attention heads.
|
|
357
|
+
temperature: Temperature for softmax operations.
|
|
358
|
+
use_baf: Whether to incorporate B-allele frequency signal.
|
|
359
|
+
baf_weight: Initial weight for BAF signal in fusion.
|
|
360
|
+
smoothing_window: Window size for pyramidal smoothing convolution.
|
|
361
|
+
threshold_scale: Multiplier for STDDEV-based dynamic threshold.
|
|
362
|
+
n_copy_states: Number of discrete copy-number states (0-somy to N-somy).
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
use_baf: bool = False
|
|
366
|
+
baf_weight: float = 0.3
|
|
367
|
+
smoothing_window: int = 100
|
|
368
|
+
threshold_scale: float = 1.5
|
|
369
|
+
n_copy_states: int = 5
|
|
370
|
+
|
|
371
|
+
def __post_init__(self) -> None:
|
|
372
|
+
"""Validate enhanced segmentation hyperparameters."""
|
|
373
|
+
super().__post_init__()
|
|
374
|
+
|
|
375
|
+
if not 0.0 <= self.baf_weight <= 1.0:
|
|
376
|
+
raise ValueError(f"baf_weight must be in [0, 1], got {self.baf_weight}")
|
|
377
|
+
if self.smoothing_window <= 0:
|
|
378
|
+
raise ValueError(f"smoothing_window must be positive, got {self.smoothing_window}")
|
|
379
|
+
if self.threshold_scale <= 0.0:
|
|
380
|
+
raise ValueError(f"threshold_scale must be positive, got {self.threshold_scale}")
|
|
381
|
+
if self.n_copy_states <= 1:
|
|
382
|
+
raise ValueError(f"n_copy_states must be greater than 1, got {self.n_copy_states}")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _build_pyramidal_kernel(window_size: int) -> Float[Array, "window_size"]:
|
|
386
|
+
"""Build a normalized pyramidal (triangular) smoothing kernel.
|
|
387
|
+
|
|
388
|
+
Mirrors the infercnvpy approach: ``min(r, r[::-1])`` creates a
|
|
389
|
+
triangle that peaks at the centre and tapers linearly to the edges.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
window_size: Length of the kernel (must be >= 1).
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
Normalised 1-D pyramidal kernel that sums to 1.
|
|
396
|
+
"""
|
|
397
|
+
r = jnp.arange(1, window_size + 1, dtype=jnp.float32)
|
|
398
|
+
pyramid = jnp.minimum(r, r[::-1])
|
|
399
|
+
return pyramid / jnp.sum(pyramid)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class EnhancedCNVSegmentation(DifferentiableCNVSegmentation):
|
|
403
|
+
"""Enhanced CNV segmentation with multi-signal fusion and pyramidal smoothing.
|
|
404
|
+
|
|
405
|
+
Inherits from DifferentiableCNVSegmentation and adds:
|
|
406
|
+
|
|
407
|
+
1. **Multi-signal fusion** -- learnable linear combination of log-ratio
|
|
408
|
+
coverage, BAF, and SNP density signals.
|
|
409
|
+
2. **Pyramidal smoothing** -- infercnvpy-style triangular convolution
|
|
410
|
+
for spatial noise reduction.
|
|
411
|
+
3. **Dynamic thresholding** -- ``threshold_scale * std(smoothed)``
|
|
412
|
+
filters low-amplitude noise.
|
|
413
|
+
4. **HMM state mapping** -- soft copy-number posteriors (0-somy to
|
|
414
|
+
4-somy by default) via learned emission model.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
config: EnhancedCNVSegmentationConfig with model parameters.
|
|
418
|
+
rngs: Flax NNX random number generators.
|
|
419
|
+
name: Optional operator name.
|
|
420
|
+
|
|
421
|
+
Example:
|
|
422
|
+
```python
|
|
423
|
+
config = EnhancedCNVSegmentationConfig(
|
|
424
|
+
max_segments=50, use_baf=True, smoothing_window=100,
|
|
425
|
+
)
|
|
426
|
+
op = EnhancedCNVSegmentation(config, rngs=nnx.Rngs(0))
|
|
427
|
+
data = {"coverage": cov, "baf_signal": baf, "snp_density": snp}
|
|
428
|
+
result, state, meta = op.apply(data, {}, None)
|
|
429
|
+
```
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
config: EnhancedCNVSegmentationConfig # pyright: ignore[reportIncompatibleVariableOverride]
|
|
433
|
+
|
|
434
|
+
def __init__(
|
|
435
|
+
self,
|
|
436
|
+
config: EnhancedCNVSegmentationConfig,
|
|
437
|
+
*,
|
|
438
|
+
rngs: nnx.Rngs | None = None,
|
|
439
|
+
name: str | None = None,
|
|
440
|
+
) -> None:
|
|
441
|
+
"""Initialize the enhanced CNV segmentation operator.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
config: Enhanced segmentation configuration.
|
|
445
|
+
rngs: Random number generators for initialization.
|
|
446
|
+
name: Optional operator name.
|
|
447
|
+
"""
|
|
448
|
+
super().__init__(config, rngs=rngs, name=name)
|
|
449
|
+
|
|
450
|
+
if rngs is None:
|
|
451
|
+
rngs = nnx.Rngs(0)
|
|
452
|
+
|
|
453
|
+
# --- Signal fusion ---
|
|
454
|
+
# Number of input channels: coverage is always present.
|
|
455
|
+
# BAF and SNP density are optional extras.
|
|
456
|
+
n_signals = 3 if config.use_baf else 1
|
|
457
|
+
self.signal_fusion = nnx.Linear(n_signals, 1, rngs=rngs)
|
|
458
|
+
|
|
459
|
+
# --- Copy-number state mapping head ---
|
|
460
|
+
self.copy_number_head = nnx.Linear(
|
|
461
|
+
config.hidden_dim,
|
|
462
|
+
config.n_copy_states,
|
|
463
|
+
rngs=rngs,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# -----------------------------------------------------------------
|
|
467
|
+
# Multi-signal fusion
|
|
468
|
+
# -----------------------------------------------------------------
|
|
469
|
+
|
|
470
|
+
def fuse_signals(
|
|
471
|
+
self,
|
|
472
|
+
data: dict[str, Float[Array, "n_positions"]],
|
|
473
|
+
) -> Float[Array, "n_positions"]:
|
|
474
|
+
"""Fuse multiple genomic signals via learned linear combination.
|
|
475
|
+
|
|
476
|
+
When ``use_baf`` is enabled, the operator expects ``baf_signal``
|
|
477
|
+
and ``snp_density`` alongside ``coverage`` in the data dict.
|
|
478
|
+
A learnable ``nnx.Linear(n_signals, 1)`` combines them.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
data: Dictionary with at least ``"coverage"`` key.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
Fused 1-D signal of shape ``(n_positions,)``.
|
|
485
|
+
"""
|
|
486
|
+
coverage = data["coverage"]
|
|
487
|
+
|
|
488
|
+
if self.config.use_baf:
|
|
489
|
+
baf = data.get("baf_signal", jnp.zeros_like(coverage))
|
|
490
|
+
snp = data.get("snp_density", jnp.zeros_like(coverage))
|
|
491
|
+
baf_scaled = baf * self.config.baf_weight
|
|
492
|
+
# Stack into (n_positions, 3)
|
|
493
|
+
stacked = jnp.stack([coverage, baf_scaled, snp], axis=-1)
|
|
494
|
+
else:
|
|
495
|
+
stacked = coverage[:, None] # (n_positions, 1)
|
|
496
|
+
|
|
497
|
+
# Linear fusion -> (n_positions, 1) -> squeeze
|
|
498
|
+
fused = self.signal_fusion(stacked).squeeze(-1)
|
|
499
|
+
return fused
|
|
500
|
+
|
|
501
|
+
# -----------------------------------------------------------------
|
|
502
|
+
# Pyramidal smoothing (infercnvpy-style)
|
|
503
|
+
# -----------------------------------------------------------------
|
|
504
|
+
|
|
505
|
+
def pyramidal_smooth(
|
|
506
|
+
self,
|
|
507
|
+
signal: Float[Array, "n_positions"],
|
|
508
|
+
) -> Float[Array, "n_positions"]:
|
|
509
|
+
"""Apply pyramidal (triangular) convolution smoothing.
|
|
510
|
+
|
|
511
|
+
Mirrors the infercnvpy ``_running_mean`` approach: a triangular
|
|
512
|
+
kernel ``min(r, r[::-1])`` is convolved with the signal using
|
|
513
|
+
``'same'`` mode so the output length matches the input.
|
|
514
|
+
|
|
515
|
+
The convolution is implemented via ``jax.numpy.convolve`` which
|
|
516
|
+
is fully differentiable and JIT-compatible.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
signal: 1-D signal to smooth.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
Smoothed signal of the same length.
|
|
523
|
+
"""
|
|
524
|
+
kernel = _build_pyramidal_kernel(self.config.smoothing_window)
|
|
525
|
+
# 'same' mode preserves signal length
|
|
526
|
+
smoothed = jnp.convolve(signal, kernel, mode="same")
|
|
527
|
+
return smoothed
|
|
528
|
+
|
|
529
|
+
# -----------------------------------------------------------------
|
|
530
|
+
# Dynamic thresholding (infercnvpy-style)
|
|
531
|
+
# -----------------------------------------------------------------
|
|
532
|
+
|
|
533
|
+
def dynamic_threshold_filter(
|
|
534
|
+
self,
|
|
535
|
+
signal: Float[Array, "n_positions"],
|
|
536
|
+
) -> tuple[Float[Array, "n_positions"], Float[Array, ""]]:
|
|
537
|
+
"""Apply STDDEV-based dynamic noise filtering.
|
|
538
|
+
|
|
539
|
+
Following infercnvpy Step 5: ``threshold = scale * std(signal)``.
|
|
540
|
+
Values with absolute magnitude below the threshold are pushed
|
|
541
|
+
toward zero using a soft sigmoid gate for differentiability.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
signal: Smoothed signal to filter.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
Tuple of (filtered_signal, threshold_value).
|
|
548
|
+
"""
|
|
549
|
+
noise_threshold = self.config.threshold_scale * jnp.std(signal)
|
|
550
|
+
# Soft gate: sigmoid((|x| - threshold) / temperature)
|
|
551
|
+
# Outputs near 0 when |x| < threshold, near 1 when |x| > threshold
|
|
552
|
+
gate = soft_ops.greater(
|
|
553
|
+
soft_ops.abs(signal, softness=self._temperature),
|
|
554
|
+
noise_threshold,
|
|
555
|
+
softness=self._temperature,
|
|
556
|
+
)
|
|
557
|
+
filtered = signal * gate
|
|
558
|
+
return filtered, noise_threshold
|
|
559
|
+
|
|
560
|
+
# -----------------------------------------------------------------
|
|
561
|
+
# HMM state mapping
|
|
562
|
+
# -----------------------------------------------------------------
|
|
563
|
+
|
|
564
|
+
def compute_copy_number_posteriors(
|
|
565
|
+
self,
|
|
566
|
+
embeddings: Float[Array, "n_positions hidden_dim"],
|
|
567
|
+
) -> Float[Array, "n_positions n_copy_states"]:
|
|
568
|
+
"""Map embeddings to soft copy-number state posteriors.
|
|
569
|
+
|
|
570
|
+
Uses a learned linear head followed by temperature-scaled softmax
|
|
571
|
+
to produce per-position posterior probabilities over discrete
|
|
572
|
+
copy-number states (0-somy through (n_copy_states-1)-somy).
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
embeddings: Position embeddings.
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
Copy-number state posteriors, shape ``(n_positions, n_copy_states)``.
|
|
579
|
+
"""
|
|
580
|
+
logits = self.copy_number_head(embeddings)
|
|
581
|
+
return jax.nn.softmax(logits / self._temperature, axis=-1)
|
|
582
|
+
|
|
583
|
+
def compute_expected_copy_number(
|
|
584
|
+
self,
|
|
585
|
+
posteriors: Float[Array, "n_positions n_copy_states"],
|
|
586
|
+
) -> Float[Array, "n_positions"]:
|
|
587
|
+
"""Compute expected copy number as posterior-weighted state values.
|
|
588
|
+
|
|
589
|
+
E[CN] = sum_k( k * P(state=k) ) for k in 0..n_copy_states-1.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
posteriors: Copy-number state posteriors.
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
Expected copy number at each position.
|
|
596
|
+
"""
|
|
597
|
+
state_values = jnp.arange(self.config.n_copy_states, dtype=jnp.float32)
|
|
598
|
+
return jnp.einsum("ns,s->n", posteriors, state_values)
|
|
599
|
+
|
|
600
|
+
# -----------------------------------------------------------------
|
|
601
|
+
# Main apply
|
|
602
|
+
# -----------------------------------------------------------------
|
|
603
|
+
|
|
604
|
+
def apply(
|
|
605
|
+
self,
|
|
606
|
+
data: PyTree,
|
|
607
|
+
state: PyTree,
|
|
608
|
+
metadata: dict[str, Any] | None,
|
|
609
|
+
random_params: Any = None,
|
|
610
|
+
stats: dict[str, Any] | None = None,
|
|
611
|
+
) -> tuple[PyTree, PyTree, dict[str, Any] | None]:
|
|
612
|
+
"""Apply enhanced CNV segmentation to genomic signal data.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
data: Dictionary containing:
|
|
616
|
+
- ``"coverage"``: Log-ratio coverage signal ``(n_positions,)``
|
|
617
|
+
- ``"baf_signal"`` (optional): B-allele frequency ``(n_positions,)``
|
|
618
|
+
- ``"snp_density"`` (optional): SNP density ``(n_positions,)``
|
|
619
|
+
state: Element state (passed through unchanged).
|
|
620
|
+
metadata: Element metadata (passed through unchanged).
|
|
621
|
+
random_params: Not used.
|
|
622
|
+
stats: Not used.
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
Tuple of ``(transformed_data, state, metadata)`` where
|
|
626
|
+
``transformed_data`` contains:
|
|
627
|
+
|
|
628
|
+
- ``"coverage"``: Original coverage
|
|
629
|
+
- ``"fused_signal"``: Fused multi-signal output
|
|
630
|
+
- ``"pyramidal_smoothed"``: After pyramidal convolution
|
|
631
|
+
- ``"thresholded_signal"``: After dynamic noise filtering
|
|
632
|
+
- ``"dynamic_threshold"``: Scalar threshold value
|
|
633
|
+
- ``"boundary_probs"``: Soft boundary probabilities
|
|
634
|
+
- ``"segment_assignments"``: Soft segment memberships
|
|
635
|
+
- ``"segment_means"``: Mean value per segment
|
|
636
|
+
- ``"smoothed_coverage"``: Final segmented/smoothed signal
|
|
637
|
+
- ``"copy_number_posteriors"``: Per-position CN state posteriors
|
|
638
|
+
- ``"expected_copy_number"``: Expected copy number per position
|
|
639
|
+
"""
|
|
640
|
+
coverage = data["coverage"]
|
|
641
|
+
|
|
642
|
+
# 1. Multi-signal fusion
|
|
643
|
+
fused = self.fuse_signals(data)
|
|
644
|
+
|
|
645
|
+
# 2. Pyramidal smoothing
|
|
646
|
+
smoothed = self.pyramidal_smooth(fused)
|
|
647
|
+
|
|
648
|
+
# 3. Dynamic thresholding
|
|
649
|
+
thresholded, threshold_val = self.dynamic_threshold_filter(smoothed)
|
|
650
|
+
|
|
651
|
+
# 4. Attention-based segmentation on thresholded signal (inherited methods)
|
|
652
|
+
embeddings = self.compute_embeddings(thresholded)
|
|
653
|
+
boundary_probs = self.compute_boundary_probs(embeddings)
|
|
654
|
+
segment_assignments = self.compute_segment_assignments(embeddings)
|
|
655
|
+
segment_means = self.compute_segment_means(thresholded, segment_assignments)
|
|
656
|
+
smoothed_coverage = self.compute_smoothed_coverage(
|
|
657
|
+
thresholded, segment_assignments, segment_means
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# 5. HMM state mapping
|
|
661
|
+
cn_posteriors = self.compute_copy_number_posteriors(embeddings)
|
|
662
|
+
expected_cn = self.compute_expected_copy_number(cn_posteriors)
|
|
663
|
+
|
|
664
|
+
transformed_data = {
|
|
665
|
+
"coverage": coverage,
|
|
666
|
+
"fused_signal": fused,
|
|
667
|
+
"pyramidal_smoothed": smoothed,
|
|
668
|
+
"thresholded_signal": thresholded,
|
|
669
|
+
"dynamic_threshold": threshold_val,
|
|
670
|
+
"boundary_probs": boundary_probs,
|
|
671
|
+
"segment_assignments": segment_assignments,
|
|
672
|
+
"segment_means": segment_means,
|
|
673
|
+
"smoothed_coverage": smoothed_coverage,
|
|
674
|
+
"copy_number_posteriors": cn_posteriors,
|
|
675
|
+
"expected_copy_number": expected_cn,
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return transformed_data, state, metadata
|