Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
0b529dd
Port scale-aware inference and LOT batch correction to modular-viscy-…
Soorya19Pradeep May 14, 2026
5e6407a
Add visualization script for TripletDataModule scale-aware rescaling
Soorya19Pradeep May 14, 2026
378a4ac
fix(viscy-data): handle nested timepoint_statistics in _collate_norm_…
edyoshikun Jun 29, 2026
19a3301
feat(viscy-data): add Z-reduction and focus-centered z_range to Tripl…
edyoshikun Jun 29, 2026
365fc42
docs(dynaclr): triplet inference DAG + sample 2D-from-3D predict config
edyoshikun Jun 29, 2026
dc39073
docs(dynaclr): add triplet z-projection dataloader inspection script
edyoshikun Jun 29, 2026
a14a0ac
Potential fix for pull request finding
edyoshikun Jun 29, 2026
52c72a1
Potential fix for pull request finding
edyoshikun Jun 29, 2026
6d320ae
Potential fix for pull request finding
edyoshikun Jun 29, 2026
2a1b327
Potential fix for pull request finding
edyoshikun Jun 29, 2026
dbe22f2
Potential fix for pull request finding
edyoshikun Jun 29, 2026
4a2de14
Potential fix for pull request finding
edyoshikun Jun 29, 2026
5b81c2c
fix(viscy-data): address triplet z-reduction/rescaling review findings
edyoshikun Jun 30, 2026
c7f8a16
CLI to split ann data by a column to group by
edyoshikun Jul 6, 2026
b39205f
perf(viscy-utils): GPU-accelerate MMD via PyTorch
Soorya19Pradeep Jul 10, 2026
f85cc7e
feat(viscy-utils): add MMD witness function
Soorya19Pradeep Jul 10, 2026
fa09832
feat(dynaclr): add MMD-witness weak-label source for linear classifiers
edyoshikun Jul 14, 2026
c9d67ec
test(dynaclr): cover MMD-witness weak labeling for linear classifiers
edyoshikun Jul 14, 2026
c643e95
config(dynaclr): add witness-labeled LC recipe for infectomics
edyoshikun Jul 14, 2026
bfc1936
docs(dynaclr): witness-score LC DAG with flow diagrams and mock plots
edyoshikun Jul 14, 2026
361a8a5
fix(dynaclr): evaluate witness LC against ground-truth annotations
edyoshikun Jul 14, 2026
1d8bffa
config(dynaclr): add eval_against to witness LC recipe
edyoshikun Jul 14, 2026
1af4f03
docs(dynaclr): reflect annotation-based eval in witness DAG + visuals
edyoshikun Jul 14, 2026
faf741d
feat(dynaclr): add witness gating diagnostic page to LC summary PDF
edyoshikun Jul 15, 2026
0a17c20
feat(dynaclr): filter-based witness references + annotation-scored eval
edyoshikun Jul 15, 2026
b6015a9
fix(dynaclr): witness ROC/F1 plots use annotation-scored eval, not wi…
edyoshikun Jul 15, 2026
401a38a
feat(dynaclr): per-marker eval target for witness classifiers
edyoshikun Jul 15, 2026
b769bc0
feat(dynaclr): witness-GMM annotations replace the witness label source
edyoshikun Jul 17, 2026
d01e749
chore(dynaclr): untrack mock DAG visuals
edyoshikun Jul 17, 2026
68f3543
refactor(mmd): debloat MMD utilities
edyoshikun Jul 17, 2026
a3d7a5f
fix(triplet): reject non-positive pixel size in rescaling
edyoshikun Jul 17, 2026
9b0a114
fix(data): clear error when batch mixes FOV timepoint stats
edyoshikun Jul 17, 2026
4c6aa9f
fix(triplet): pre-resolve timepoint_statistics norm_meta to sample t
edyoshikun Jul 17, 2026
4a28b20
refactor(triplet): lazy-import viscy_transforms in rescale/z_reduction
edyoshikun Jul 17, 2026
7fa02de
feat(dynaclr): per-marker embedding-consistency QC (MMD + correlation…
edyoshikun Jul 22, 2026
10bea7b
docs(dynaclr): gate-free witness reference default + rationale
edyoshikun Jul 22, 2026
6306aa3
chore(ruff): ignore D and E501 under scripts/
edyoshikun Jul 22, 2026
06f6655
feat(dynaclr): occlusion-attribution report script for classifier eval
edyoshikun Jul 22, 2026
8549598
feat(dynaclr): add clustering PoP eval configs (infection/remodel/vel…
edyoshikun Jul 22, 2026
5b18944
feat(dynaclr): add embedding clustering proof-of-principle scripts
edyoshikun Jul 22, 2026
7cf6674
feat(dynaclr): MMD significance gate before witness-GMM labeling
edyoshikun Jul 22, 2026
f86b9e5
feat(dynaclr): add canonical per-marker prediction paths
edyoshikun Jul 22, 2026
b28e38a
feat(dynaclr): evaluate reusable dataset-scoped embeddings
edyoshikun Jul 22, 2026
85374c6
feat(dynaclr): add train-predict-eval model matrix launcher
edyoshikun Jul 22, 2026
cd03f60
config(dynaclr): add organelle-box remodeling matrix
edyoshikun Jul 22, 2026
1650293
feat(data): support parquet focus planes and cell-level splits
edyoshikun Jul 22, 2026
576c124
config(dynaclr): add Zuben gut preparation and training recipes
edyoshikun Jul 22, 2026
8192d75
docs(dynaclr): document run-once eval-many pipeline
edyoshikun Jul 22, 2026
111af49
feat(dynaclr): Benjamini-Yekutieli FDR control for the MMD significan…
edyoshikun Jul 22, 2026
87763f9
feat(viscy-utils): model-agnostic visualization module (occlusion + P…
edyoshikun Jul 22, 2026
1ac6b49
feat(dynaclr): expose occluder fill in occlusion report CONFIG
edyoshikun Jul 22, 2026
32bf4f0
Revert "config(dynaclr): add Zuben gut preparation and training recipes"
edyoshikun Jul 23, 2026
a405e30
Revert "feat(data): support parquet focus planes and cell-level splits"
edyoshikun Jul 23, 2026
6ac217e
qc for the embeddings via pearson and mmd
edyoshikun Jul 23, 2026
1897b8f
plotting for mmd
edyoshikun Jul 23, 2026
214b398
markdown cleanup
edyoshikun Jul 23, 2026
0396452
cleanp the markdowns dags
edyoshikun Jul 23, 2026
55b559d
feat(dynaclr): witness-GMM label provenance + diagnostic hardening
edyoshikun Jul 23, 2026
7fe82c1
remove eval matrix from gitignore
edyoshikun Jul 24, 2026
7498c50
feat(dynaclr): time-matched MMD-vs-HPI diagnostic for witness-GMM
edyoshikun Jul 24, 2026
f6018f9
feat(dynaclr): control-anchored gate for weak-channel witness labels
edyoshikun Jul 24, 2026
a63d374
fix(dynaclr): make LOT MMD comparisons consistent
edyoshikun Jul 24, 2026
13bb401
fix(dynaclr): repeat marker options in batch prediction
edyoshikun Jul 24, 2026
c854754
fix(dynaclr): forward matrix prediction settings
edyoshikun Jul 24, 2026
7f3ceca
fix(viscy-data): resize rescaled patches exactly
edyoshikun Jul 27, 2026
b9952de
fix(dynaclr): make LOT output replacement safe
edyoshikun Jul 27, 2026
89c17a0
refactor(viscy-utils): unify LC train/val split behind one helper
edyoshikun Jul 27, 2026
eb970dc
feat(dynaclr): witness-GMM phase-classifier configs + driver
edyoshikun Jul 27, 2026
5ff2c28
feat(dynaclr): witness-GMM diagnostic plots (AUROC, prob grids, %-rem…
edyoshikun Jul 27, 2026
7b03e1b
fix(data): normalize MIP depth across z sampling
edyoshikun Jul 28, 2026
d89f9ed
feat(dynaclr): nest embedding zarrs under {ckpt}/embeddings/
edyoshikun Jul 28, 2026
37e3312
feat(dynaclr): predict-triplet foundation models, focus-centered Z, o…
edyoshikun Jul 28, 2026
1fd4def
feat(dynaclr): matrix foundation rows + skip-existing progressive col…
edyoshikun Jul 28, 2026
1c6455d
feat(dynaclr): organelle_remodeling collection + DynaCLR/MorphEm matrix
edyoshikun Jul 28, 2026
f676a9a
docs(dynaclr): un-defer 04_21 in organelle_remodeling collection desc…
edyoshikun Jul 28, 2026
655a18f
feat(qc): audit_focus_slice — flag suspect focus detections (Z-aware)
edyoshikun Jul 28, 2026
3f2a500
fix(dynaclr): iter_embeddings glob missing embeddings/ segment
edyoshikun Jul 31, 2026
3ec6934
feat(dynaclr): embedding-consistency-qc v1/v2 split, Fréchet, HPI poo…
edyoshikun Jul 31, 2026
7aa2c3d
feat(dynaclr): microscope field + mock→uninfected on organelle collec…
edyoshikun Jul 31, 2026
e3fd8f7
fix(dynaclr): fixed Pearson-r heatmap scale (vmin=0.8) across markers
edyoshikun Jul 31, 2026
d3af40e
feat(viscy-utils): per-plate HPI-binned control-reference normalization
edyoshikun Aug 3, 2026
13d0547
docs(viscy-utils): when to use control-reference normalization
edyoshikun Aug 4, 2026
8ea80b7
feat(dynaclr): optional control-reference normalization in linear cla…
edyoshikun Aug 4, 2026
79a2634
Revert "docs(viscy-utils): when to use control-reference normalization"
edyoshikun Aug 4, 2026
f719022
feat(dynaclr): expand organelle remodeling matrix
edyoshikun Aug 7, 2026
2abcc52
feat(dynaclr): normalize biological MMD representations
edyoshikun Aug 12, 2026
2150116
feat(dynaclr): persist normalized PCA representations
edyoshikun Aug 12, 2026
c7c889d
docs(dynaclr): clarify pooled PCA balancing
edyoshikun Aug 13, 2026
d719a66
feat(dynaclr): promote pooled witness TCN stack
edyoshikun Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Sample triplet predict config: feed a 3D OME-Zarr to a 2D model.
#
# Demonstrates the TripletDataModule options that avoid materializing a separate
# 2D MIP dataset:
# - z_reduction: collapse the extracted z_range window to a single slice
# (label-free channels -> center slice, others -> max projection).
# - reference_pixel_size: rescale each patch to the model's training pixel size
# when the inference dataset was acquired at a different magnification.
#
# See docs/DAGs/inference_triplet.md for the full pipeline.
#
# TODO: point to the path to save the embeddings
# TODO: point to the path to the data
# TODO: point to the path to the tracks
# TODO: point to the path to the checkpoint

seed_everything: 42
trainer:
accelerator: gpu
strategy: auto
devices: 1
num_nodes: 1
precision: 32-true
inference_mode: true
logger: false
callbacks:
- class_path: lightning.pytorch.callbacks.TQDMProgressBar
init_args:
refresh_rate: 10
- class_path: viscy_utils.callbacks.embedding_writer.EmbeddingWriter
init_args:
output_path: #TODO point to the path to save the embeddings (e.g. /embeddings/embeddings.zarr)
embedding_key: features # "projections" for frozen-backbone MLP heads
overwrite: true
pca_kwargs: null # reductions left to a later step (dynaclr reduce-dimensionality)
phate_kwargs: null
umap_kwargs: null
model:
class_path: dynaclr.engine.ContrastiveModule
init_args:
encoder:
class_path: viscy_models.contrastive.ContrastiveEncoder
init_args:
backbone: convnext_tiny
in_channels: 1
in_stack_depth: 1 # 2D model — pairs with data.z_reduction below
stem_kernel_size: [1, 4, 4]
stem_stride: [1, 4, 4]
embedding_dim: 768
projection_dim: 32
drop_path_rate: 0.0 # stochastic depth off at inference
example_input_array_shape: [1, 1, 1, 160, 160]
data:
class_path: viscy_data.triplet.TripletDataModule
init_args:
data_path: #TODO point to the path to the data (e.g. /registered_test.zarr)
tracks_path: #TODO point to the path to the tracks (e.g. /track_test.zarr)
source_channel:
- Phase3D
z_range: [15, 45] # 3D window; collapsed to 1 slice by z_reduction.
# Center it on the focus plane to control which planes collapse.
z_reduction: mip # "mip" (max projection) or "center" (center slice).
# Label-free channels always take the center slice;
# others are max-projected. This sets the fallback only.
reference_pixel_size: 0.1494 # µm/px of the model's TRAINING dataset. Remove (or set
# null) when the inference dataset is at the same resolution.
initial_yx_patch_size: [160, 160] # ignored when reference_pixel_size is set (computed from ratio)
final_yx_patch_size: [160, 160] # patch size fed to the model after rescale + z_reduction
batch_size: 32
num_workers: 0 # REQUIRED for predict (avoids zarr-fork deadlock)
normalizations:
- class_path: viscy_transforms.NormalizeSampled
init_args:
keys: [Phase3D]
level: fov_statistics
subtrahend: mean
divisor: std
# augmentations omitted: predict must be deterministic. The datamodule still
# applies normalizations + reference_pixel_size rescale + z_reduction at predict time.
return_predictions: false
ckpt_path: #TODO point to the path to the checkpoint (e.g. /checkpoints/epoch=94-step=2375.ckpt)
185 changes: 185 additions & 0 deletions applications/dynaclr/docs/DAGs/inference_triplet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Inference DAG (Triplet path)

Embedding inference for a trained DynaCLR encoder using `TripletDataModule` —
the **zarr + tracking** path (no parquet). Use this when you want to run a
trained checkpoint directly over an OME-Zarr store and its `ultrack` tracking,
rather than the parquet-first `MultiExperimentDataModule` path
(see [evaluation.md](evaluation.md) for the parquet path).

The triplet path is the one that carries the patch-rescaling
(`reference_pixel_size`) and on-the-fly Z-reduction (`z_reduction`) options, so
a 3D zarr can feed a 2D model without materializing a separate MIP dataset.

## Prerequisites

- A trained checkpoint (`last.ckpt` or a selected epoch) for a
`dynaclr.engine.ContrastiveModule`.
- The inference dataset as an OME-Zarr store with `normalization` metadata in
the FOV `zattrs` (so `NormalizeSampled` has per-FOV stats), plus a tracking
zarr/CSV directory with `track_id, t, y, x` columns.
- The model's training pixel size (µm/px) if the inference dataset was acquired
at a different magnification — passed as `reference_pixel_size` to rescale
each patch to the physical area the model was trained on.

## Step-by-step detail

```
dataset.zarr (preprocessed: normalization in FOV zattrs)
tracking.zarr/CSV (track_id, t, y, x per cell)
checkpoint.ckpt (trained ContrastiveModule)
├──► predict config (TripletDataModule + ContrastiveModule + EmbeddingWriter)
viscy predict --config configs/prediction/predict_triplet.yml
│ TripletDataModule(fit=False): samples ONE anchor patch per (cell, timepoint)
│ - extracts z_range window, yx at initial_yx_patch_size
│ - reference_pixel_size → extract larger patch, BatchedZoomd to final_yx
│ - z_reduction → BatchedChannelWiseZReductiond collapses Z to 1 (2D model)
│ ContrastiveModule.predict_step → backbone features (+ projections)
│ EmbeddingWriter accumulates (features, index) and writes one combined store
embeddings.zarr (AnnData: obsm["X_backbone"], obs = fov_name/track_id/t/...)
dynaclr split-embeddings --input embeddings.zarr --output-dir embeddings/
│ groups rows by obs["experiment"], writes one zarr per experiment
│ removes the combined store afterwards
embeddings/{experiment}.zarr (one per experiment, informatively named)
downstream eval (reduce-dimensionality, linear classifiers, MMD, pseudotime …)
see evaluation.md / pseudotime.md
```

## Pipeline DAG (process dependency)

```
predict config + checkpoint + zarr + tracking
viscy predict (GPU, minutes–hours by cell count)
split-embeddings (CPU, ~1 min, I/O bound)
downstream eval (CPU/GPU, per analysis)
```

## Key commands

| Step | Command | Input | Output |
| ---------------- | --------------------------------------------------------------------------- | --------------------------------------- | ----------------------------------- |
| Predict | `uv run viscy predict --config configs/prediction/predict_triplet.yml` | predict config + ckpt + zarr + tracking | combined `embeddings.zarr` |
| Predict (SLURM) | `sbatch configs/prediction/predict_triplet.sh` | same | combined `embeddings.zarr` |
| Split embeddings | `dynaclr split-embeddings --input embeddings.zarr --output-dir embeddings/` | combined zarr with `obs["experiment"]` | one `{experiment}.zarr` per dataset |

## What lives where

| Data | Location | When written |
| --------------------------------- | ------------------------------------------------- | --------------------- |
| Pixel data (TCZYX) | dataset.zarr on VAST | data prep |
| Cell tracks (track_id, t, y, x) | tracking.zarr / CSV on VAST | data prep |
| Normalization stats (per FOV) | dataset.zarr FOV `zattrs["normalization"]` | `viscy preprocess` |
| Backbone embeddings | `embeddings.zarr` → `obsm["X_backbone"]` | `viscy predict` |
| Cell index (fov_name/track_id/t) | `embeddings.zarr` → `obs` | `viscy predict` |
| Per-experiment embeddings | `embeddings/{experiment}.zarr` | `split-embeddings` |

## Predict config structure

A ready-to-edit sample lives at
[`configs/prediction/predict_triplet_2d_from_3d.yml`](../../configs/prediction/predict_triplet_2d_from_3d.yml)
(the 2D-from-3D case, with `z_reduction` + `reference_pixel_size`). The skeleton
below annotates the load-bearing fields:

```yaml
seed_everything: 42

trainer:
accelerator: gpu
devices: 1
precision: 32-true
inference_mode: true
logger: false
callbacks:
- class_path: viscy_utils.callbacks.embedding_writer.EmbeddingWriter
init_args:
output_path: /path/to/embeddings/embeddings.zarr
embedding_key: features # "projections" for frozen-backbone MLP heads
overwrite: true

model:
class_path: dynaclr.engine.ContrastiveModule
init_args:
encoder:
class_path: viscy_models.contrastive.ContrastiveEncoder
init_args:
backbone: convnext_tiny
in_channels: 1
in_stack_depth: 1 # 2D model — pair with z_reduction below
# … must match the trained checkpoint's encoder args …

data:
class_path: viscy_data.TripletDataModule
init_args:
data_path: /path/to/dataset.zarr
tracks_path: /path/to/tracking.zarr
source_channel: [Phase3D]
z_range: [0, 16] # window collapsed by z_reduction
final_yx_patch_size: [160, 160]
reference_pixel_size: 0.1494 # rescale to the model's training pixel size (optional)
z_reduction: mip # collapse z_range to 1 slice for a 2D model (optional)
batch_size: 400
num_workers: 0 # REQUIRED for predict (see Notes)
predict_cells: false # true + include_fov_names/include_track_ids to subset
normalizations:
- class_path: viscy_transforms.NormalizeSampled
init_args:
keys: [Phase3D]
subtrahend: mean
divisor: std
augmentations: [] # MUST be empty for deterministic predict

ckpt_path: /path/to/checkpoint/last.ckpt
return_predictions: false # writer persists to zarr; don't hold in memory
```

## Notes

- **`num_workers: 0` is required for the predict path.** `HCSDataModule`/
`TripletDataModule` predict does not use `mmap_preload`, and >0 workers risks a
zarr-fork deadlock. This matches the dynacell predict overlay.
- **`augmentations: []`** — predict must be deterministic. The datamodule still
applies `normalizations` (and the `reference_pixel_size` rescale + `z_reduction`
collapse) at predict time via `_no_augmentation_transform`; only random
augmentations are dropped.
- **2D from 3D without a MIP dataset.** Set `z_reduction: mip` (or `center`) to
collapse the extracted `z_range` window to a single slice. Label-free channels
(resolved by name via `parse_channel_name`) take the center slice; all other
channels are max-projected. Pair with `in_stack_depth: 1` on the encoder.
Center the `z_range` on the focus plane to control which planes are collapsed.
- **Pixel-size rescaling.** When the inference dataset's pixel size differs from
the model's training pixel size, set `reference_pixel_size` (µm/px) so a larger
patch covering the same physical area is extracted and bilinearly resized to
`final_yx_patch_size`. Leave unset for same-resolution datasets.
- **`embedding_key`.** Use `features` for the backbone output (most models) and
`projections` for frozen-backbone MLP-head models, which writes
`obsm["X_projections"]` instead.
- **`split-embeddings` requires `obs["experiment"]`** on the combined store. For a
single-experiment predict run the split step is optional — the combined
`embeddings.zarr` is already per-experiment.
- Downstream analyses (dimensionality reduction, linear classifiers, MMD,
pseudotime) consume the per-experiment zarrs and are documented in
[evaluation.md](evaluation.md) and [pseudotime.md](pseudotime.md).

## Triplet vs parquet (MultiExperimentDataModule)

| Aspect | Triplet path (this doc) | Parquet path (evaluation.md) |
| ------------------- | ------------------------------------------------ | -------------------------------------------------- |
| Data entry point | `data_path` zarr + `tracks_path` | `cell_index.parquet` (built + preprocessed) |
| Setup cost | reads tracking + zarr shape at init | reads parquet only at init |
| Focus / z window | caller sets `z_range`; `z_reduction` collapses | per-FOV `z_extraction_window` from `focus_slice` |
| 2D-from-3D | `z_reduction: mip` / `center` | `BatchedChannelWiseZReductiond` in normalizations |
Comment thread
Copilot marked this conversation as resolved.
Outdated
| Pixel rescaling | `reference_pixel_size` | `reference_pixel_size_xy_um` |
| Best for | ad-hoc predict over a single zarr + tracking | large multi-experiment runs, reproducible recipes |
2 changes: 2 additions & 0 deletions applications/dynaclr/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ dependencies = [
optional-dependencies.eval = [
"anndata",
"dtaidistance",
"joblib",
"natsort",
"phate",
"pot",
"scikit-learn",
"statsmodels",
"umap-learn",
Expand Down
Loading
Loading