From 0b529ddcb472afba84476d9280f81f0fa425c4c7 Mon Sep 17 00:00:00 2001 From: Soorya Pradeep Date: Thu, 14 May 2026 09:54:44 -0700 Subject: [PATCH 01/89] Port scale-aware inference and LOT batch correction to modular-viscy-staging Scale-aware patch extraction (packages/viscy-data): - Add _read_pixel_size() helper to read X pixel size from OME-Zarr metadata - Add reference_pixel_size parameter to TripletDataModule: when set, computes initial_yx_patch_size from the pixel-size ratio so the same physical area is covered at inference time - Replace BatchedRescaleYXd (removed per review) with existing BatchedZoomd using scale_factor=(1.0, final_y/initial_y, final_x/initial_x) and mode="bilinear" with antialias; import is lazy to avoid a hard dep LOT batch correction (applications/dynaclr): - Add dynaclr.evaluation.lot_correction submodule with core logic (fit, apply, save, load), Pydantic configs, and Click CLI entry points - Register fit-lot-correction and apply-lot-correction in cli.py via LazyCommand - Add pot and joblib to optional-dependencies.eval in pyproject.toml Co-Authored-By: Claude Sonnet 4.6 --- applications/dynaclr/pyproject.toml | 2 + applications/dynaclr/src/dynaclr/cli.py | 16 + .../evaluation/lot_correction/__init__.py | 15 + .../lot_correction/apply_lot_correction.py | 84 ++++++ .../evaluation/lot_correction/config.py | 118 ++++++++ .../lot_correction/fit_lot_correction.py | 95 ++++++ .../lot_correction/lot_correction.py | 284 ++++++++++++++++++ packages/viscy-data/src/viscy_data/triplet.py | 64 +++- 8 files changed, 675 insertions(+), 3 deletions(-) create mode 100644 applications/dynaclr/src/dynaclr/evaluation/lot_correction/__init__.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py diff --git a/applications/dynaclr/pyproject.toml b/applications/dynaclr/pyproject.toml index 4cce8f180..ab36b670e 100644 --- a/applications/dynaclr/pyproject.toml +++ b/applications/dynaclr/pyproject.toml @@ -53,8 +53,10 @@ dependencies = [ optional-dependencies.eval = [ "anndata", "dtaidistance", + "joblib", "natsort", "phate", + "pot", "scikit-learn", "statsmodels", "umap-learn", diff --git a/applications/dynaclr/src/dynaclr/cli.py b/applications/dynaclr/src/dynaclr/cli.py index 40d845fdc..30f28b322 100644 --- a/applications/dynaclr/src/dynaclr/cli.py +++ b/applications/dynaclr/src/dynaclr/cli.py @@ -264,6 +264,22 @@ def dynaclr(): ) ) +dynaclr.add_command( + LazyCommand( + name="fit-lot-correction", + import_path="dynaclr.evaluation.lot_correction.fit_lot_correction.main", + short_help="Fit a LOT batch-correction pipeline on source and target embedding zarrs", + ) +) + +dynaclr.add_command( + LazyCommand( + name="apply-lot-correction", + import_path="dynaclr.evaluation.lot_correction.apply_lot_correction.main", + short_help="Apply a fitted LOT pipeline to correct batch effects in an embedding zarr", + ) +) + def main(): """Run the DynaCLR CLI. diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/__init__.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/__init__.py new file mode 100644 index 000000000..e08fbd0f6 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/__init__.py @@ -0,0 +1,15 @@ +"""LOT (Linear Optimal Transport) batch correction for embedding zarrs.""" + +from dynaclr.evaluation.lot_correction.lot_correction import ( + apply_lot_correction, + fit_lot_correction, + load_lot_pipeline, + save_lot_pipeline, +) + +__all__ = [ + "fit_lot_correction", + "apply_lot_correction", + "save_lot_pipeline", + "load_lot_pipeline", +] diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py new file mode 100644 index 000000000..f42085454 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py @@ -0,0 +1,84 @@ +"""CLI for applying a fitted LOT pipeline to an embedding zarr. + +Usage +----- + dynaclr apply-lot-correction -c config.yaml + +Transforms all cells through StandardScaler → PCA → LOT and writes a new +zarr whose ``.X`` contains the corrected embeddings (shape n_cells × n_pca). +All ``.obs`` metadata from the input zarr is preserved. + +Example config (YAML) +--------------------- + input_zarr: /path/to/lightsheet_organelle.zarr + pipeline: /path/to/lot_pipeline.pkl + output_zarr: /path/to/corrected_organelle.zarr + overwrite: false +""" + +import logging +from pathlib import Path + +import click +from pydantic import ValidationError + +from dynaclr.evaluation.lot_correction.config import LotApplyConfig +from dynaclr.evaluation.lot_correction.lot_correction import ( + apply_lot_correction, + load_lot_pipeline, +) +from viscy_utils.cli_utils import load_config + +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "-c", + "--config", + type=click.Path(exists=True, path_type=Path), + required=True, + help="Path to YAML configuration file.", +) +def main(config: Path): + """Apply a fitted LOT pipeline to correct batch effects in an embedding zarr.""" + click.echo("=" * 60) + click.echo("LOT BATCH CORRECTION — APPLY") + click.echo("=" * 60) + + try: + config_dict = load_config(config) + apply_config = LotApplyConfig(**config_dict) + except ValidationError as e: + click.echo(f"\nConfiguration validation failed:\n{e}", err=True) + raise click.Abort() + except Exception as e: + click.echo(f"\nFailed to load configuration: {e}", err=True) + raise click.Abort() + + click.echo(f"\nConfiguration loaded: {config}") + click.echo(f" Input zarr: {apply_config.input_zarr}") + click.echo(f" Pipeline: {apply_config.pipeline}") + click.echo(f" Output zarr: {apply_config.output_zarr}") + click.echo(f" Overwrite: {apply_config.overwrite}") + + try: + pipeline = load_lot_pipeline(apply_config.pipeline) + click.echo( + f"\nPipeline loaded — n_pca={pipeline['n_pca']}, " + f"PCA variance={pipeline.get('pca_variance_explained', float('nan')):.1f}%" + ) + apply_lot_correction( + input_zarr=apply_config.input_zarr, + pipeline=pipeline, + output_zarr=apply_config.output_zarr, + overwrite=apply_config.overwrite, + ) + click.echo(f"\nCorrected zarr written to: {apply_config.output_zarr}") + except Exception as e: + click.echo(f"\nApplication failed: {e}", err=True) + raise click.Abort() + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py new file mode 100644 index 000000000..c3c899194 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py @@ -0,0 +1,118 @@ +"""Pydantic configuration models for LOT batch correction.""" + +from pathlib import Path +from typing import Optional, Union + +from pydantic import BaseModel, Field, model_validator + + +class UninfFilter(BaseModel): + """Specification for selecting uninfected reference cells from an obs table. + + Exactly one of ``startswith`` or ``equals`` must be provided. + + Parameters + ---------- + column : str + Name of the ``.obs`` column to filter on (e.g. ``"fov_name"``). + startswith : str or list[str], optional + Keep cells whose column value starts with any of these prefixes. + equals : str, optional + Keep cells whose column value equals this string. + """ + + column: str = Field(..., min_length=1) + startswith: Optional[Union[str, list[str]]] = Field(default=None) + equals: Optional[str] = Field(default=None) + + @model_validator(mode="after") + def exactly_one_filter(self): + has_sw = self.startswith is not None + has_eq = self.equals is not None + if not has_sw and not has_eq: + raise ValueError("UninfFilter must specify either 'startswith' or 'equals'.") + if has_sw and has_eq: + raise ValueError("UninfFilter must specify only one of 'startswith' or 'equals'.") + return self + + def to_dict(self) -> dict: + """Convert to the dict format expected by _apply_filter.""" + d = {"column": self.column} + if self.startswith is not None: + d["startswith"] = self.startswith + else: + d["equals"] = self.equals + return d + + +class LotFitConfig(BaseModel): + """Configuration for fitting a LOT batch-correction pipeline. + + Parameters + ---------- + source_zarr : str + Path to the source AnnData zarr (e.g. light-sheet embeddings). + target_zarr : str + Path to the target AnnData zarr (e.g. confocal embeddings). + source_uninf_filter : UninfFilter + Filter identifying uninfected cells in the source dataset. + target_uninf_filter : UninfFilter + Filter identifying uninfected cells in the target dataset. + n_pca : int, optional + Number of PCA components for the shared PCA, by default 50. + ns_lot : int, optional + Maximum cells subsampled per dataset for LOT fitting, by default 3000. + random_seed : int, optional + Random seed, by default 42. + output_pipeline : str + Path to save the fitted pipeline (joblib pickle). + """ + + source_zarr: str = Field(..., min_length=1) + target_zarr: str = Field(..., min_length=1) + source_uninf_filter: UninfFilter + target_uninf_filter: UninfFilter + n_pca: int = Field(default=50, gt=0) + ns_lot: int = Field(default=3000, gt=0) + random_seed: int = Field(default=42) + output_pipeline: str = Field(..., min_length=1) + + @model_validator(mode="after") + def validate_paths(self): + if not Path(self.source_zarr).exists(): + raise ValueError(f"source_zarr not found: {self.source_zarr}") + if not Path(self.target_zarr).exists(): + raise ValueError(f"target_zarr not found: {self.target_zarr}") + return self + + +class LotApplyConfig(BaseModel): + """Configuration for applying a fitted LOT pipeline to a zarr. + + Parameters + ---------- + input_zarr : str + Path to the source AnnData zarr to correct. + pipeline : str + Path to the fitted pipeline file (joblib pickle). + output_zarr : str + Path to write the corrected AnnData zarr. + overwrite : bool, optional + Overwrite output if it exists, by default False. + """ + + input_zarr: str = Field(..., min_length=1) + pipeline: str = Field(..., min_length=1) + output_zarr: str = Field(..., min_length=1) + overwrite: bool = Field(default=False) + + @model_validator(mode="after") + def validate_paths(self): + if not Path(self.input_zarr).exists(): + raise ValueError(f"input_zarr not found: {self.input_zarr}") + if not Path(self.pipeline).exists(): + raise ValueError(f"pipeline file not found: {self.pipeline}") + output = Path(self.output_zarr) + if output.exists() and not self.overwrite: + raise ValueError(f"output_zarr already exists: {self.output_zarr}. Set overwrite: true to overwrite.") + return self diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py new file mode 100644 index 000000000..7ecff69b5 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py @@ -0,0 +1,95 @@ +"""CLI for fitting a LOT batch-correction pipeline on embedding zarrs. + +Usage +----- + dynaclr fit-lot-correction -c config.yaml + +The fitted pipeline (StandardScaler + PCA + LinearTransport) is saved to +the path specified by ``output_pipeline`` in the config file. + +Example config (YAML) +--------------------- + source_zarr: /path/to/lightsheet_organelle.zarr + target_zarr: /path/to/confocal_organelle.zarr + source_uninf_filter: + column: fov_name + startswith: + - "C/1/" + target_uninf_filter: + column: fov_name + startswith: + - "G3BP1/uninfected" + n_pca: 50 + ns_lot: 3000 + random_seed: 42 + output_pipeline: /path/to/lot_pipeline.pkl +""" + +import logging +from pathlib import Path + +import click +from pydantic import ValidationError + +from dynaclr.evaluation.lot_correction.config import LotFitConfig +from dynaclr.evaluation.lot_correction.lot_correction import ( + fit_lot_correction, + save_lot_pipeline, +) +from viscy_utils.cli_utils import load_config + +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "-c", + "--config", + type=click.Path(exists=True, path_type=Path), + required=True, + help="Path to YAML configuration file.", +) +def main(config: Path): + """Fit a LOT batch-correction pipeline on source and target embedding zarrs.""" + click.echo("=" * 60) + click.echo("LOT BATCH CORRECTION — FIT") + click.echo("=" * 60) + + try: + config_dict = load_config(config) + fit_config = LotFitConfig(**config_dict) + except ValidationError as e: + click.echo(f"\nConfiguration validation failed:\n{e}", err=True) + raise click.Abort() + except Exception as e: + click.echo(f"\nFailed to load configuration: {e}", err=True) + raise click.Abort() + + click.echo(f"\nConfiguration loaded: {config}") + click.echo(f" Source zarr: {fit_config.source_zarr}") + click.echo(f" Target zarr: {fit_config.target_zarr}") + click.echo(f" n_pca: {fit_config.n_pca}") + click.echo(f" ns_lot: {fit_config.ns_lot}") + click.echo(f" Random seed: {fit_config.random_seed}") + click.echo(f" Output: {fit_config.output_pipeline}") + + try: + pipeline = fit_lot_correction( + source_zarr=fit_config.source_zarr, + target_zarr=fit_config.target_zarr, + source_uninf_filter=fit_config.source_uninf_filter.to_dict(), + target_uninf_filter=fit_config.target_uninf_filter.to_dict(), + n_pca=fit_config.n_pca, + ns_lot=fit_config.ns_lot, + random_seed=fit_config.random_seed, + ) + click.echo(f"\nPipeline fitted — PCA explained variance: {pipeline['pca_variance_explained']:.1f}%") + save_lot_pipeline(pipeline, fit_config.output_pipeline) + click.echo(f"Pipeline saved to: {fit_config.output_pipeline}") + except Exception as e: + click.echo(f"\nFitting failed: {e}", err=True) + raise click.Abort() + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py new file mode 100644 index 000000000..bc56fed34 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py @@ -0,0 +1,284 @@ +"""Core functions for LOT (Linear Optimal Transport) batch correction. + +Pipeline +-------- +1. Load source and target embedding zarrs (AnnData format). +2. Filter cells to the uninfected reference population in each dataset. +3. Fit a shared StandardScaler + PCA on the combined source + target cells. +4. Fit a LinearTransport (LOT) map in PCA space using uninfected cells only, + mapping source → target distribution. +5. Save the fitted pipeline (scaler, PCA, LOT) to disk with joblib. + +The saved pipeline can then be applied to any source zarr to produce a new +zarr whose embeddings are in the target's PCA coordinate system, corrected +for cross-platform batch effects. +""" + +import logging +from pathlib import Path +from typing import Union + +import anndata as ad +import joblib +import numpy as np +import ot +from sklearn.decomposition import PCA +from sklearn.preprocessing import StandardScaler + +_logger = logging.getLogger(__name__) + + +def _to_np(X) -> np.ndarray: + """Convert sparse or dense matrix to float32 numpy array.""" + return np.array(X.toarray() if hasattr(X, "toarray") else X, dtype=np.float32) + + +def _apply_filter(obs, filter_spec: dict) -> np.ndarray: + """Return a boolean mask for rows of *obs* matching *filter_spec*. + + Parameters + ---------- + obs : pd.DataFrame + AnnData ``.obs`` table. + filter_spec : dict + Must contain ``"column"`` plus one of: + + * ``"startswith"`` – str or list[str]: keep rows where the column + value starts with any of the given prefixes. + * ``"equals"`` – str: keep rows where the column value equals the + given string. + + Returns + ------- + np.ndarray of bool + Boolean mask with the same length as *obs*. + """ + col = filter_spec["column"] + values = obs[col].astype(str) + + if "startswith" in filter_spec: + prefixes = filter_spec["startswith"] + if isinstance(prefixes, str): + prefixes = [prefixes] + mask = np.zeros(len(obs), dtype=bool) + for p in prefixes: + mask |= values.str.startswith(p).values + return mask + + if "equals" in filter_spec: + return (values == str(filter_spec["equals"])).values + + raise ValueError(f"filter_spec must contain either 'startswith' or 'equals'. Got: {list(filter_spec.keys())}") + + +def fit_lot_correction( + source_zarr: Union[str, Path], + target_zarr: Union[str, Path], + source_uninf_filter: dict, + target_uninf_filter: dict, + n_pca: int = 50, + ns_lot: int = 3000, + random_seed: int = 42, +) -> dict: + """Fit a shared PCA + LOT batch-correction pipeline. + + Parameters + ---------- + source_zarr : str or Path + Path to the source AnnData zarr (e.g. light-sheet embeddings). + target_zarr : str or Path + Path to the target AnnData zarr (e.g. confocal embeddings). + source_uninf_filter : dict + Filter spec selecting uninfected source cells used to fit LOT. + target_uninf_filter : dict + Filter spec selecting uninfected target cells used to fit LOT. + n_pca : int, optional + Number of PCA components, by default 50. + ns_lot : int, optional + Maximum number of cells subsampled per dataset for LOT fitting, + by default 3000. + random_seed : int, optional + Random seed for reproducibility, by default 42. + + Returns + ------- + dict with keys ``"scaler"``, ``"pca"``, ``"lot"``, ``"n_pca"``, + ``"ns_lot"``, ``"random_seed"``, ``"pca_variance_explained"``. + """ + rng = np.random.default_rng(random_seed) + + _logger.info("Loading source zarr: %s", source_zarr) + adata_src = ad.read_zarr(source_zarr) + adata_src.obs_names_make_unique() + + _logger.info("Loading target zarr: %s", target_zarr) + adata_tgt = ad.read_zarr(target_zarr) + adata_tgt.obs_names_make_unique() + + _logger.info("Source shape: %s Target shape: %s", adata_src.shape, adata_tgt.shape) + + X_src = _to_np(adata_src.X) + X_tgt = _to_np(adata_tgt.X) + + src_uninf_mask = _apply_filter(adata_src.obs, source_uninf_filter) + tgt_uninf_mask = _apply_filter(adata_tgt.obs, target_uninf_filter) + + _logger.info( + "Uninfected cells — source: %d / %d, target: %d / %d", + src_uninf_mask.sum(), + len(X_src), + tgt_uninf_mask.sum(), + len(X_tgt), + ) + + if src_uninf_mask.sum() < 5 or tgt_uninf_mask.sum() < 5: + raise ValueError( + "Too few uninfected cells to fit LOT " + f"(source={src_uninf_mask.sum()}, target={tgt_uninf_mask.sum()}). " + "Check your filter specifications." + ) + + _logger.info("Fitting shared StandardScaler + PCA-%d ...", n_pca) + scaler = StandardScaler() + X_combined_scaled = scaler.fit_transform(np.vstack([X_src, X_tgt])) + pca = PCA(n_components=n_pca, random_state=random_seed) + Z_all = pca.fit_transform(X_combined_scaled) + var_exp = pca.explained_variance_ratio_.sum() * 100 + _logger.info("PCA explained variance: %.1f%%", var_exp) + + n_src = len(X_src) + Z_src_uninf = Z_all[:n_src][src_uninf_mask] + Z_tgt_uninf = Z_all[n_src:][tgt_uninf_mask] + + ns_src = min(len(Z_src_uninf), ns_lot) + ns_tgt = min(len(Z_tgt_uninf), ns_lot) + idx_src = rng.choice(len(Z_src_uninf), ns_src, replace=False) + idx_tgt = rng.choice(len(Z_tgt_uninf), ns_tgt, replace=False) + + _logger.info("Fitting LOT (source subsample=%d, target subsample=%d) ...", ns_src, ns_tgt) + lot = ot.da.LinearTransport(reg=1e-3) + lot.fit(Xs=Z_src_uninf[idx_src], Xt=Z_tgt_uninf[idx_tgt]) + _logger.info("LOT fitted.") + + return { + "scaler": scaler, + "pca": pca, + "lot": lot, + "n_pca": n_pca, + "ns_lot": ns_lot, + "random_seed": random_seed, + "pca_variance_explained": float(var_exp), + } + + +def apply_lot_correction( + input_zarr: Union[str, Path], + pipeline: dict, + output_zarr: Union[str, Path], + overwrite: bool = False, +) -> None: + """Apply a fitted LOT pipeline to an embedding zarr. + + Transforms all cells through StandardScaler → PCA → LOT and writes an + AnnData zarr whose ``.X`` contains the corrected embeddings in the + target's PCA space. All ``.obs`` metadata is preserved. + + Parameters + ---------- + input_zarr : str or Path + Path to the source AnnData zarr to correct. + pipeline : dict + Fitted pipeline as returned by :func:`fit_lot_correction`. + output_zarr : str or Path + Path to write the corrected AnnData zarr. + overwrite : bool, optional + If ``False`` (default) and *output_zarr* already exists, raise. + """ + import shutil + + import pandas as pd + + output_zarr = Path(output_zarr) + if output_zarr.exists(): + if not overwrite: + raise FileExistsError(f"Output path already exists: {output_zarr}. Set overwrite=true to overwrite.") + shutil.rmtree(output_zarr) + + _logger.info("Loading input zarr: %s", input_zarr) + adata_in = ad.read_zarr(input_zarr) + adata_in.obs_names_make_unique() + + X = _to_np(adata_in.X) + _logger.info("Input shape: %s", adata_in.shape) + + scaler = pipeline["scaler"] + pca = pipeline["pca"] + lot = pipeline["lot"] + + _logger.info("Applying StandardScaler → PCA → LOT ...") + Z = pca.transform(scaler.transform(X)) + Z_corrected = lot.transform(Z) + _logger.info("Corrected embeddings shape: %s (n_pca=%d)", Z_corrected.shape, pipeline["n_pca"]) + + obs = adata_in.obs.copy() + for col in obs.columns: + dtype = obs[col].dtype + if isinstance(dtype, pd.StringDtype): + obs[col] = obs[col].astype(object) + elif isinstance(dtype, pd.CategoricalDtype) and isinstance(dtype.categories.dtype, pd.StringDtype): + obs[col] = obs[col].astype(object).astype("category") + + try: + ad.settings.allow_write_nullable_strings = True + except AttributeError: + pass + + adata_out = ad.AnnData(X=Z_corrected.astype(np.float32), obs=obs) + adata_out.uns["lot_correction"] = { + "source_zarr": str(input_zarr), + "n_pca": pipeline["n_pca"], + "pca_variance_explained": pipeline.get("pca_variance_explained"), + } + + _logger.info("Writing corrected zarr: %s", output_zarr) + adata_out.write_zarr(output_zarr) + _logger.info("Done.") + + +def save_lot_pipeline(pipeline: dict, path: Union[str, Path]) -> None: + """Save a fitted LOT pipeline to disk using joblib. + + Parameters + ---------- + pipeline : dict + Fitted pipeline as returned by :func:`fit_lot_correction`. + path : str or Path + Output path (e.g. ``lot_pipeline.pkl``). + """ + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + joblib.dump(pipeline, path) + _logger.info("Pipeline saved to %s", path) + + +def load_lot_pipeline(path: Union[str, Path]) -> dict: + """Load a fitted LOT pipeline from disk. + + Parameters + ---------- + path : str or Path + Path to the saved pipeline file. + + Returns + ------- + dict + Pipeline with keys ``"scaler"``, ``"pca"``, ``"lot"``. + """ + pipeline = joblib.load(path) + _logger.info( + "Pipeline loaded from %s (n_pca=%d, pca_var=%.1f%%)", + path, + pipeline["n_pca"], + pipeline.get("pca_variance_explained", float("nan")), + ) + return pipeline diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index deed9fe57..5f6592ff7 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -40,6 +40,25 @@ _logger = logging.getLogger("lightning.pytorch") +def _read_pixel_size(data_path: str | Path) -> float: + """Read the X pixel size (µm/pixel) from the first FOV in an OME-Zarr dataset. + + Parameters + ---------- + data_path : str | Path + Path to the OME-Zarr plate or position. + + Returns + ------- + float + X pixel size in micrometers per pixel. + """ + with open_ome_zarr(data_path, mode="r") as store: + for _, pos in store.positions(): + return float(pos.scale[-1]) + raise ValueError(f"No positions found in {data_path}") + + def _default_tensorstore_config(cache_pool_bytes: int = 0) -> TensorStoreConfig: """Build a TensorStoreConfig with SLURM-aware concurrency.""" cpus = os.environ.get("SLURM_CPUS_PER_TASK") @@ -316,6 +335,7 @@ def __init__( pin_memory: bool = False, z_window_size: int | None = None, cache_pool_bytes: int = 0, + reference_pixel_size: float | None = None, ): """Lightning data module for triplet sampling of patches. @@ -330,9 +350,20 @@ def __init__( z_range : tuple[int, int] Range of valid z-slices initial_yx_patch_size : tuple[int, int], optional - XY size of the initially sampled image patch, by default (512, 512) + YX size of the initially sampled image patch, by default (512, 512). + Ignored when ``reference_pixel_size`` is set — the patch size is then + computed automatically from the pixel-size ratio. final_yx_patch_size : tuple[int, int], optional Output patch size, by default (224, 224) + reference_pixel_size : float | None, optional + X pixel size (µm/pixel) of the dataset used to train the model. + When provided, reads the pixel size of the inference dataset from + its OME-Zarr metadata and computes + ``initial_yx_patch_size = round(final_yx_patch_size * + reference_pixel_size / inference_pixel_size)`` so that the same + physical area is covered. The extracted patch is then rescaled to + ``final_yx_patch_size`` with bilinear interpolation. By default + ``None`` (no rescaling). split_ratio : float, optional Ratio of training samples, by default 0.8 batch_size : int, optional @@ -409,8 +440,35 @@ def __init__( self.return_negative = return_negative self.augment_validation = augment_validation self._cache_pool_bytes = cache_pool_bytes - self._augmentation_transform = Compose(self.normalizations + self.augmentations) - self._no_augmentation_transform = Compose(self.normalizations) + if reference_pixel_size is not None: + inference_pixel_size = _read_pixel_size(data_path) + scale = reference_pixel_size / inference_pixel_size + self.initial_yx_patch_size = tuple(round(s * scale) for s in final_yx_patch_size) + _logger.info( + f"Pixel size rescaling enabled: " + f"reference={reference_pixel_size:.4f} µm/px, " + f"inference={inference_pixel_size:.4f} µm/px, " + f"scale={scale:.4f}. " + f"Extracting {self.initial_yx_patch_size} px patches " + f"and resizing to {final_yx_patch_size} px." + ) + from viscy_transforms import BatchedZoomd + + scale_yx = ( + final_yx_patch_size[0] / self.initial_yx_patch_size[0], + final_yx_patch_size[1] / self.initial_yx_patch_size[1], + ) + rescale_transform = BatchedZoomd( + keys=list(self.source_channel), + scale_factor=(1.0, *scale_yx), + mode="bilinear", + antialias=True, + ) + self._augmentation_transform = Compose(self.normalizations + self.augmentations + [rescale_transform]) + self._no_augmentation_transform = Compose(self.normalizations + [rescale_transform]) + else: + self._augmentation_transform = Compose(self.normalizations + self.augmentations) + self._no_augmentation_transform = Compose(self.normalizations) def _align_tracks_tables_with_positions( self, From 5e6407a2b741462fbc7405b37dbbf44a118a6681 Mon Sep 17 00:00:00 2001 From: Soorya Pradeep Date: Thu, 14 May 2026 09:58:11 -0700 Subject: [PATCH 02/89] Add visualization script for TripletDataModule scale-aware rescaling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plots n cell patches side-by-side: - Left: raw patch at initial_yx_patch_size (larger physical area) - Right: same patch bilinearly downscaled to final_yx_patch_size (model input) Both columns share the same percentile contrast window so differences are spatial. Physical scale (µm × µm) is shown in each panel title. Usage: python visualize_triplet_rescaling.py --data-path /path/to/data.zarr --tracks-path /path/to/tracks --source-channel Phase3D --z-range 0 5 --final-yx-patch-size 224 224 --reference-pixel-size 0.325 --output rescaling_comparison.png Co-Authored-By: Claude Sonnet 4.6 --- .../visualize_triplet_rescaling.py | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 applications/dynaclr/scripts/dataloader_inspection/visualize_triplet_rescaling.py diff --git a/applications/dynaclr/scripts/dataloader_inspection/visualize_triplet_rescaling.py b/applications/dynaclr/scripts/dataloader_inspection/visualize_triplet_rescaling.py new file mode 100644 index 000000000..75d4bb942 --- /dev/null +++ b/applications/dynaclr/scripts/dataloader_inspection/visualize_triplet_rescaling.py @@ -0,0 +1,253 @@ +r"""Visualize scale-aware patch rescaling in TripletDataModule. + +Loads a few cell patches from an OME-Zarr dataset using TripletDataModule +with ``reference_pixel_size`` set, then plots each patch in two columns: + + Left — raw patch at ``initial_yx_patch_size`` (larger physical area sampled + to match the reference pixel size) + Right — the same patch bilinearly downscaled to ``final_yx_patch_size`` + (what the model actually receives) + +Both columns use the same percentile-based grayscale contrast window so +that spatial content differences are visible rather than intensity shifts. +A physical-scale annotation (µm × µm) is printed below each patch. + +Usage:: + + python visualize_triplet_rescaling.py \\ + --data-path /path/to/data.zarr \\ + --tracks-path /path/to/tracks \\ + --source-channel Phase3D \\ + --z-range 0 5 \\ + --final-yx-patch-size 224 224 \\ + --reference-pixel-size 0.325 \\ + --n-samples 6 \\ + --output rescaling_comparison.png +""" + +import argparse +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import torch + +from viscy_data.triplet import TripletDataModule, _read_pixel_size + +# ── CLI ─────────────────────────────────────────────────────────────────────── + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments.""" + p = argparse.ArgumentParser( + description="Visualize TripletDataModule scale-aware patch rescaling.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + p.add_argument("--data-path", required=True, help="Path to OME-Zarr plate/position.") + p.add_argument("--tracks-path", required=True, help="Path to tracks CSV directory.") + p.add_argument( + "--source-channel", + required=True, + nargs="+", + help="Channel name(s) to load (e.g. Phase3D).", + ) + p.add_argument( + "--z-range", + required=True, + type=int, + nargs=2, + metavar=("Z_START", "Z_STOP"), + help="Z-slice range [start, stop).", + ) + p.add_argument( + "--final-yx-patch-size", + type=int, + nargs=2, + default=[224, 224], + metavar=("Y", "X"), + help="Target patch size fed to the model (pixels).", + ) + p.add_argument( + "--reference-pixel-size", + type=float, + required=True, + help="X pixel size (µm/px) of the model's training dataset.", + ) + p.add_argument( + "--n-samples", + type=int, + default=6, + help="Number of cell patches to visualize.", + ) + p.add_argument( + "--z-slice", + type=int, + default=None, + help="Z index within the patch to display. Defaults to middle slice.", + ) + p.add_argument( + "--output", + type=Path, + default=Path("rescaling_comparison.png"), + help="Output PNG path.", + ) + return p.parse_args() + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + + +def _percentile_norm(img: np.ndarray, lo: float = 1.0, hi: float = 99.0): + """Return (vmin, vmax) for percentile-based display.""" + vmin, vmax = np.percentile(img, [lo, hi]) + if vmax <= vmin: + vmax = vmin + 1.0 + return float(vmin), float(vmax) + + +def _rescale_yx( + patch: torch.Tensor, + target_yx: tuple[int, int], +) -> torch.Tensor: + """Bilinear-rescale YX of a (C, Z, H, W) patch to target_yx.""" + c, z, h, w = patch.shape + flat = patch.reshape(c * z, 1, h, w).float() + out = torch.nn.functional.interpolate( + flat, + size=target_yx, + mode="bilinear", + align_corners=False, + antialias=True, + ) + return out.reshape(c, z, *target_yx) + + +def _mid_slice(patch: torch.Tensor, z_idx: int | None) -> np.ndarray: + """Return a 2-D (H, W) numpy array from (C, Z, H, W), channel 0, given z.""" + z_size = patch.shape[1] + z = z_idx if z_idx is not None else z_size // 2 + z = max(0, min(z, z_size - 1)) + return patch[0, z].numpy() + + +# ── Main ────────────────────────────────────────────────────────────────────── + + +def main(): + """Run the visualization CLI.""" + args = parse_args() + + final_yx = tuple(args.final_yx_patch_size) + z_range = tuple(args.z_range) + + # Build the data module with scale-aware rescaling enabled. + dm = TripletDataModule( + data_path=args.data_path, + tracks_path=args.tracks_path, + source_channel=args.source_channel, + z_range=z_range, + final_yx_patch_size=final_yx, + reference_pixel_size=args.reference_pixel_size, + batch_size=args.n_samples, + num_workers=0, + ) + dm.setup("predict") + + inference_pixel_size = _read_pixel_size(args.data_path) + initial_yx = dm.initial_yx_patch_size + scale = args.reference_pixel_size / inference_pixel_size + + print( + f"Reference pixel size : {args.reference_pixel_size:.4f} µm/px\n" + f"Inference pixel size : {inference_pixel_size:.4f} µm/px\n" + f"Scale factor : {scale:.4f}\n" + f"initial_yx_patch_size: {initial_yx}\n" + f"final_yx_patch_size : {final_yx}\n" + ) + + # Draw samples directly from the dataset (raw, before any transforms). + n = min(args.n_samples, len(dm.predict_dataset)) + raw_batch = dm.predict_dataset.__getitems__(list(range(n))) + raw_patches = raw_batch["anchor"] # (B, C, Z, initial_Y, initial_X) + + # ── Plot ────────────────────────────────────────────────────────────────── + fig, axes = plt.subplots( + n, + 2, + figsize=(8, 4 * n), + squeeze=False, + ) + + for i in range(n): + raw = raw_patches[i] # (C, Z, initial_Y, initial_X) + rescaled = _rescale_yx(raw, final_yx) # (C, Z, final_Y, final_X) + + raw_2d = _mid_slice(raw, args.z_slice) + rescaled_2d = _mid_slice(rescaled, args.z_slice) + + # Shared contrast from the raw patch so differences are spatial only. + vmin, vmax = _percentile_norm(raw_2d) + + phys_raw_y = initial_yx[0] * inference_pixel_size + phys_raw_x = initial_yx[1] * inference_pixel_size + phys_final_y = final_yx[0] * args.reference_pixel_size + phys_final_x = final_yx[1] * args.reference_pixel_size + + # Left column: raw patch + ax = axes[i, 0] + ax.imshow(raw_2d, cmap="gray", vmin=vmin, vmax=vmax, interpolation="nearest") + ax.set_title( + f"Sample {i} — raw patch\n{initial_yx[0]}×{initial_yx[1]} px ({phys_raw_y:.1f}×{phys_raw_x:.1f} µm)", + fontsize=9, + ) + ax.axis("off") + + # Right column: rescaled patch + ax = axes[i, 1] + ax.imshow(rescaled_2d, cmap="gray", vmin=vmin, vmax=vmax, interpolation="nearest") + ax.set_title( + f"Sample {i} — rescaled (model input)\n" + f"{final_yx[0]}×{final_yx[1]} px " + f"({phys_final_y:.1f}×{phys_final_x:.1f} µm)", + fontsize=9, + ) + ax.axis("off") + + axes[0, 0].annotate( + "RAW (initial_yx_patch_size)", + xy=(0.5, 1.12), + xycoords="axes fraction", + ha="center", + fontsize=11, + fontweight="bold", + color="#e74c3c", + ) + axes[0, 1].annotate( + "RESCALED (final_yx_patch_size)", + xy=(0.5, 1.12), + xycoords="axes fraction", + ha="center", + fontsize=11, + fontweight="bold", + color="#2ecc71", + ) + + fig.suptitle( + f"Scale-aware patch rescaling\n" + f"reference={args.reference_pixel_size} µm/px · " + f"inference={inference_pixel_size:.4f} µm/px · " + f"scale={scale:.3f}", + fontsize=12, + fontweight="bold", + y=1.01, + ) + + fig.tight_layout() + args.output.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(args.output, dpi=150, bbox_inches="tight") + print(f"Saved: {args.output}") + plt.close(fig) + + +if __name__ == "__main__": + main() From 378a4ac5b19087552d5e9fe83f52c49385686555 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 13:16:02 -0700 Subject: [PATCH 03/89] fix(viscy-data): handle nested timepoint_statistics in _collate_norm_meta _collate_norm_meta iterated every normalization level and called torch.stack on each stat, but timepoint_statistics is nested {timepoint: {stat: tensor}} rather than flat {stat: tensor}. Any zarr carrying timepoint_statistics (alongside fov/dataset stats) crashed batch collation in TripletDataModule with "expected Tensor as element 0 ... but got dict". Stack within each timepoint sub-dict instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/viscy-data/src/viscy_data/_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/viscy-data/src/viscy_data/_utils.py b/packages/viscy-data/src/viscy_data/_utils.py index cad79cb7d..bf133e184 100644 --- a/packages/viscy-data/src/viscy_data/_utils.py +++ b/packages/viscy-data/src/viscy_data/_utils.py @@ -187,6 +187,13 @@ def _collate_norm_meta(norm_metas: list[NormMeta]) -> NormMeta: if level_stats is None: result[ch][level] = None continue + if level == "timepoint_statistics": + # Nested {timepoint: {stat: tensor}}; stack within each timepoint. + result[ch][level] = { + tp: {stat: torch.stack([m[ch][level][tp][stat] for m in norm_metas]) for stat in tp_stats} + for tp, tp_stats in level_stats.items() + } + continue result[ch][level] = {stat: torch.stack([m[ch][level][stat] for m in norm_metas]) for stat in level_stats} return result From 19a3301490d0b0ab20af3fe4e3aaedbb3c9e03d2 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 13:17:21 -0700 Subject: [PATCH 04/89] feat(viscy-data): add Z-reduction and focus-centered z_range to TripletDataModule Lets a 3D OME-Zarr feed a 2D model without materializing a separate MIP dataset, and centers the extracted Z window on each FOV's focus plane. - z_reduction ("mip"/"center"): collapse the extracted z_range to one slice via BatchedChannelWiseZReductiond. Label-free channels (resolved by parse_channel_name) take the center slice; others are max-projected. on_after_batch_transfer expects Z=1 when reduction is on. - z_extraction_window/z_focus_offset/focus_channel: resolve a per-FOV focus-centered window from each position's focus_slice[ch].fov_statistics.z_focus_mean (fallback z_total//2), all windows the same width. z_range stays as an explicit override; exactly one of z_range / z_extraction_window must be given. Per-FOV windows are resolved at setup() and looked up per patch in the dataset. Tests cover both reduction strategies (discriminating center vs MIP), the normalize-then-reduce order, per-FOV focus resolution, and the z_range/z_extraction_window XOR guard. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/viscy-data/src/viscy_data/triplet.py | 214 ++++++++++++++++-- packages/viscy-data/tests/test_triplet.py | 184 ++++++++++++++- 2 files changed, 375 insertions(+), 23 deletions(-) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index 5f6592ff7..2d7f8a32f 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -59,6 +59,84 @@ def _read_pixel_size(data_path: str | Path) -> float: raise ValueError(f"No positions found in {data_path}") +def _focus_window(z_focus_mean: float | None, z_total: int, z_extraction_window: int, z_focus_offset: float) -> slice: + """Compute a fixed-width Z window centered on a focus plane. + + Parameters + ---------- + z_focus_mean : float or None + Focus plane (slice index). When ``None``, the window is centered on + ``z_total // 2``. + z_total : int + Total number of Z slices available in the FOV. + z_extraction_window : int + Window width (clamped to ``z_total``). + z_focus_offset : float + Fraction of the window placed below the focus plane (0.5 = symmetric). + + Returns + ------- + slice + ``slice(z_start, z_end)`` with ``z_end - z_start == min(z_extraction_window, z_total)``. + """ + z_center = int(round(z_focus_mean)) if z_focus_mean is not None else z_total // 2 + effective_extract = min(z_extraction_window, z_total) + z_below = int(effective_extract * z_focus_offset) + z_start = max(0, z_center - z_below) + z_end = min(z_total, z_start + effective_extract) + z_start = max(0, z_end - effective_extract) + return slice(z_start, z_end) + + +def _resolve_per_fov_z_ranges( + positions: "list[Position]", + z_extraction_window: int, + z_focus_offset: float, + focus_channel: str, +) -> dict[str, slice]: + """Resolve a per-FOV focus-centered Z window for each position. + + Each FOV's window is centered on its own + ``zattrs["focus_slice"][focus_channel]["fov_statistics"]["z_focus_mean"]`` + (the per-FOV focus plane used by the cell-index pipeline). All windows have + the same width (``z_extraction_window``), so the extracted patch Z-size is + uniform across FOVs. FOVs lacking a recorded focus fall back to the + geometric center (``z_total // 2``). + + Parameters + ---------- + positions : list[Position] + Open OME-Zarr positions. + z_extraction_window : int + Window width. + z_focus_offset : float + Fraction of the window placed below the focus plane. + focus_channel : str + Channel name to look up in each FOV's ``focus_slice`` metadata. + + Returns + ------- + dict[str, slice] + Map from stripped FOV name to its ``slice(z_start, z_end)``. + """ + z_ranges: dict[str, slice] = {} + for pos in positions: + fov_name = pos.zgroup.name.strip("/") + z_total = pos["0"].shape[2] + fov_stats = pos.zattrs.get("focus_slice", {}).get(focus_channel, {}).get("fov_statistics", {}) + z_focus_mean = fov_stats.get("z_focus_mean") + z_ranges[fov_name] = _focus_window(z_focus_mean, z_total, z_extraction_window, z_focus_offset) + _logger.info( + "FOV '%s': focus-centered z_range=%s (z_total=%d, z_focus_mean=%s, window=%d).", + fov_name, + (z_ranges[fov_name].start, z_ranges[fov_name].stop), + z_total, + z_focus_mean, + min(z_extraction_window, z_total), + ) + return z_ranges + + def _default_tensorstore_config(cache_pool_bytes: int = 0) -> TensorStoreConfig: """Build a TensorStoreConfig with SLURM-aware concurrency.""" cpus = os.environ.get("SLURM_CPUS_PER_TASK") @@ -78,7 +156,7 @@ def __init__( tracks_tables: "list[pd.DataFrame]", channel_names: list[str], initial_yx_patch_size: tuple[int, int], - z_range: slice, + z_range: "slice | dict[str, slice]", fit: bool = True, predict_cells: bool = False, include_fov_names: list[str] | None = None, @@ -98,8 +176,10 @@ def __init__( Input channel names initial_yx_patch_size : tuple[int, int] YX size of the initially sampled image patch - z_range : slice - Range of Z-slices + z_range : slice or dict[str, slice] + Range of Z-slices. A single ``slice`` applies to every FOV; a + ``dict`` maps each stripped FOV name to its own slice (per-FOV + focus-centered windows). All slices must have the same width. fit : bool, optional Fitting mode in which the full triplet will be sampled, only sample anchor if ``False``, by default True @@ -141,6 +221,12 @@ def __init__( self.return_negative = return_negative self._tensorstores: dict[str, "ts.TensorStore"] = {} + def _fov_z_range(self, fov_name: str) -> slice: + """Return the Z slice for a FOV (per-FOV dict or shared single slice).""" + if isinstance(self.z_range, dict): + return self.z_range[fov_name.strip("/")] + return self.z_range + def _get_tensorstore(self, position: Position) -> "ts.TensorStore": """Get cached tensorstore handle, opening via iohub's tensorstore impl on miss. @@ -175,8 +261,9 @@ def _filter_tracks(self, tracks_tables: "list[pd.DataFrame]") -> "pd.DataFrame": tracks["fov_name"] = pos.zgroup.name.strip("/") tracks["global_track_id"] = tracks["fov_name"].str.cat(tracks["track_id"].astype(str), sep="_") image: ImageArray = pos["0"] - if self.z_range.stop > image.slices: - raise ValueError(f"Z range {self.z_range} exceeds image with Z={image.slices}") + z_range = self._fov_z_range(pos.zgroup.name) + if z_range.stop > image.slices: + raise ValueError(f"Z range {z_range} exceeds image with Z={image.slices}") y_range = (y_exclude, image.height - y_exclude) x_range = (x_exclude, image.width - x_exclude) # FIXME: Check if future time points are available after interval @@ -254,7 +341,7 @@ def _slice_patch(self, track_row: "pd.Series") -> "tuple[ts.TensorStore, NormMet patch = image.oindex[ time, [int(i) for i in self.channel_indices], - self.z_range, + self._fov_z_range(position.zgroup.name), slice(y_center - y_half, y_center + y_half), slice(x_center - x_half, x_center + x_half), ] @@ -314,7 +401,10 @@ def __init__( data_path: str, tracks_path: str, source_channel: str | Sequence[str], - z_range: tuple[int, int], + z_range: tuple[int, int] | None = None, + z_extraction_window: int | None = None, + z_focus_offset: float = 0.5, + focus_channel: str | None = None, initial_yx_patch_size: tuple[int, int] = (512, 512), final_yx_patch_size: tuple[int, int] = (224, 224), split_ratio: float = 0.8, @@ -336,6 +426,7 @@ def __init__( z_window_size: int | None = None, cache_pool_bytes: int = 0, reference_pixel_size: float | None = None, + z_reduction: Literal["mip", "center"] | None = None, ): """Lightning data module for triplet sampling of patches. @@ -347,8 +438,21 @@ def __init__( Tracks labels dataset path source_channel : str | Sequence[str] List of input channel names - z_range : tuple[int, int] - Range of valid z-slices + z_range : tuple[int, int] or None, optional + Explicit ``(z_start, z_end)`` slice range. Mutually exclusive with + ``z_extraction_window``: provide exactly one. When ``None``, the + range is resolved from the focus plane via ``z_extraction_window``. + z_extraction_window : int or None, optional + Number of Z slices to extract, centered on the plate's focus plane + (read from ``zattrs["focus_slice"]``). Mutually exclusive with + ``z_range``. By default ``None`` (use the explicit ``z_range``). + z_focus_offset : float, optional + Fraction of ``z_extraction_window`` placed below the focus plane, + by default 0.5 (symmetric). Only used with ``z_extraction_window``. + focus_channel : str or None, optional + Channel name whose ``focus_slice`` metadata centers the window. + Defaults to the first ``source_channel``. Only used with + ``z_extraction_window``. initial_yx_patch_size : tuple[int, int], optional YX size of the initially sampled image patch, by default (512, 512). Ignored when ``reference_pixel_size`` is set — the patch size is then @@ -364,6 +468,17 @@ def __init__( physical area is covered. The extracted patch is then rescaled to ``final_yx_patch_size`` with bilinear interpolation. By default ``None`` (no rescaling). + z_reduction : {"mip", "center"} or None, optional + Collapse the extracted ``z_range`` window to a single Z-slice so a + 3D dataset can feed a 2D model without materializing a separate + MIP dataset. Label-free channels take the center slice and all other + channels are max-projected; channel type is resolved per channel + name via :func:`viscy_data.channel_utils.parse_channel_name`. The + value (``"mip"`` or ``"center"``) only sets the fallback used when + no channel can be classified as label-free. The caller controls + which Z-planes are collapsed by setting ``z_range`` (e.g. a window + centered on the focus plane). By default ``None`` (no reduction; + full ``z_range`` is kept). split_ratio : float, optional Ratio of training samples, by default 0.8 batch_size : int, optional @@ -412,11 +527,18 @@ def __init__( """ if num_workers > 1: warnings.warn("Using more than 1 thread worker will likely degrade performance.") + if (z_range is None) == (z_extraction_window is None): + raise ValueError("Provide exactly one of 'z_range' or 'z_extraction_window'.") + # Extraction window width is known without opening the zarr: it is the + # explicit z_range span or z_extraction_window. Per-FOV focus centering + # (when z_extraction_window is set) is resolved at setup() time, where + # the positions are open. + extraction_width = (z_range[1] - z_range[0]) if z_range is not None else z_extraction_window super().__init__( data_path=data_path, source_channel=source_channel, target_channel=[], - z_window_size=z_window_size or z_range[1] - z_range[0], + z_window_size=z_window_size or extraction_width, split_ratio=split_ratio, batch_size=batch_size, num_workers=num_workers, @@ -428,7 +550,12 @@ def __init__( prefetch_factor=prefetch_factor, pin_memory=pin_memory, ) - self.z_range = slice(*z_range) + self.z_range = slice(*z_range) if z_range is not None else None + self._z_extraction_window = z_extraction_window + self._z_focus_offset = z_focus_offset + self._focus_channel = focus_channel or ( + source_channel[0] if isinstance(source_channel, (list, tuple)) else source_channel + ) self.tracks_path = Path(tracks_path) self.initial_yx_patch_size = initial_yx_patch_size self._include_wells = fit_include_wells @@ -440,6 +567,11 @@ def __init__( self.return_negative = return_negative self.augment_validation = augment_validation self._cache_pool_bytes = cache_pool_bytes + self.z_reduction = z_reduction + + # Transforms appended after normalization and augmentation, in order. + extra_transforms: list[MapTransform] = [] + if reference_pixel_size is not None: inference_pixel_size = _read_pixel_size(data_path) scale = reference_pixel_size / inference_pixel_size @@ -458,17 +590,36 @@ def __init__( final_yx_patch_size[0] / self.initial_yx_patch_size[0], final_yx_patch_size[1] / self.initial_yx_patch_size[1], ) - rescale_transform = BatchedZoomd( - keys=list(self.source_channel), - scale_factor=(1.0, *scale_yx), - mode="bilinear", - antialias=True, + extra_transforms.append( + BatchedZoomd( + keys=list(self.source_channel), + scale_factor=(1.0, *scale_yx), + mode="bilinear", + antialias=True, + ) ) - self._augmentation_transform = Compose(self.normalizations + self.augmentations + [rescale_transform]) - self._no_augmentation_transform = Compose(self.normalizations + [rescale_transform]) - else: - self._augmentation_transform = Compose(self.normalizations + self.augmentations) - self._no_augmentation_transform = Compose(self.normalizations) + + if z_reduction is not None: + from viscy_data.channel_utils import parse_channel_name + from viscy_transforms import BatchedChannelWiseZReductiond + + labelfree_keys = [ch for ch in self.source_channel if parse_channel_name(ch)["channel_type"] == "labelfree"] + mip_keys = [ch for ch in self.source_channel if ch not in labelfree_keys] + _logger.info( + f"Z-reduction enabled (default_strategy={z_reduction}): collapsing z_range to 1 slice. " + f"MIP channels={mip_keys}, center-slice channels={labelfree_keys}." + ) + extra_transforms.append( + BatchedChannelWiseZReductiond( + keys=list(self.source_channel), + labelfree_keys=labelfree_keys, + default_strategy=z_reduction, + allow_missing_keys=True, + ) + ) + + self._augmentation_transform = Compose(self.normalizations + self.augmentations + extra_transforms) + self._no_augmentation_transform = Compose(self.normalizations + extra_transforms) def _align_tracks_tables_with_positions( self, @@ -508,9 +659,26 @@ def _base_dataset_settings(self) -> dict: "time_interval": self.time_interval, } + def _resolve_z_range(self, positions: "list[Position]") -> "slice | dict[str, slice]": + """Resolve the Z range for a set of positions. + + Returns the explicit ``self.z_range`` slice when one was given, otherwise + a per-FOV focus-centered dict resolved from each position's + ``focus_slice`` metadata. + """ + if self.z_range is not None: + return self.z_range + return _resolve_per_fov_z_ranges( + positions=positions, + z_extraction_window=self._z_extraction_window, + z_focus_offset=self._z_focus_offset, + focus_channel=self._focus_channel, + ) + def _setup_fit(self, dataset_settings: dict): """Set up training and validation triplet datasets.""" positions, tracks_tables = self._align_tracks_tables_with_positions() + dataset_settings = {**dataset_settings, "z_range": self._resolve_z_range(positions)} shuffled_indices = self._set_fit_global_state(len(positions)) positions = [positions[i] for i in shuffled_indices] tracks_tables = [tracks_tables[i] for i in shuffled_indices] @@ -544,6 +712,7 @@ def _setup_predict(self, dataset_settings: dict): """Set up the prediction triplet dataset.""" self._set_predict_global_state() positions, tracks_tables = self._align_tracks_tables_with_positions() + dataset_settings = {**dataset_settings, "z_range": self._resolve_z_range(positions)} self.predict_dataset = TripletDataset( positions=positions, tracks_tables=tracks_tables, @@ -621,7 +790,8 @@ def on_after_batch_transfer(self, batch, dataloader_idx: int): if isinstance(batch, Tensor): # example array return batch - expected_spatial = (self.z_window_size, *self.yx_patch_size) + expected_z = 1 if self.z_reduction is not None else self.z_window_size + expected_spatial = (expected_z, *self.yx_patch_size) for key in ["anchor", "positive", "negative"]: if key in batch: norm_meta_key = f"{key}_norm_meta" diff --git a/packages/viscy-data/tests/test_triplet.py b/packages/viscy-data/tests/test_triplet.py index afb582341..7d0be1e6c 100644 --- a/packages/viscy-data/tests/test_triplet.py +++ b/packages/viscy-data/tests/test_triplet.py @@ -1,8 +1,10 @@ import pandas as pd +import torch from iohub import open_ome_zarr -from pytest import mark +from pytest import mark, raises from viscy_data import TripletDataModule, TripletDataset +from viscy_data.channel_utils import parse_channel_name @mark.parametrize("include_wells", [None, ["A/1", "A/2", "B/1"]]) @@ -107,6 +109,186 @@ def test_datamodule_z_window_size(preprocessed_hcs_dataset, tracks_hcs_dataset, ) +def test_z_range_xor_extraction_window(preprocessed_hcs_dataset, tracks_hcs_dataset): + """Exactly one of z_range / z_extraction_window must be provided.""" + with open_ome_zarr(preprocessed_hcs_dataset) as dataset: + channel_names = dataset.channel_names + common = dict( + data_path=preprocessed_hcs_dataset, + tracks_path=tracks_hcs_dataset, + source_channel=channel_names, + num_workers=0, + ) + with raises(ValueError, match="exactly one"): + TripletDataModule(z_range=None, z_extraction_window=None, **common) + with raises(ValueError, match="exactly one"): + TripletDataModule(z_range=(4, 9), z_extraction_window=8, **common) + + +@mark.parametrize("z_focus_offset", [0.5, 0.3]) +def test_focus_centered_z_range(tmp_path_factory, preprocessed_hcs_dataset, tracks_hcs_dataset, z_focus_offset): + """z_extraction_window resolves a per-FOV focus-centered z_range from zattrs. + + Writes a different per-FOV ``z_focus_mean`` to each position's ``focus_slice`` + ``fov_statistics`` (on a private copy of the session-scoped dataset), then + checks each FOV gets its own window of ``z_extraction_window`` slices centered + on its focus plane with ``z_focus_offset`` of the window below it. + """ + import shutil + + z_extraction_window = 5 + # Copy the session-scoped dataset so writing focus_slice does not leak. + data_path = tmp_path_factory.mktemp("focus") / "data.zarr" + shutil.copytree(preprocessed_hcs_dataset, data_path) + with open_ome_zarr(data_path) as dataset: + channel_names = dataset.channel_names + fov_names = [name for name, _ in dataset.positions()] + z_total = dataset[fov_names[0]]["0"].shape[2] + focus_channel = channel_names[0] + + # Give each FOV a distinct focus plane so per-FOV resolution is exercised. + per_fov_focus = {fov: float(3 + i % (z_total - 4)) for i, fov in enumerate(fov_names)} + with open_ome_zarr(data_path, mode="r+") as dataset: + for fov, pos in dataset.positions(): + pos.zattrs["focus_slice"] = {focus_channel: {"fov_statistics": {"z_focus_mean": per_fov_focus[fov]}}} + + def expected_window(z_focus_mean): + z_center = round(z_focus_mean) + z_below = int(z_extraction_window * z_focus_offset) + z_start = max(0, z_center - z_below) + z_end = min(z_total, z_start + z_extraction_window) + return slice(max(0, z_end - z_extraction_window), z_end) + + dm = TripletDataModule( + data_path=data_path, + tracks_path=tracks_hcs_dataset, + source_channel=channel_names, + z_extraction_window=z_extraction_window, + z_focus_offset=z_focus_offset, + focus_channel=focus_channel, + initial_yx_patch_size=(32, 32), + final_yx_patch_size=(32, 32), + num_workers=0, + batch_size=4, + return_negative=True, + ) + assert dm.z_range is None # explicit z_range not given; resolved per-FOV at setup + dm.setup(stage="fit") + resolved = dm.train_dataset.z_range + assert isinstance(resolved, dict) + # Every resolved FOV window matches its own focus plane and has uniform width. + for fov, z_slice in resolved.items(): + assert z_slice == expected_window(per_fov_focus[fov.strip("/")]), f"FOV {fov} window mismatch" + assert z_slice.stop - z_slice.start == z_extraction_window + for batch in dm.train_dataloader(): + dm.on_after_batch_transfer(batch, 0) + assert batch["anchor"].shape[2] == z_extraction_window + break + break + + +@mark.parametrize("z_reduction", ["mip", "center"]) +def test_datamodule_z_reduction(preprocessed_hcs_dataset, tracks_hcs_dataset, z_reduction): + """z_reduction collapses the z_range window to a single slice per channel. + + Label-free channels (Phase, Retardance) take the center slice; all other + channels (GFP, DAPI) are max-projected, regardless of ``z_reduction``, + which only sets the fallback strategy. + """ + z_range = (4, 9) + yx_patch_size = [32, 32] + batch_size = 4 + with open_ome_zarr(preprocessed_hcs_dataset) as dataset: + channel_names = dataset.channel_names + dm = TripletDataModule( + data_path=preprocessed_hcs_dataset, + tracks_path=tracks_hcs_dataset, + source_channel=channel_names, + z_range=z_range, + initial_yx_patch_size=(32, 32), + final_yx_patch_size=(32, 32), + num_workers=0, + batch_size=batch_size, + return_negative=True, + z_reduction=z_reduction, + ) + dm.setup(stage="fit") + labelfree = {ch for ch in channel_names if parse_channel_name(ch)["channel_type"] == "labelfree"} + assert labelfree, "fixture must contain at least one label-free channel" + assert set(channel_names) - labelfree, "fixture must contain at least one non-label-free channel" + z_window_size = z_range[1] - z_range[0] + center = z_window_size // 2 + for batch in dm.train_dataloader(): + # Snapshot the raw extracted patch before transforms reduce it. + raw = batch["anchor"].clone() + dm.on_after_batch_transfer(batch, 0) + reduced = batch["anchor"] + assert reduced.shape == (batch_size, len(channel_names), 1, *yx_patch_size) + for ci, ch in enumerate(channel_names): + mip = raw[:, ci].amax(dim=1, keepdim=True) + center_slice = raw[:, ci, center : center + 1] + if ch in labelfree: + assert torch.equal(reduced[:, ci], center_slice), f"label-free channel {ch} should be center-sliced" + # Random Z-stack: center slice must differ from MIP, so a strategy + # swap (center vs mip) would be caught rather than passing silently. + assert not torch.equal(reduced[:, ci], mip), f"label-free channel {ch} was max-projected, not centered" + else: + assert torch.equal(reduced[:, ci], mip), f"non-label-free channel {ch} should be max-projected" + assert not torch.equal(reduced[:, ci], center_slice), ( + f"non-label-free channel {ch} was centered, not MIP" + ) + + +def test_z_reduction_runs_on_normalized_stack(preprocessed_hcs_dataset, tracks_hcs_dataset): + """Z-reduction must run after normalization (production order: normalize -> reduce). + + The fixture's ``dataset_statistics`` normalization is a fixed monotone-increasing + affine ``(x - 0.5) / (1/sqrt(12))``, which commutes with both center-slice and + MIP. So the datamodule output (normalize-then-reduce) must equal the same affine + applied to the reduced raw stack. A bug that reduced *before* normalizing, or + skipped normalization, would change the values and fail this check. + """ + import numpy as np + + from viscy_transforms import NormalizeSampled + + z_range = (4, 9) + batch_size = 4 + mean, std = 0.5, 1 / np.sqrt(12) # matches preprocessed_hcs_dataset fixture + with open_ome_zarr(preprocessed_hcs_dataset) as dataset: + channel_names = dataset.channel_names + normalizations = [ + NormalizeSampled(keys=list(channel_names), level="dataset_statistics", subtrahend="mean", divisor="std") + ] + dm = TripletDataModule( + data_path=preprocessed_hcs_dataset, + tracks_path=tracks_hcs_dataset, + source_channel=channel_names, + z_range=z_range, + initial_yx_patch_size=(32, 32), + final_yx_patch_size=(32, 32), + num_workers=0, + batch_size=batch_size, + return_negative=True, + normalizations=normalizations, + z_reduction="mip", + ) + dm.setup(stage="fit") + labelfree = {ch for ch in channel_names if parse_channel_name(ch)["channel_type"] == "labelfree"} + center = (z_range[1] - z_range[0]) // 2 + for batch in dm.train_dataloader(): + raw = batch["anchor"].clone() + dm.on_after_batch_transfer(batch, 0) + reduced = batch["anchor"] + for ci, ch in enumerate(channel_names): + if ch in labelfree: + reduced_raw = raw[:, ci, center : center + 1] + else: + reduced_raw = raw[:, ci].amax(dim=1, keepdim=True) + expected = (reduced_raw - mean) / (std + 1e-8) + assert torch.allclose(reduced[:, ci], expected, atol=1e-5), f"channel {ch} not reduced on normalized stack" + + def test_filter_anchors_time_interval_any(preprocessed_hcs_dataset, tracks_with_gaps_dataset): """Test that time_interval='any' returns all tracks unchanged.""" with open_ome_zarr(preprocessed_hcs_dataset) as dataset: From 365fc4279743f75808bc2c9cd5ce71fe10e0c7ac Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 13:17:29 -0700 Subject: [PATCH 05/89] docs(dynaclr): triplet inference DAG + sample 2D-from-3D predict config Documents the TripletDataModule predict path (zarr + tracking, not parquet) and adds a runnable sample config demonstrating z_reduction + reference_pixel_size to feed a 3D dataset to a 2D model. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../prediction/predict_triplet_2d_from_3d.yml | 81 ++++++++ .../dynaclr/docs/DAGs/inference_triplet.md | 185 ++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 applications/dynaclr/configs/prediction/predict_triplet_2d_from_3d.yml create mode 100644 applications/dynaclr/docs/DAGs/inference_triplet.md diff --git a/applications/dynaclr/configs/prediction/predict_triplet_2d_from_3d.yml b/applications/dynaclr/configs/prediction/predict_triplet_2d_from_3d.yml new file mode 100644 index 000000000..330962b01 --- /dev/null +++ b/applications/dynaclr/configs/prediction/predict_triplet_2d_from_3d.yml @@ -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) diff --git a/applications/dynaclr/docs/DAGs/inference_triplet.md b/applications/dynaclr/docs/DAGs/inference_triplet.md new file mode 100644 index 000000000..d031703ba --- /dev/null +++ b/applications/dynaclr/docs/DAGs/inference_triplet.md @@ -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 | +| 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 | From dc39073a6fff39be65c8a706588cc600e2596a1a Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 13:17:53 -0700 Subject: [PATCH 06/89] docs(dynaclr): add triplet z-projection dataloader inspection script Notebook-style script (no CLI) that loads a 3D OME-Zarr + tracking, extracts a per-FOV focus-centered Z window, collapses it via z_reduction, applies a random affine so anchor/positive diverge, and visualizes a couple of batches to a PNG. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../triplet_dataloader_zprojection.py | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 applications/dynaclr/scripts/dataloader_inspection/triplet_dataloader_zprojection.py diff --git a/applications/dynaclr/scripts/dataloader_inspection/triplet_dataloader_zprojection.py b/applications/dynaclr/scripts/dataloader_inspection/triplet_dataloader_zprojection.py new file mode 100644 index 000000000..83773d617 --- /dev/null +++ b/applications/dynaclr/scripts/dataloader_inspection/triplet_dataloader_zprojection.py @@ -0,0 +1,137 @@ +"""Inspect TripletDataModule with focus-centered z_range + on-the-fly Z-reduction. + +Loads a 3D OME-Zarr + tracking, extracts a focus-centered Z window per FOV +(from ``focus_slice`` zattrs), collapses it to a single slice via ``z_reduction`` +(so a 3D dataset feeds a 2D model), and visualizes a couple of batches. + +Run cell-by-cell in an interactive window (VS Code / Jupyter) or top-to-bottom. +""" + +# %% +from pathlib import Path + +import matplotlib.pyplot as plt +from iohub import open_ome_zarr + +from viscy_data import TripletDataModule +from viscy_transforms import BatchedRandAffined, NormalizeSampled + +# --- edit these --------------------------------------------------------------- +DATASET_DIR = "/hpc/projects/organelle_phenotyping/datasets/2026_04_08_A549_G3BP1_ZIKV" +DATA_PATH = f"{DATASET_DIR}/2026_04_08_A549_G3BP1_ZIKV.zarr" +TRACKS_PATH = f"{DATASET_DIR}/tracking.zarr" +SOURCE_CHANNEL = ["Phase3D", "raw GFP EX488 EM525-45"] # label-free + fluorescence +FOCUS_CHANNEL = "Phase3D" # channel whose focus plane centers the Z window +Z_EXTRACTION_WINDOW = 15 # Z slices extracted, centered on each FOV's focus plane +Z_FOCUS_OFFSET = 0.5 # fraction of the window below the focus plane +Z_REDUCTION = "mip" # "mip" (fluorescence) / center-slice (label-free), or None +YX_PATCH = (256, 256) # extracted == final (no YX rescale; set reference_pixel_size to rescale) +BATCH_SIZE = 8 +N_BATCHES = 2 +OUTPUT_DIR = "/home/eduardo.hirata/repos/viscy/applications/dynaclr/scripts/dataloader_inspection/output" +OUTPUT_PNG = f"{OUTPUT_DIR}/triplet_zprojection.png" +# ------------------------------------------------------------------------------ + +# %% [markdown] +# Peek at the store: channels, shape, and the per-FOV focus plane the window centers on. + +# %% +with open_ome_zarr(DATA_PATH) as plate: + print("channels:", plate.channel_names) + name, pos = next(iter(plate.positions())) + print("first FOV:", name, "| TCZYX:", pos["0"].shape) + focus = pos.zattrs.get("focus_slice", {}).get(FOCUS_CHANNEL, {}) + print("per-FOV z_focus_mean:", focus.get("fov_statistics", {}).get("z_focus_mean")) + +# %% [markdown] +# Build the datamodule. ``z_extraction_window`` (not ``z_range``) makes each FOV's +# window center on its own focus plane; ``z_reduction`` collapses Z to 1. + +# %% +dm = TripletDataModule( + data_path=DATA_PATH, + tracks_path=TRACKS_PATH, + source_channel=SOURCE_CHANNEL, + z_extraction_window=Z_EXTRACTION_WINDOW, + z_focus_offset=Z_FOCUS_OFFSET, + focus_channel=FOCUS_CHANNEL, + initial_yx_patch_size=YX_PATCH, + final_yx_patch_size=YX_PATCH, + z_reduction=Z_REDUCTION, + batch_size=BATCH_SIZE, + num_workers=0, + normalizations=[ + NormalizeSampled( + keys=SOURCE_CHANNEL, + level="fov_statistics", + subtrahend="mean", + divisor="std", + ) + ], + augmentations=[ + # Random affine so anchor and positive (a clone of the anchor when + # time_interval="any") diverge — applied per-key with fresh random + # params, on the 3D stack before z_reduction. prob=1.0 = always fire. + BatchedRandAffined( + keys=SOURCE_CHANNEL, + prob=1.0, + # rotate_range is (Z, Y, X) radians: the Z entry is the in-plane (XY) + # rotation. Rotating about Y/X would tumble the stack out of plane and + # collapse to a strip after MIP, so keep those at 0. + rotate_range=(3.14159, 0.0, 0.0), # full in-plane rotation + scale_range=(0.8, 1.2), + translate_range=(0.0, 0.1, 0.1), # up to 10% YX shift, no Z shift + ) + ], +) +dm.setup(stage="fit") + +# Per-FOV focus-centered windows resolved at setup (one slice per FOV). +resolved = dm.train_dataset.z_range +print(f"resolved {len(resolved)} per-FOV z-windows; e.g.:") +for fov, z_slice in list(resolved.items())[:5]: + print(f" {fov}: {z_slice}") + +# %% [markdown] +# Pull a couple of batches and check shapes. After z_reduction the Z axis is 1. + +# %% +batches = [] +for i, batch in enumerate(dm.train_dataloader()): + dm.on_after_batch_transfer(batch, 0) # normalize + Z-reduce on the batch + print(f"batch {i}: anchor {tuple(batch['anchor'].shape)} (expect Z=1)") + batches.append(batch) + if i + 1 >= N_BATCHES: + break + +# %% [markdown] +# Visualize anchor / positive / negative for the first few cells of each batch. +# Each panel is one channel of the Z-reduced (B, C, 1, Y, X) patch. + +# %% +keys = [k for k in ("anchor", "positive", "negative") if k in batches[0]] +n_cells = min(4, BATCH_SIZE) +n_rows = N_BATCHES * n_cells +n_cols = len(keys) * len(SOURCE_CHANNEL) +fig, axes = plt.subplots(n_rows, n_cols, figsize=(2.4 * n_cols, 2.4 * n_rows), squeeze=False) + +for bi, batch in enumerate(batches): + for ci in range(n_cells): + row = bi * n_cells + ci + col = 0 + for key in keys: + patch = batch[key][ci] # (C, 1, Y, X) + for ch_idx, ch in enumerate(SOURCE_CHANNEL): + img = patch[ch_idx, 0].cpu().numpy() # (Y, X) + lo, hi = (img.min(), img.max()) if img.max() > img.min() else (0.0, 1.0) + ax = axes[row, col] + ax.imshow(img, cmap="gray", vmin=lo, vmax=hi) + ax.set_title(f"b{bi} c{ci}\n{key}/{ch.split()[0]}", fontsize=7) + ax.axis("off") + col += 1 + +fig.suptitle(f"Triplet patches — z_extraction_window={Z_EXTRACTION_WINDOW}, z_reduction={Z_REDUCTION}", fontsize=10) +fig.tight_layout() +Path(OUTPUT_PNG).parent.mkdir(parents=True, exist_ok=True) +fig.savefig(OUTPUT_PNG, dpi=120, bbox_inches="tight") +print("saved:", OUTPUT_PNG) From a14a0ac12d43d7a43eb96eafe0a6ee18e84d4af2 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 14:07:46 -0700 Subject: [PATCH 07/89] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/viscy-data/src/viscy_data/triplet.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index 2d7f8a32f..4d42601e2 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -553,9 +553,7 @@ def __init__( self.z_range = slice(*z_range) if z_range is not None else None self._z_extraction_window = z_extraction_window self._z_focus_offset = z_focus_offset - self._focus_channel = focus_channel or ( - source_channel[0] if isinstance(source_channel, (list, tuple)) else source_channel - ) + self._focus_channel = focus_channel or self.source_channel[0] self.tracks_path = Path(tracks_path) self.initial_yx_patch_size = initial_yx_patch_size self._include_wells = fit_include_wells From 52c72a1d4c3e6402517a3aad9012c796b165eeff Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 14:07:56 -0700 Subject: [PATCH 08/89] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/viscy-data/tests/test_triplet.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/viscy-data/tests/test_triplet.py b/packages/viscy-data/tests/test_triplet.py index 7d0be1e6c..483dac897 100644 --- a/packages/viscy-data/tests/test_triplet.py +++ b/packages/viscy-data/tests/test_triplet.py @@ -184,8 +184,6 @@ def expected_window(z_focus_mean): dm.on_after_batch_transfer(batch, 0) assert batch["anchor"].shape[2] == z_extraction_window break - break - @mark.parametrize("z_reduction", ["mip", "center"]) def test_datamodule_z_reduction(preprocessed_hcs_dataset, tracks_hcs_dataset, z_reduction): From 6d320aeae359e14774d356a57bd079bca6dc05ba Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 14:08:54 -0700 Subject: [PATCH 09/89] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/viscy-data/src/viscy_data/triplet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index 4d42601e2..11f746540 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -529,6 +529,10 @@ def __init__( warnings.warn("Using more than 1 thread worker will likely degrade performance.") if (z_range is None) == (z_extraction_window is None): raise ValueError("Provide exactly one of 'z_range' or 'z_extraction_window'.") + if z_extraction_window is not None and z_extraction_window <= 0: + raise ValueError("'z_extraction_window' must be a positive integer.") + if z_extraction_window is not None and not (0.0 <= z_focus_offset <= 1.0): + raise ValueError("'z_focus_offset' must be between 0.0 and 1.0 (inclusive).") # Extraction window width is known without opening the zarr: it is the # explicit z_range span or z_extraction_window. Per-FOV focus centering # (when z_extraction_window is set) is resolved at setup() time, where From 2a1b327a30a466941727d8c77b95eadfba7ff3c9 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 14:09:06 -0700 Subject: [PATCH 10/89] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- applications/dynaclr/docs/DAGs/inference_triplet.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/applications/dynaclr/docs/DAGs/inference_triplet.md b/applications/dynaclr/docs/DAGs/inference_triplet.md index d031703ba..2aa6bddb1 100644 --- a/applications/dynaclr/docs/DAGs/inference_triplet.md +++ b/applications/dynaclr/docs/DAGs/inference_triplet.md @@ -177,9 +177,10 @@ return_predictions: false # writer persists to zarr; don't hold in | 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 | +| 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 | explicit `z_range` or per-FOV `z_extraction_window` from `focus_slice`; `z_reduction` collapses | per-FOV `z_extraction_window` from `focus_slice` | | 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 | From dbe22f25da4f28fda81dd71b3f1247c09bf769d5 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 14:09:16 -0700 Subject: [PATCH 11/89] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/viscy-data/src/viscy_data/triplet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index 11f746540..d27b11ba5 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -577,7 +577,7 @@ def __init__( if reference_pixel_size is not None: inference_pixel_size = _read_pixel_size(data_path) scale = reference_pixel_size / inference_pixel_size - self.initial_yx_patch_size = tuple(round(s * scale) for s in final_yx_patch_size) + self.initial_yx_patch_size = tuple(max(1, int(round(s * scale))) for s in final_yx_patch_size) _logger.info( f"Pixel size rescaling enabled: " f"reference={reference_pixel_size:.4f} µm/px, " From 4a2de142dd314f0f431ebdc999bdf5371a5702ed Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 29 Jun 2026 14:15:48 -0700 Subject: [PATCH 12/89] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/viscy-data/src/viscy_data/triplet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index d27b11ba5..be04159df 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -126,7 +126,7 @@ def _resolve_per_fov_z_ranges( fov_stats = pos.zattrs.get("focus_slice", {}).get(focus_channel, {}).get("fov_statistics", {}) z_focus_mean = fov_stats.get("z_focus_mean") z_ranges[fov_name] = _focus_window(z_focus_mean, z_total, z_extraction_window, z_focus_offset) - _logger.info( + _logger.debug( "FOV '%s': focus-centered z_range=%s (z_total=%d, z_focus_mean=%s, window=%d).", fov_name, (z_ranges[fov_name].start, z_ranges[fov_name].stop), From 5b81c2c8bce71bb2321b28f84779eb7fdc4abaac Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 30 Jun 2026 15:17:08 -0700 Subject: [PATCH 13/89] fix(viscy-data): address triplet z-reduction/rescaling review findings - Declare viscy-transforms in viscy-data's triplet extra and hoist the BatchedZoomd/BatchedChannelWiseZReductiond imports to module top; the inline imports were masking a missing runtime dependency that would ImportError for anyone using z_reduction or reference_pixel_size. - Raise in _resolve_per_fov_z_ranges when a FOV's Z is smaller than z_extraction_window, instead of silently emitting a narrower window that breaks cross-FOV batch stacking. - Remove a dead duplicate break in test_focus_centered_z_range. - Correct the inference DAG doc: embeddings live in .X (the embedding_key array), mirrored to obsm["X_backbone"]/["X_projections"]. uv.lock intentionally omitted: the workspace glob currently entangles the untracked applications/eet package, so a clean regen of the single viscy-transforms edge is not possible until eet is committed or removed. Co-Authored-By: Claude Opus 4.8 (1M context) --- applications/dynaclr/docs/DAGs/inference_triplet.md | 5 +++-- packages/viscy-data/pyproject.toml | 2 +- packages/viscy-data/src/viscy_data/triplet.py | 13 ++++++++----- packages/viscy-data/tests/test_triplet.py | 1 + 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/applications/dynaclr/docs/DAGs/inference_triplet.md b/applications/dynaclr/docs/DAGs/inference_triplet.md index 2aa6bddb1..52aa3aab4 100644 --- a/applications/dynaclr/docs/DAGs/inference_triplet.md +++ b/applications/dynaclr/docs/DAGs/inference_triplet.md @@ -38,7 +38,8 @@ viscy predict --config configs/prediction/predict_triplet.yml │ 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/...) +embeddings.zarr (AnnData: .X = embedding_key array, mirrored to obsm["X_backbone"] + /["X_projections"]; obs = fov_name/track_id/t/...) │ ▼ dynaclr split-embeddings --input embeddings.zarr --output-dir embeddings/ @@ -82,7 +83,7 @@ downstream eval (CPU/GPU, per analysis) | 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` | +| Backbone embeddings | `embeddings.zarr` → `.X` (+ `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` | diff --git a/packages/viscy-data/pyproject.toml b/packages/viscy-data/pyproject.toml index 959f9543e..504e36aa5 100644 --- a/packages/viscy-data/pyproject.toml +++ b/packages/viscy-data/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ optional-dependencies.all = [ "viscy-data[livecell,mmap,triplet]" ] optional-dependencies.livecell = [ "pycocotools", "tifffile", "torchvision" ] optional-dependencies.mmap = [ "tensordict" ] -optional-dependencies.triplet = [ "tensorstore" ] +optional-dependencies.triplet = [ "tensorstore", "viscy-transforms" ] urls.Homepage = "https://github.com/mehta-lab/VisCy" urls.Issues = "https://github.com/mehta-lab/VisCy/issues" urls.Repository = "https://github.com/mehta-lab/VisCy" diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index be04159df..6bb81ad0f 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -34,8 +34,10 @@ _read_norm_meta, _transform_channel_wise, ) +from viscy_data.channel_utils import parse_channel_name from viscy_data.hcs import HCSDataModule from viscy_data.select import _filter_fovs, _filter_wells +from viscy_transforms import BatchedChannelWiseZReductiond, BatchedZoomd _logger = logging.getLogger("lightning.pytorch") @@ -123,6 +125,12 @@ def _resolve_per_fov_z_ranges( for pos in positions: fov_name = pos.zgroup.name.strip("/") z_total = pos["0"].shape[2] + if z_total < z_extraction_window: + raise ValueError( + f"FOV '{fov_name}' has Z={z_total} < z_extraction_window={z_extraction_window}; " + "its window would be narrower than the others and break cross-FOV batch stacking. " + "Lower z_extraction_window or exclude this FOV." + ) fov_stats = pos.zattrs.get("focus_slice", {}).get(focus_channel, {}).get("fov_statistics", {}) z_focus_mean = fov_stats.get("z_focus_mean") z_ranges[fov_name] = _focus_window(z_focus_mean, z_total, z_extraction_window, z_focus_offset) @@ -586,8 +594,6 @@ def __init__( f"Extracting {self.initial_yx_patch_size} px patches " f"and resizing to {final_yx_patch_size} px." ) - from viscy_transforms import BatchedZoomd - scale_yx = ( final_yx_patch_size[0] / self.initial_yx_patch_size[0], final_yx_patch_size[1] / self.initial_yx_patch_size[1], @@ -602,9 +608,6 @@ def __init__( ) if z_reduction is not None: - from viscy_data.channel_utils import parse_channel_name - from viscy_transforms import BatchedChannelWiseZReductiond - labelfree_keys = [ch for ch in self.source_channel if parse_channel_name(ch)["channel_type"] == "labelfree"] mip_keys = [ch for ch in self.source_channel if ch not in labelfree_keys] _logger.info( diff --git a/packages/viscy-data/tests/test_triplet.py b/packages/viscy-data/tests/test_triplet.py index 483dac897..6a6fa939f 100644 --- a/packages/viscy-data/tests/test_triplet.py +++ b/packages/viscy-data/tests/test_triplet.py @@ -185,6 +185,7 @@ def expected_window(z_focus_mean): assert batch["anchor"].shape[2] == z_extraction_window break + @mark.parametrize("z_reduction", ["mip", "center"]) def test_datamodule_z_reduction(preprocessed_hcs_dataset, tracks_hcs_dataset, z_reduction): """z_reduction collapses the z_range window to a single slice per channel. From c7f8a163275752bf4208f846c373caa63053bd13 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Mon, 6 Jul 2026 10:01:36 -0700 Subject: [PATCH 14/89] CLI to split ann data by a column to group by --- .../dynaclr/evaluation/split_embeddings.py | 97 +++++++++++++------ 1 file changed, 70 insertions(+), 27 deletions(-) diff --git a/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py b/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py index c55aedbcd..651247b55 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py +++ b/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py @@ -1,8 +1,9 @@ -"""Split a combined embeddings zarr into one zarr per experiment. +"""Split a combined embeddings zarr into one zarr per group. Reads the combined embeddings.zarr produced by the predict step, groups rows -by obs["experiment"], and writes one AnnData zarr per experiment under -output_dir/{experiment}.zarr. The combined zarr is removed after splitting. +by obs[group_by] (``experiment`` by default), and writes one AnnData zarr per +group under output_dir/{group}.zarr. The combined zarr is removed after +splitting. Usage ----- @@ -11,6 +12,10 @@ Or with inline arguments: dynaclr split-embeddings --input /path/to/embeddings.zarr --output-dir /path/to/embeddings/ + +Split by a different column (e.g. one zarr per marker): + +dynaclr split-embeddings --input /path/to/embeddings.zarr --output-dir /path/to/embeddings/ --group-by marker """ from __future__ import annotations @@ -20,22 +25,36 @@ import click -def split_embeddings(input_path: Path, output_dir: Path) -> list[Path]: - """Split combined embeddings zarr into one zarr per experiment. +def split_embeddings( + input_path: Path, + output_dir: Path, + group_by: str = "experiment", + prefix_by: str | None = None, +) -> list[Path]: + """Split combined embeddings zarr into one zarr per group. Parameters ---------- input_path : Path Path to the combined embeddings zarr (AnnData format). - Must have obs["experiment"] column. + Must have the ``group_by`` (and ``prefix_by``, if set) column in obs. output_dir : Path - Directory to write per-experiment zarrs. - Each experiment is written to output_dir/{experiment}.zarr. + Directory to write per-group zarrs. + Each group value is written to ``output_dir/{group}.zarr``, or + ``output_dir/{prefix}_{group}.zarr`` when ``prefix_by`` is set. + group_by : str, optional + obs column to group rows by. By default ``"experiment"``. + prefix_by : str or None, optional + obs column whose value prefixes each output filename as + ``{prefix}_{group}.zarr`` (e.g. ``prefix_by="experiment"`` with + ``group_by="marker"`` yields ``{dataset}_{marker}.zarr``). The prefix + column must be constant within each group. By default ``None`` (no + prefix). Returns ------- list[Path] - Paths to the written per-experiment zarrs. + Paths to the written per-group zarrs. """ import anndata as ad @@ -49,24 +68,36 @@ def split_embeddings(input_path: Path, output_dir: Path) -> list[Path]: adata = ad.read_zarr(input_path) click.echo(f" {adata.n_obs} cells, {adata.n_vars} features") - if "experiment" not in adata.obs.columns: - raise ValueError( - "embeddings zarr obs is missing 'experiment' column. " - "Re-run the predict step with the updated pipeline to include metadata." - ) + for col in filter(None, [group_by, prefix_by]): + if col not in adata.obs.columns: + raise ValueError( + f"embeddings zarr obs is missing '{col}' column. " + f"Available columns: {sorted(adata.obs.columns)}. " + "Re-run the predict step with the updated pipeline to include metadata." + ) - experiments = adata.obs["experiment"].unique().tolist() - click.echo(f" {len(experiments)} experiments: {experiments}") + groups = adata.obs[group_by].unique().tolist() + click.echo(f" {len(groups)} {group_by} groups: {groups}") output_dir.mkdir(parents=True, exist_ok=True) written: list[Path] = [] - for exp in experiments: - mask = adata.obs["experiment"] == exp - adata_exp = adata[mask].copy() - out_path = output_dir / f"{exp}.zarr" - click.echo(f" Writing {exp}: {adata_exp.n_obs} cells → {out_path}") - adata_exp.write_zarr(out_path) + for group in groups: + mask = adata.obs[group_by] == group + adata_group = adata[mask].copy() + if prefix_by is not None: + prefixes = adata_group.obs[prefix_by].unique().tolist() + if len(prefixes) != 1: + raise ValueError( + f"'{prefix_by}' is not constant within {group_by}={group!r}: " + f"found {prefixes}. Cannot build a '{{prefix}}_{{group}}' filename." + ) + name = f"{prefixes[0]}_{group}" + else: + name = f"{group}" + out_path = output_dir / f"{name}.zarr" + click.echo(f" Writing {name}: {adata_group.n_obs} cells → {out_path}") + adata_group.write_zarr(out_path) written.append(out_path) click.echo(f"\nRemoving combined zarr: {input_path}") @@ -74,7 +105,7 @@ def split_embeddings(input_path: Path, output_dir: Path) -> list[Path]: shutil.rmtree(input_path) - click.echo(f"\nWrote {len(written)} per-experiment zarrs to {output_dir}") + click.echo(f"\nWrote {len(written)} per-{group_by} zarrs to {output_dir}") return written @@ -90,11 +121,23 @@ def split_embeddings(input_path: Path, output_dir: Path) -> list[Path]: "--output-dir", type=click.Path(path_type=Path), required=True, - help="Directory to write per-experiment zarrs", + help="Directory to write per-group zarrs", +) +@click.option( + "--group-by", + default="experiment", + show_default=True, + help="obs column to group rows by (e.g. 'marker' for one zarr per marker)", +) +@click.option( + "--prefix-by", + default=None, + help="obs column to prefix filenames as {prefix}_{group}.zarr " + "(e.g. 'experiment' with --group-by marker gives {dataset}_{marker}.zarr)", ) -def main(input_path: Path, output_dir: Path) -> None: - """Split a combined embeddings zarr into one zarr per experiment.""" - split_embeddings(input_path, output_dir) +def main(input_path: Path, output_dir: Path, group_by: str, prefix_by: str | None) -> None: + """Split a combined embeddings zarr into one zarr per group.""" + split_embeddings(input_path, output_dir, group_by=group_by, prefix_by=prefix_by) if __name__ == "__main__": From b39205f1a447296a3194956e4d6fbc2494294f71 Mon Sep 17 00:00:00 2001 From: Soorya Pradeep Date: Fri, 10 Jul 2026 15:20:18 -0700 Subject: [PATCH 15/89] perf(viscy-utils): GPU-accelerate MMD via PyTorch Replace the NumPy/scipy.cdist backend with PyTorch so the pooled RBF kernel matrix is built once on the available device (CUDA if usable, else CPU) and reused across all permutations. Device selection probes a trivial kernel launch and falls back to CPU, so a present-but-unusable GPU (e.g. compute capability older than the torch build) does not crash. Public API is unchanged: median_heuristic/compute_mmd_unbiased return the same bandwidth-convention values, and mmd_permutation_test still returns (mmd2, p_value, null_distribution) so dynaclr's effect-size and activity z-score computations keep working. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/viscy_utils/evaluation/mmd.py | 202 ++++++++++++------ 1 file changed, 137 insertions(+), 65 deletions(-) diff --git a/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py b/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py index e206dcdc6..d6c68f01b 100644 --- a/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py +++ b/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py @@ -1,8 +1,80 @@ -"""Maximum Mean Discrepancy (MMD) with Gaussian RBF kernel and permutation test.""" +"""Maximum Mean Discrepancy (MMD) with Gaussian RBF kernel and permutation test. + +GPU-accelerated via PyTorch: the pooled RBF kernel matrix is built once on the +available device (CUDA if present, otherwise CPU) and reused across all +permutations. The public API is device-agnostic — inputs and outputs are NumPy +arrays / Python floats — so callers do not need to manage tensors or devices. +""" import numpy as np +import torch from numpy.typing import NDArray -from scipy.spatial.distance import cdist + + +_DEVICE: torch.device | None = None + + +def _get_device() -> torch.device: + """Return a usable CUDA device, otherwise CPU (detected once and cached). + + ``torch.cuda.is_available()`` only reports that a GPU is *present*, not that + the installed PyTorch build can launch kernels on it (e.g. a GPU whose + compute capability predates the build raises at the first kernel launch). + We therefore probe with a trivial kernel and fall back to CPU if it fails. + """ + global _DEVICE + if _DEVICE is None: + _DEVICE = torch.device("cpu") + if torch.cuda.is_available(): + try: + (torch.zeros(1, device="cuda") + 1.0).cpu() + _DEVICE = torch.device("cuda") + except RuntimeError: + _DEVICE = torch.device("cpu") + return _DEVICE + + +def _sq_dists(A: torch.Tensor, B: torch.Tensor) -> torch.Tensor: + """Pairwise squared Euclidean distances via the (a-b)^2 = a^2 + b^2 - 2ab expansion. + + Parameters + ---------- + A : torch.Tensor + Shape (n, d). + B : torch.Tensor + Shape (m, d). + + Returns + ------- + torch.Tensor + Squared distances, shape (n, m). Clamped at 0 to absorb the small + negative values the expansion can produce from floating-point error. + """ + A2 = (A * A).sum(1, keepdim=True) + B2 = (B * B).sum(1, keepdim=True).T + return (A2 + B2 - 2.0 * (A @ B.T)).clamp_min_(0.0) + + +def _rbf_kernel(A: torch.Tensor, B: torch.Tensor, bandwidth: float) -> torch.Tensor: + """Gaussian RBF kernel matrix on the device of ``A``. + + K(a, b) = exp(-||a - b||^2 / (2 * bandwidth)) + + Parameters + ---------- + A : torch.Tensor + Shape (n, d). + B : torch.Tensor + Shape (m, d). + bandwidth : float + Kernel bandwidth (sigma^2). Must be > 0. + + Returns + ------- + torch.Tensor + Kernel matrix, shape (n, m). + """ + return torch.exp(-_sq_dists(A, B) / (2.0 * bandwidth)) def median_heuristic(X: NDArray, Y: NDArray, subsample: int = 1000) -> float: @@ -31,13 +103,16 @@ def median_heuristic(X: NDArray, Y: NDArray, subsample: int = 1000) -> float: if len(pool) > subsample: idx = rng.choice(len(pool), subsample, replace=False) pool = pool[idx] - sq_dists = cdist(pool, pool, metric="sqeuclidean") - upper = sq_dists[np.triu_indices_from(sq_dists, k=1)] - return float(np.median(upper)) + 1e-12 + device = _get_device() + P = torch.from_numpy(pool).to(device) + d2 = _sq_dists(P, P) + n = d2.shape[0] + mask = torch.triu(torch.ones(n, n, dtype=torch.bool, device=device), diagonal=1) + return float(d2[mask].median().item()) + 1e-12 def gaussian_rbf_kernel(X: NDArray, Y: NDArray, bandwidth: float) -> NDArray: - """Compute Gaussian RBF kernel matrix K(X, Y) in float32. + """Compute Gaussian RBF kernel matrix K(X, Y). K(x, y) = exp(-||x - y||^2 / (2 * bandwidth)) @@ -55,8 +130,10 @@ def gaussian_rbf_kernel(X: NDArray, Y: NDArray, bandwidth: float) -> NDArray: NDArray Kernel matrix, shape (n, m), float32. """ - sq_dists = cdist(X.astype(np.float32), Y.astype(np.float32), metric="sqeuclidean") - return np.exp(-sq_dists / (2.0 * bandwidth), dtype=np.float32) + device = _get_device() + A = torch.from_numpy(np.asarray(X, dtype=np.float32)).to(device) + B = torch.from_numpy(np.asarray(Y, dtype=np.float32)).to(device) + return _rbf_kernel(A, B, bandwidth).cpu().numpy() def compute_mmd_unbiased(X: NDArray, Y: NDArray, bandwidth: float | None = None) -> float: @@ -82,15 +159,24 @@ def compute_mmd_unbiased(X: NDArray, Y: NDArray, bandwidth: float | None = None) """ if bandwidth is None: bandwidth = median_heuristic(X, Y) + device = _get_device() + Xt = torch.from_numpy(np.asarray(X, dtype=np.float32)).to(device) + Yt = torch.from_numpy(np.asarray(Y, dtype=np.float32)).to(device) n = len(X) m = len(Y) - K_XX = gaussian_rbf_kernel(X, X, bandwidth) - K_YY = gaussian_rbf_kernel(Y, Y, bandwidth) - K_XY = gaussian_rbf_kernel(X, Y, bandwidth) - np.fill_diagonal(K_XX, 0.0) - np.fill_diagonal(K_YY, 0.0) - mmd2 = K_XX.sum() / (n * (n - 1)) + K_YY.sum() / (m * (m - 1)) - 2.0 * K_XY.mean() - return float(mmd2) + K_XX = _rbf_kernel(Xt, Xt, bandwidth) + K_YY = _rbf_kernel(Yt, Yt, bandwidth) + K_XY = _rbf_kernel(Xt, Yt, bandwidth) + K_XX.fill_diagonal_(0.0) + K_YY.fill_diagonal_(0.0) + # Reduce in float64 so the estimate is symmetric in (X, Y) to machine + # precision despite the float32 kernel. + mmd2 = ( + K_XX.double().sum() / (n * (n - 1)) + + K_YY.double().sum() / (m * (m - 1)) + - 2.0 * K_XY.double().mean() + ) + return float(mmd2.item()) _MMD_PERM_MAX_N = 20_000 @@ -105,14 +191,16 @@ def mmd_permutation_test( ) -> tuple[float, float, NDArray]: """MMD^2 with vectorized permutation test for significance. - Precomputes the pooled kernel matrix K_pool once, then all permutations - are evaluated via vectorized row/column sums — no repeated cdist calls - and no Python loop over individual permutations. + Builds the pooled RBF kernel matrix K once on the available device, then + evaluates the observed split and all permutations in a single batch of + matrix multiplications — no per-permutation Python loop and no repeated + distance computations. - Strategy: for each permutation p, MMD^2 = sum_X/n(n-1) + sum_Y/m(m-1) - 2*mean_XY - where sum_X = sum of K_pool[ix,ix] off-diagonal = (K_pool[ix,:] * one_hot_X).sum(). - We represent each permutation as a binary label vector z in {0,1}^(n+m), - then use K_pool @ z and K_pool @ (1-z) to get row sums in O(n_perm * N) ops. + Strategy: represent each permutation as a binary label vector z in + {0,1}^(n+m) (1 = assigned to X group), then for a batch of P permutations + stacked as Z (P, N), the within-X / within-Y / cross kernel sums follow from + K @ Z.T and K @ (1 - Z).T. With the kernel diagonal zeroed, this gives the + unbiased MMD^2 for every permutation at once in O(P * N^2) GEMM ops. Parameters ---------- @@ -142,58 +230,42 @@ def mmd_permutation_test( m = len(Y) N = n + m # The pooled kernel matrix is (N, N) float32 — quadratic in N. Cap N - # explicitly so callers see a clear error rather than an OOM when they - # forget to subsample (50k => 10 GB; 100k => 40 GB). + # explicitly so callers see a clear error rather than an OOM (host or GPU) + # when they forget to subsample (50k => 10 GB; 100k => 40 GB). if N > _MMD_PERM_MAX_N: raise ValueError( f"mmd_permutation_test pooled kernel would be ({N}, {N}) float32 " f"≈ {(N * N * 4) / 1e9:.1f} GB. Subsample X and/or Y so that " f"len(X) + len(Y) <= {_MMD_PERM_MAX_N}." ) + device = _get_device() pool = np.concatenate([X, Y], axis=0).astype(np.float32) - # Compute full pooled kernel matrix once: (N, N) float32 - K = gaussian_rbf_kernel(pool, pool, bandwidth) - np.fill_diagonal(K, 0.0) - - def _mmd2_from_labels(z: NDArray) -> NDArray: - """Vectorized MMD^2 for a batch of label vectors. - - Parameters - ---------- - z : NDArray - Shape (n_perm, N), float32, 1 = assigned to X group. - - Returns - ------- - NDArray - MMD^2 values, shape (n_perm,). - """ - nz = z.sum(axis=1) # actual n per permutation (n_perm,) - mz = N - nz # actual m per permutation - # Row sums of K restricted to X-group and Y-group - # K @ z.T -> (N, n_perm), then z @ (K @ z.T) -> (n_perm, n_perm) diagonal = sum_XX - KzT = K @ z.T # (N, n_perm) - sum_XX = (z * KzT.T).sum(axis=1) # (n_perm,) — within-X kernel sums (diagonal zeroed) - sum_YY = ((1 - z) * (K @ (1 - z).T).T).sum(axis=1) # (n_perm,) — within-Y - sum_XY = (z * (K @ (1 - z).T).T).sum(axis=1) # (n_perm,) — cross - kxx = sum_XX / (nz * (nz - 1)) - kyy = sum_YY / (mz * (mz - 1)) - kxy = sum_XY / (nz * mz) - return kxx + kyy - 2.0 * kxy - - # Observed: original split (first n are X) - z_obs = np.zeros((1, N), dtype=np.float32) - z_obs[0, :n] = 1.0 - observed = float(_mmd2_from_labels(z_obs)[0]) - - # Null: random permutations as binary label vectors + P = torch.from_numpy(pool).to(device) + # Compute full pooled kernel matrix once: (N, N) + K = _rbf_kernel(P, P, bandwidth) + K.fill_diagonal_(0.0) + + # Label matrix: row 0 = observed split (first n are X), rows 1: = random + # permutations. Every row has exactly n ones, so group sizes are preserved. rng = np.random.default_rng(seed) - # Generate all permutation indices at once - perms = np.stack([rng.permutation(N) for _ in range(n_permutations)]) # (n_perm, N) - z_null = np.zeros((n_permutations, N), dtype=np.float32) + perms = np.stack([rng.permutation(N) for _ in range(n_permutations)]) # (P, N) + labels = np.zeros((n_permutations + 1, N), dtype=np.float32) + labels[0, :n] = 1.0 row_idx = np.arange(n_permutations)[:, None] - z_null[row_idx, perms[:, :n]] = 1.0 + labels[1:][row_idx, perms[:, :n]] = 1.0 + z = torch.from_numpy(labels).to(device) # (P+1, N) + one_minus_z = 1.0 - z + + # Vectorized kernel sums for every split at once + KzX = (K @ z.T).T # (P+1, N) row sums restricted to X group + KzY = (K @ one_minus_z.T).T # (P+1, N) row sums restricted to Y group + sum_XX = (z * KzX).sum(1) # within-X (diagonal zeroed) + sum_YY = (one_minus_z * KzY).sum(1) # within-Y + sum_XY = (z * KzY).sum(1) # cross + mmd2_all = sum_XX / (n * (n - 1)) + sum_YY / (m * (m - 1)) - 2.0 * sum_XY / (n * m) + mmd2_all = mmd2_all.cpu().numpy() - null = _mmd2_from_labels(z_null) + observed = float(mmd2_all[0]) + null = mmd2_all[1:] p_value = float((np.sum(null >= observed) + 1) / (n_permutations + 1)) return observed, p_value, null From f85cc7e8107d10e31648b445436bffe9fba75f1b Mon Sep 17 00:00:00 2001 From: Soorya Pradeep Date: Fri, 10 Jul 2026 15:27:41 -0700 Subject: [PATCH 16/89] feat(viscy-utils): add MMD witness function Add witness_function to the MMD module: the per-point empirical witness w(z) = mean_i k(z, x_i) - mean_j k(z, y_j), the RKHS direction along which distributions P (X) and Q (Y) differ. Positive scores lean toward X, negative toward Y. Reuses the GPU RBF-kernel helpers and bandwidth (median-heuristic) convention already in the module, and chunks over query points to bound the intermediate kernel matrices. Tests cover distribution separation, X<->Y antisymmetry, and chunk-size invariance. Co-Authored-By: Claude Opus 4.8 (1M context) --- applications/dynaclr/tests/test_mmd.py | 42 +++++++++++++- .../src/viscy_utils/evaluation/mmd.py | 57 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/applications/dynaclr/tests/test_mmd.py b/applications/dynaclr/tests/test_mmd.py index 1b02196f2..08637a8d8 100644 --- a/applications/dynaclr/tests/test_mmd.py +++ b/applications/dynaclr/tests/test_mmd.py @@ -9,7 +9,12 @@ from dynaclr.evaluation.mmd.compute_mmd import run_mmd_analysis, run_mmd_pooled from dynaclr.evaluation.mmd.config import ComparisonSpec, MMDEvalConfig, MMDPooledConfig, MMDSettings -from viscy_utils.evaluation.mmd import compute_mmd_unbiased, median_heuristic, mmd_permutation_test +from viscy_utils.evaluation.mmd import ( + compute_mmd_unbiased, + median_heuristic, + mmd_permutation_test, + witness_function, +) # --------------------------------------------------------------------------- # Helpers @@ -132,6 +137,41 @@ def test_compute_mmd_unbiased_symmetric(): assert abs(compute_mmd_unbiased(X, Y, bw) - compute_mmd_unbiased(Y, X, bw)) < 1e-10 +def test_witness_function_separates_distributions(): + """Witness scores are positive for X-like points and negative for Y-like points.""" + rng = np.random.default_rng(6) + X = rng.normal(0.0, 1.0, (200, 8)) + Y = rng.normal(5.0, 1.0, (200, 8)) + query = np.vstack([X, Y]) + scores = witness_function(query, X, Y) + assert scores.shape == (len(query),) + assert scores[: len(X)].mean() > 0 > scores[len(X) :].mean() + + +def test_witness_function_antisymmetric(): + """Swapping X and Y negates the witness scores.""" + rng = np.random.default_rng(7) + X = rng.normal(0.0, 1.0, (100, 8)) + Y = rng.normal(3.0, 1.0, (100, 8)) + query = rng.normal(1.5, 1.0, (50, 8)) + bw = median_heuristic(X, Y) + w_xy = witness_function(query, X, Y, bandwidth=bw) + w_yx = witness_function(query, Y, X, bandwidth=bw) + assert np.allclose(w_xy, -w_yx, atol=1e-6) + + +def test_witness_function_chunking_invariant(): + """Chunk size does not change the result.""" + rng = np.random.default_rng(8) + X = rng.normal(0.0, 1.0, (120, 8)) + Y = rng.normal(2.0, 1.0, (120, 8)) + query = rng.normal(1.0, 1.0, (77, 8)) + bw = median_heuristic(X, Y) + small = witness_function(query, X, Y, bandwidth=bw, chunk_size=10) + large = witness_function(query, X, Y, bandwidth=bw, chunk_size=1000) + assert np.allclose(small, large, atol=1e-6) + + # --------------------------------------------------------------------------- # run_mmd_analysis tests # --------------------------------------------------------------------------- diff --git a/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py b/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py index d6c68f01b..eca4b1aae 100644 --- a/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py +++ b/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py @@ -269,3 +269,60 @@ def mmd_permutation_test( null = mmd2_all[1:] p_value = float((np.sum(null >= observed) + 1) / (n_permutations + 1)) return observed, p_value, null + + +def witness_function( + query: NDArray, + X: NDArray, + Y: NDArray, + bandwidth: float | None = None, + chunk_size: int = 500, +) -> NDArray: + """Evaluate the empirical MMD witness function at query points. + + The witness function is the RKHS direction along which distributions P + (sampled by ``X``) and Q (sampled by ``Y``) differ most. Evaluated at a + point z with the Gaussian RBF kernel k: + + w(z) = (1/n) sum_i k(z, x_i) - (1/m) sum_j k(z, y_j) + + A positive score means z looks more like X (P); a negative score means it + looks more like Y (Q). The squared MMD equals the difference in mean witness + score between the two samples, so this is the per-point contribution to the + MMD. + + Query points are processed in chunks so the intermediate kernel matrices + stay bounded regardless of ``len(query)``. + + Parameters + ---------- + query : NDArray + Points at which to evaluate the witness function, shape (q, d). + X : NDArray + Samples from distribution P, shape (n, d). Positive scores lean toward X. + Y : NDArray + Samples from distribution Q, shape (m, d). Negative scores lean toward Y. + bandwidth : float or None + Gaussian RBF bandwidth (sigma^2). None = median heuristic on (X, Y). + chunk_size : int + Number of query points evaluated per batch. + + Returns + ------- + NDArray + Witness scores, shape (q,), float64. + """ + if bandwidth is None: + bandwidth = median_heuristic(X, Y) + device = _get_device() + Xt = torch.from_numpy(np.asarray(X, dtype=np.float32)).to(device) + Yt = torch.from_numpy(np.asarray(Y, dtype=np.float32)).to(device) + q = np.asarray(query, dtype=np.float32) + scores = np.empty(len(q), dtype=np.float64) + for start in range(0, len(q), chunk_size): + sl = slice(start, start + chunk_size) + Qc = torch.from_numpy(q[sl]).to(device) + k_x = _rbf_kernel(Qc, Xt, bandwidth).mean(dim=1) + k_y = _rbf_kernel(Qc, Yt, bandwidth).mean(dim=1) + scores[sl] = (k_x - k_y).double().cpu().numpy() + return scores From fa098320466db0eaf3814e15f717d574605a34fe Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 09:56:20 -0700 Subject: [PATCH 17/89] feat(dynaclr): add MMD-witness weak-label source for linear classifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `label_source: witness` to the linear-classifier eval as a second option to annotation-CSV labels. Per marker, cells are pooled across experiments, control/perturbed references are built from per-experiment wells, the MMD witness is fit and scored per cell, and scores are gated (sign + dead-zone) into control/perturbed pseudo-labels. Everything downstream (train_linear_classifier, publish, append-predictions, plots) is unchanged — only the label source differs. - evaluate_config.py: WitnessLabelSource, WitnessSettings, label_source switch on LinearClassifiersStepConfig (annotations stays the default). - witness_labels.py: well->reference mask (path-component match so C/1 != C/10), witness fit/score, dead-zone gating. - orchestrated.py: branch label assembly into _annotation_run_specs / _witness_run_specs / _build_labeled_adata; training loop unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/dynaclr/evaluation/evaluate_config.py | 120 +++++- .../linear_classifiers/orchestrated.py | 358 +++++++++++------- .../linear_classifiers/witness_labels.py | 165 ++++++++ .../lot_correction/apply_lot_correction.py | 88 ++--- .../evaluation/lot_correction/config.py | 102 +++-- .../lot_correction/fit_lot_correction.py | 138 +++++-- .../lot_correction/lot_correction.py | 269 +++++++------ .../lot_correction/lot_correction_test.py | 152 ++++++++ .../src/dynaclr/evaluation/mmd/compute_mmd.py | 112 +++++- .../src/dynaclr/evaluation/mmd/config.py | 44 ++- .../src/dynaclr/evaluation/mmd/plotting.py | 90 +++++ 11 files changed, 1241 insertions(+), 397 deletions(-) create mode 100644 applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction_test.py diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index 53f1c0da9..77fb9e81e 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -4,7 +4,7 @@ from typing import Literal -from pydantic import BaseModel +from pydantic import BaseModel, model_validator from dynaclr.evaluation.dimensionality_reduction.config import PCAConfig, PHATEConfig, UMAPConfig from dynaclr.evaluation.mmd.config import ComparisonSpec, MAPSettings, MMDSettings @@ -172,6 +172,90 @@ class TaskSpec(BaseModel): marker_filters: list[str] | None = None +class WitnessLabelSource(BaseModel): + """Control/perturbed well spec for one experiment, used to weak-label via the MMD witness. + + The MMD witness function scores each cell by how much it looks like the + control distribution (``control_wells``) vs the perturbed distribution + (``perturbed_wells``). Those scores are then gated into discrete + pseudo-labels that replace annotation-CSV labels for the classifier. + + Parameters + ---------- + experiment : str + Experiment name matching obs["experiment"] in the embeddings zarr. + control_wells : list[str] + Well ids (e.g. ``["C/1"]``) whose cells form the control reference + (witness group X). Matched against ``obs["fov_name"]`` by path prefix, + so ``"C/1"`` matches ``"C/1/000000"``. + perturbed_wells : list[str] + Well ids whose cells form the perturbed reference (witness group Y). + Cells in neither list are dropped (never treated as perturbed by + default), so dead/empty wells do not contaminate the reference. + """ + + experiment: str + control_wells: list[str] + perturbed_wells: list[str] + + @model_validator(mode="after") + def _validate_wells(self) -> "WitnessLabelSource": + if not self.control_wells or not self.perturbed_wells: + raise ValueError(f"{self.experiment}: control_wells and perturbed_wells must both be non-empty") + overlap = set(self.control_wells) & set(self.perturbed_wells) + if overlap: + raise ValueError(f"{self.experiment}: wells appear in both control and perturbed: {sorted(overlap)}") + return self + + +class WitnessSettings(BaseModel): + """Settings for MMD-witness weak labeling. + + Parameters + ---------- + label_column : str + Name of the pseudo-label obs column produced by gating (this is the + ``task`` the classifier trains on). Default: ``"witness_state"``. + control_label : str + Class name assigned to control-like cells (witness score above the + dead-zone). Default: ``"control"``. + perturbed_label : str + Class name assigned to perturbed-like cells (score below the + negative dead-zone). Default: ``"perturbed"``. + dead_zone : float + Fraction in [0, 1). Cells whose ``|witness score|`` falls at or below + the ``dead_zone`` quantile of all ``|witness score|`` are left unlabeled + ("unknown") and dropped from training — the analog of the annotation + path's ``!= "unknown"`` filter. 0.0 disables the dead-zone (plain sign + gating; every cell is labeled). Default: 0.1. + bandwidth : float or None + Gaussian RBF bandwidth for the witness kernel. None = median heuristic + on the pooled (control, perturbed) reference. Default: None. + max_reference_cells : int or None + Subsample each reference group (control, perturbed) to at most this + many cells before fitting the witness (bounds kernel cost). None = + use all. Default: 5000. + marker_filters : list[str] or None + If set, fit/score one witness classifier per listed marker. None + (default) runs one per marker discovered in the data (all unique + obs["marker"] values), matching the annotation path's behavior. + """ + + label_column: str = "witness_state" + control_label: str = "control" + perturbed_label: str = "perturbed" + dead_zone: float = 0.1 + bandwidth: float | None = None + max_reference_cells: int | None = 5000 + marker_filters: list[str] | None = None + + @model_validator(mode="after") + def _validate(self) -> "WitnessSettings": + if not 0.0 <= self.dead_zone < 1.0: + raise ValueError(f"dead_zone must be in [0, 1), got {self.dead_zone}") + return self + + class MMDStepConfig(BaseModel): """Configuration for one MMD evaluation block. @@ -231,11 +315,28 @@ class LinearClassifiersStepConfig(BaseModel): Parameters ---------- + label_source : {"annotations", "witness"} + Where per-cell labels come from. ``"annotations"`` (default) loads + labels from per-experiment annotation CSVs (``annotations`` + ``tasks``). + ``"witness"`` derives weak labels from the MMD witness score using + per-experiment control/perturbed wells (``witness_labels`` + ``witness``), + requiring no annotation CSVs. Everything downstream (classifier training, + publishing, append-predictions, plots) is identical for both. annotations : list[AnnotationSource] Per-experiment annotation CSVs. Each entry maps an experiment name (matching obs["experiment"] in embeddings.zarr) to a CSV path. + Required (with ``tasks``) when ``label_source="annotations"``. tasks : list[TaskSpec] Tasks to evaluate. Each task can optionally filter by marker. + Required (with ``annotations``) when ``label_source="annotations"``. + witness_labels : list[WitnessLabelSource] + Per-experiment control/perturbed well specs. Required when + ``label_source="witness"``. One classifier is trained per marker + (all markers, or the markers named in the witness settings) on the + gated witness pseudo-labels pooled across these experiments. + witness : WitnessSettings + Witness kernel + gating settings. Only used when + ``label_source="witness"``. publish_dir : str or None Central LC registry root for this model (e.g., ``/hpc/projects/.../linear_classifiers/DynaCLR-2D-MIP-BagOfChannels/``). @@ -269,8 +370,11 @@ class LinearClassifiersStepConfig(BaseModel): cell-level stratified ``train_test_split``. Default: None. """ - annotations: list[AnnotationSource] - tasks: list[TaskSpec] + label_source: Literal["annotations", "witness"] = "annotations" + annotations: list[AnnotationSource] = [] + tasks: list[TaskSpec] = [] + witness_labels: list[WitnessLabelSource] = [] + witness: WitnessSettings = WitnessSettings() publish_dir: str | None = None use_scaling: bool = True use_pca: bool = False @@ -282,6 +386,16 @@ class LinearClassifiersStepConfig(BaseModel): random_seed: int = 42 split_groups_by: list[str] | None = None + @model_validator(mode="after") + def _validate_label_source(self) -> "LinearClassifiersStepConfig": + if self.label_source == "annotations": + if not self.annotations or not self.tasks: + raise ValueError("label_source='annotations' requires non-empty annotations and tasks") + else: # witness + if not self.witness_labels: + raise ValueError("label_source='witness' requires non-empty witness_labels") + return self + class AppendPredictionsStepConfig(BaseModel): """Configuration for the append-predictions step. diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py index 70a0bd7b8..fd7c73bbc 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py @@ -42,6 +42,111 @@ from dynaclr.evaluation.evaluate_config import LinearClassifiersStepConfig +def _annotation_run_specs( + config: LinearClassifiersStepConfig, + adata: ad.AnnData, +) -> list[tuple[str, str | None]]: + """Expand (task, marker_filter) runs for the annotation label source. + + Mirrors the original behavior: for each task, ``marker_filters=None`` + expands to every unique ``obs["marker"]``; a list runs one per marker. + """ + specs: list[tuple[str, str | None]] = [] + for task_spec in config.tasks: + runs = ( + task_spec.marker_filters + if task_spec.marker_filters is not None + else sorted(adata.obs["marker"].unique().tolist()) + ) + specs.extend((task_spec.task, m) for m in runs) + return specs + + +def _witness_run_specs( + config: LinearClassifiersStepConfig, + adata: ad.AnnData, +) -> list[tuple[str, str | None]]: + """Expand (task, marker_filter) runs for the witness label source. + + The single synthetic task is ``witness.label_column``; markers come from + ``witness.marker_filters`` (or every unique ``obs["marker"]`` when None). + """ + task = config.witness.label_column + runs = ( + config.witness.marker_filters + if config.witness.marker_filters is not None + else sorted(adata.obs["marker"].unique().tolist()) + ) + return [(task, m) for m in runs] + + +def _build_labeled_adata( + config: LinearClassifiersStepConfig, + adata: ad.AnnData, + task: str, + marker_filter: str | None, +) -> ad.AnnData | None: + """Return the marker-filtered, labeled AnnData for one run, or None if empty. + + Annotation mode joins per-experiment CSVs and keeps rows with a valid + (non-``unknown``) label. Witness mode derives weak labels from the MMD + witness score using per-experiment control/perturbed wells. + """ + import anndata as ad + + if marker_filter is not None: + adata_task = adata[adata.obs["marker"] == marker_filter] + click.echo(f" Filtered to {adata_task.n_obs} cells with marker={marker_filter}") + else: + adata_task = adata + if adata_task.n_obs == 0: + return None + + if config.label_source == "witness": + from dynaclr.evaluation.linear_classifiers.witness_labels import build_witness_labels + + combined = build_witness_labels( + adata_task, + config.witness_labels, + config.witness, + random_seed=config.random_seed, + ) + return combined if combined.n_obs > 0 else None + + # Annotation mode: join CSVs per experiment and collect valid-labeled subsets. + annotated_parts: list[ad.AnnData] = [] + for ann_src in config.annotations: + exp_mask = adata_task.obs["experiment"] == ann_src.experiment + n_exp = int(exp_mask.sum()) + if n_exp == 0: + click.echo(f" Experiment {ann_src.experiment!r}: no matching cells, skipping.") + continue + + adata_exp = adata_task[exp_mask].copy() + ann_path = Path(ann_src.path) + if not ann_path.exists(): + raise FileNotFoundError(f"Annotation CSV not found: {ann_src.path}") + + try: + adata_exp = load_annotation_anndata(adata_exp, str(ann_path), task) + except KeyError: + click.echo(f" Experiment {ann_src.experiment!r}: task {task!r} not in {ann_path.name}, skipping.") + continue + + valid_mask = adata_exp.obs[task].notna() & (adata_exp.obs[task] != "unknown") + n_valid = int(valid_mask.sum()) + if n_valid == 0: + click.echo(f" Experiment {ann_src.experiment!r}: no valid labels for {task!r}, skipping.") + continue + + annotated_parts.append(adata_exp[valid_mask]) + click.echo(f" Experiment {ann_src.experiment!r}: {n_valid}/{n_exp} labeled cells") + + if not annotated_parts: + return None + return annotated_parts[0] if len(annotated_parts) == 1 else ad.concat(annotated_parts, join="outer") + + def run_linear_classifiers( embeddings_path: Path, config: LinearClassifiersStepConfig, @@ -99,161 +204,120 @@ def run_linear_classifiers( # Collect trained (task, marker, pipeline) tuples for publish_dir promotion. trained_pipelines: list[tuple[str, str, Any]] = [] - for task_spec in config.tasks: - task = task_spec.task - # Expand marker_filters: None → all unique markers; list → one run per specified marker - runs: list[str] = ( - task_spec.marker_filters - if task_spec.marker_filters is not None - else sorted(adata.obs["marker"].unique().tolist()) - ) - val_outputs_by_task[task] = [] + # Build the list of (task, marker_filter) runs and, per run, resolve the + # labeled AnnData. Annotation and witness modes differ only here — the + # training/publish/plot path below is shared. + if config.label_source == "witness": + run_specs = _witness_run_specs(config, adata) + else: + run_specs = _annotation_run_specs(config, adata) - for marker_filter in runs: - label = f"{task}" + (f" (marker={marker_filter})" if marker_filter else " (all markers)") - click.echo(f"\n{'=' * 60}") - click.echo(f"Task: {label}") - click.echo("=" * 60) - - # Filter by marker if specified - if marker_filter is not None: - adata_task = adata[adata.obs["marker"] == marker_filter] - click.echo(f" Filtered to {adata_task.n_obs} cells with marker={marker_filter}") - else: - adata_task = adata - - if adata_task.n_obs == 0: - click.echo(f" No cells found for marker_filter={marker_filter!r}, skipping.") - continue + for task in {t for t, _ in run_specs}: + val_outputs_by_task[task] = [] - # Join annotation CSVs per experiment and collect annotated subsets - annotated_parts: list[ad.AnnData] = [] - for ann_src in config.annotations: - exp_mask = adata_task.obs["experiment"] == ann_src.experiment - n_exp = int(exp_mask.sum()) - if n_exp == 0: - click.echo(f" Experiment {ann_src.experiment!r}: no matching cells, skipping.") - continue - - adata_exp = adata_task[exp_mask].copy() - ann_path = Path(ann_src.path) - if not ann_path.exists(): - raise FileNotFoundError(f"Annotation CSV not found: {ann_src.path}") - - try: - adata_exp = load_annotation_anndata(adata_exp, str(ann_path), task) - except KeyError: - click.echo(f" Experiment {ann_src.experiment!r}: task {task!r} not in {ann_path.name}, skipping.") - continue - - valid_mask = adata_exp.obs[task].notna() & (adata_exp.obs[task] != "unknown") - n_valid = int(valid_mask.sum()) - if n_valid == 0: - click.echo(f" Experiment {ann_src.experiment!r}: no valid labels for {task!r}, skipping.") - continue - - annotated_parts.append(adata_exp[valid_mask]) - click.echo(f" Experiment {ann_src.experiment!r}: {n_valid}/{n_exp} labeled cells") - - if not annotated_parts: - click.echo(f" No annotated data found for task {task!r}, skipping.") - continue + for task, marker_filter in run_specs: + label = f"{task}" + (f" (marker={marker_filter})" if marker_filter else " (all markers)") + click.echo(f"\n{'=' * 60}") + click.echo(f"Task: {label}") + click.echo("=" * 60) - combined = annotated_parts[0] if len(annotated_parts) == 1 else ad.concat(annotated_parts, join="outer") - class_dist = combined.obs[task].value_counts().to_dict() - click.echo(f" Total: {combined.n_obs} cells, class distribution: {class_dist}") - - # Build the per-cell group id when split_groups_by is set, so - # GroupShuffleSplit can guarantee no group (e.g. track) lands - # in both train and val. This kills track-level temporal - # leakage that inflates val AUROC for temporal-contrastive - # SSL embeddings. - groups: np.ndarray | None = None - if config.split_groups_by: - missing = [c for c in config.split_groups_by if c not in combined.obs.columns] - if missing: - raise ValueError(f"split_groups_by columns missing from obs: {missing}") - group_series = combined.obs[config.split_groups_by[0]].astype(str) - for col in config.split_groups_by[1:]: - group_series = group_series + "::" + combined.obs[col].astype(str) - groups = group_series.to_numpy() - click.echo( - f" Group-aware split keyed on {config.split_groups_by}: {pd.unique(groups).size} unique groups" - ) + combined = _build_labeled_adata(config, adata, task, marker_filter) + if combined is None or combined.n_obs == 0: + click.echo(f" No labeled data for {label}, skipping.") + continue - classifier_params = { - "max_iter": config.max_iter, - "class_weight": config.class_weight, - "solver": config.solver, - "random_state": config.random_seed, - } + class_dist = combined.obs[task].value_counts().to_dict() + click.echo(f" Total: {combined.n_obs} cells, class distribution: {class_dist}") + + # Build the per-cell group id when split_groups_by is set, so + # GroupShuffleSplit can guarantee no group (e.g. track) lands + # in both train and val. This kills track-level temporal + # leakage that inflates val AUROC for temporal-contrastive + # SSL embeddings. + groups: np.ndarray | None = None + if config.split_groups_by: + missing = [c for c in config.split_groups_by if c not in combined.obs.columns] + if missing: + raise ValueError(f"split_groups_by columns missing from obs: {missing}") + group_series = combined.obs[config.split_groups_by[0]].astype(str) + for col in config.split_groups_by[1:]: + group_series = group_series + "::" + combined.obs[col].astype(str) + groups = group_series.to_numpy() + click.echo(f" Group-aware split keyed on {config.split_groups_by}: {pd.unique(groups).size} unique groups") + + classifier_params = { + "max_iter": config.max_iter, + "class_weight": config.class_weight, + "solver": config.solver, + "random_state": config.random_seed, + } + + try: + pipeline, metrics, val_outputs = train_linear_classifier( + adata=combined, + task=task, + use_scaling=config.use_scaling, + use_pca=config.use_pca, + n_pca_components=config.n_pca_components, + classifier_params=classifier_params, + split_train_data=config.split_train_data, + random_seed=config.random_seed, + groups=groups, + ) + except ValueError as exc: + click.echo(f" Skipping {label}: {exc}") + continue + # Save pipeline for append-predictions step. Always write to the + # local staging dir; promotion to publish_dir (if configured) happens + # atomically after all classifiers finish training. + pipeline_filename = f"{task}_{marker_filter}.joblib" + joblib.dump(pipeline, pipelines_dir / pipeline_filename) + pipeline_manifest.append({"task": task, "marker_filter": marker_filter, "path": pipeline_filename}) + trained_pipelines.append((task, marker_filter, pipeline)) + click.echo(f" Pipeline saved: {pipeline_filename}") + + # Replay the same split to recover val obs (hours_post_perturbation). + # Must mirror train_linear_classifier exactly — same seed, same + # splitter (Group-aware when groups is set, cell-level otherwise). + y_full = combined.obs[task].to_numpy(dtype=object) + val_hours: np.ndarray | None = None + if config.split_train_data < 1.0 and "hours_post_perturbation" in combined.obs.columns: try: - pipeline, metrics, val_outputs = train_linear_classifier( - adata=combined, - task=task, - use_scaling=config.use_scaling, - use_pca=config.use_pca, - n_pca_components=config.n_pca_components, - classifier_params=classifier_params, - split_train_data=config.split_train_data, - random_seed=config.random_seed, - groups=groups, - ) - except ValueError as exc: - click.echo(f" Skipping {label}: {exc}") - continue - - # Save pipeline for append-predictions step. Always write to the - # local staging dir; promotion to publish_dir (if configured) happens - # atomically after all classifiers finish training. - pipeline_filename = f"{task}_{marker_filter}.joblib" - joblib.dump(pipeline, pipelines_dir / pipeline_filename) - pipeline_manifest.append({"task": task, "marker_filter": marker_filter, "path": pipeline_filename}) - trained_pipelines.append((task, marker_filter, pipeline)) - click.echo(f" Pipeline saved: {pipeline_filename}") - - # Replay the same split to recover val obs (hours_post_perturbation). - # Must mirror train_linear_classifier exactly — same seed, same - # splitter (Group-aware when groups is set, cell-level otherwise). - y_full = combined.obs[task].to_numpy(dtype=object) - val_hours: np.ndarray | None = None - if config.split_train_data < 1.0 and "hours_post_perturbation" in combined.obs.columns: - try: - idx = np.arange(len(combined)) - if groups is not None: - gss = GroupShuffleSplit( - n_splits=1, - train_size=config.split_train_data, - random_state=config.random_seed, - ) - _, idx_val = next(gss.split(idx, y_full, groups=groups)) - else: - _, idx_val = train_test_split( - idx, - train_size=config.split_train_data, - random_state=config.random_seed, - stratify=y_full, - shuffle=True, - ) - val_hours = combined.obs["hours_post_perturbation"].to_numpy()[idx_val] - except ValueError: - click.echo(" Could not replay stratified split for val_hours; F1-over-time plot skipped.") - - row = { - "task": task, + idx = np.arange(len(combined)) + if groups is not None: + gss = GroupShuffleSplit( + n_splits=1, + train_size=config.split_train_data, + random_state=config.random_seed, + ) + _, idx_val = next(gss.split(idx, y_full, groups=groups)) + else: + _, idx_val = train_test_split( + idx, + train_size=config.split_train_data, + random_state=config.random_seed, + stratify=y_full, + shuffle=True, + ) + val_hours = combined.obs["hours_post_perturbation"].to_numpy()[idx_val] + except ValueError: + click.echo(" Could not replay stratified split for val_hours; F1-over-time plot skipped.") + + row = { + "task": task, + "marker_filter": marker_filter, + "n_samples": combined.n_obs, + **metrics, + } + all_metrics.append(row) + val_outputs_by_task[task].append( + { "marker_filter": marker_filter, - "n_samples": combined.n_obs, - **metrics, + "val_hours": val_hours, + **val_outputs, } - all_metrics.append(row) - val_outputs_by_task[task].append( - { - "marker_filter": marker_filter, - "val_hours": val_hours, - **val_outputs, - } - ) + ) if not all_metrics: click.echo("\nNo classifiers trained — check annotations and marker filters.") diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py new file mode 100644 index 000000000..7778da100 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py @@ -0,0 +1,165 @@ +"""MMD-witness weak labeling for linear classifiers. + +Derives discrete per-cell pseudo-labels from the MMD witness score instead of +annotation CSVs. For a marker-filtered, cross-experiment pool of embeddings: + +1. Build a control reference (X) and a perturbed reference (Y) from + per-experiment control/perturbed wells. +2. Fit the empirical MMD witness on (X, Y) and score every cell — a signed + scalar measuring how much the cell looks like control (positive) vs + perturbed (negative). +3. Gate the scores into ``control`` / ``perturbed`` labels, dropping an + ambiguous middle band as unlabeled (the analog of the annotation path's + ``!= "unknown"`` filter). + +The output is an AnnData whose ``obs[label_column]`` holds the pseudo-labels, +consumed by the same ``train_linear_classifier`` path as annotation labels. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np +import pandas as pd + +from viscy_utils.evaluation.mmd import median_heuristic, witness_function + +if TYPE_CHECKING: + import anndata as ad + + from dynaclr.evaluation.evaluate_config import WitnessLabelSource, WitnessSettings + + +def _well_prefix_mask(fov_name: pd.Series, wells: list[str]) -> np.ndarray: + """Boolean mask of fov_name entries whose path prefix matches any well id. + + ``fov_name`` values look like ``"C/1/000000"``; a well id ``"C/1"`` matches + any fov whose leading path components equal it. Matching on the ``/``-joined + prefix (rather than ``startswith``) avoids ``"C/1"`` spuriously matching + ``"C/10/..."``. + + Parameters + ---------- + fov_name : pd.Series + obs["fov_name"] values, e.g. ``"C/1/000000"``. + wells : list[str] + Well ids, e.g. ``["C/1", "C/2"]``. + + Returns + ------- + np.ndarray + Boolean mask, shape (len(fov_name),). + """ + stripped = fov_name.astype(object).str.strip("/") + well_set = {w.strip("/") for w in wells} + n_parts = {w.count("/") + 1 for w in well_set} + + def _matches(fov: str) -> bool: + parts = fov.split("/") + return any("/".join(parts[:k]) in well_set for k in n_parts) + + return stripped.map(_matches).to_numpy(dtype=bool) + + +def build_witness_labels( + adata: ad.AnnData, + witness_labels: list[WitnessLabelSource], + settings: WitnessSettings, + random_seed: int = 42, +) -> ad.AnnData: + """Weak-label a marker-filtered embedding pool via the MMD witness score. + + Parameters + ---------- + adata : ad.AnnData + Embeddings already filtered to a single marker. ``obs`` must carry + ``experiment`` and ``fov_name``. + witness_labels : list[WitnessLabelSource] + Per-experiment control/perturbed well specs. + settings : WitnessSettings + Kernel + gating settings. + random_seed : int + Seed for reference subsampling. Default: 42. + + Returns + ------- + ad.AnnData + Subset of ``adata`` containing only the labeled (non-dead-zone) cells, + with the gated pseudo-label written to ``obs[settings.label_column]``. + Empty AnnData if no reference cells were found in either group. + """ + obs = adata.obs + rng = np.random.default_rng(random_seed) + + control_mask = np.zeros(len(obs), dtype=bool) + perturbed_mask = np.zeros(len(obs), dtype=bool) + for src in witness_labels: + exp_mask = (obs["experiment"] == src.experiment).to_numpy(dtype=bool) + if not exp_mask.any(): + continue + fov = obs["fov_name"] + control_mask |= exp_mask & _well_prefix_mask(fov, src.control_wells) + perturbed_mask |= exp_mask & _well_prefix_mask(fov, src.perturbed_wells) + + X_all = adata.X if isinstance(adata.X, np.ndarray) else adata.X.toarray() + X_ctrl = X_all[control_mask] + Y_pert = X_all[perturbed_mask] + if len(X_ctrl) == 0 or len(Y_pert) == 0: + import anndata as ad_ + + return ad_.AnnData( + X=np.empty((0, adata.n_vars), dtype=X_all.dtype), + obs=obs.iloc[:0].copy(), + var=adata.var.copy(), + ) + + X_ref = _subsample(X_ctrl, settings.max_reference_cells, rng) + Y_ref = _subsample(Y_pert, settings.max_reference_cells, rng) + + bandwidth = settings.bandwidth if settings.bandwidth is not None else median_heuristic(X_ref, Y_ref) + scores = witness_function(X_all, X_ref, Y_ref, bandwidth=bandwidth) + + return _gate_scores(adata, scores, settings) + + +def _subsample(X: np.ndarray, max_n: int | None, rng: np.random.Generator) -> np.ndarray: + """Randomly subsample rows of ``X`` to at most ``max_n`` (no-op if None/small).""" + if max_n is None or len(X) <= max_n: + return X + idx = rng.choice(len(X), max_n, replace=False) + return X[idx] + + +def _gate_scores(adata: ad.AnnData, scores: np.ndarray, settings: WitnessSettings) -> ad.AnnData: + """Gate witness scores into pseudo-labels and return only the labeled subset. + + Cells with ``|score|`` at or below the ``dead_zone`` quantile of ``|score|`` + are dropped (ambiguous). Above the dead-zone, sign decides the class: + positive → control, negative → perturbed. + + Parameters + ---------- + adata : ad.AnnData + Marker-filtered embeddings (same order as ``scores``). + scores : np.ndarray + Witness scores, shape (adata.n_obs,). + settings : WitnessSettings + Gating settings. + + Returns + ------- + ad.AnnData + Labeled subset with ``obs[settings.label_column]`` set. + """ + if settings.dead_zone > 0.0: + threshold = float(np.quantile(np.abs(scores), settings.dead_zone)) + else: + threshold = 0.0 + + labeled_mask = np.abs(scores) > threshold if threshold > 0.0 else np.ones(len(scores), dtype=bool) + labels = np.where(scores > 0, settings.control_label, settings.perturbed_label) + + out = adata[labeled_mask].copy() + out.obs[settings.label_column] = pd.Categorical(labels[labeled_mask]) + return out diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py index f42085454..833f4f244 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/apply_lot_correction.py @@ -2,79 +2,81 @@ Usage ----- - dynaclr apply-lot-correction -c config.yaml + dynaclr apply-lot-correction \ + --pipeline lot_pipeline.pkl \ + --input embeddings.zarr \ + --output corrected.zarr -Transforms all cells through StandardScaler → PCA → LOT and writes a new -zarr whose ``.X`` contains the corrected embeddings (shape n_cells × n_pca). -All ``.obs`` metadata from the input zarr is preserved. - -Example config (YAML) ---------------------- - input_zarr: /path/to/lightsheet_organelle.zarr - pipeline: /path/to/lot_pipeline.pkl - output_zarr: /path/to/corrected_organelle.zarr - overwrite: false +Transforms all cells through StandardScaler → (optional PCA) → LOT and writes +a new zarr whose ``.X`` contains the corrected embeddings. When the pipeline +was fit with PCA the output has ``n_pca`` columns; otherwise it keeps the +input feature dimension. All ``.obs`` metadata from the input zarr is preserved. """ import logging from pathlib import Path import click -from pydantic import ValidationError -from dynaclr.evaluation.lot_correction.config import LotApplyConfig from dynaclr.evaluation.lot_correction.lot_correction import ( apply_lot_correction, load_lot_pipeline, ) -from viscy_utils.cli_utils import load_config logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") @click.command(context_settings={"help_option_names": ["-h", "--help"]}) @click.option( - "-c", - "--config", + "--pipeline", + "pipeline_path", + type=click.Path(exists=True, path_type=Path), + required=True, + help="Path to the fitted LOT pipeline (joblib pickle from fit-lot-correction).", +) +@click.option( + "--input", + "input_zarr", type=click.Path(exists=True, path_type=Path), required=True, - help="Path to YAML configuration file.", + help="Path to the embedding zarr to correct.", ) -def main(config: Path): +@click.option( + "--output", + "output_zarr", + type=click.Path(path_type=Path), + required=True, + help="Path to write the corrected embedding zarr.", +) +@click.option( + "--overwrite", + is_flag=True, + default=False, + help="Overwrite the output zarr if it already exists.", +) +def main(pipeline_path: Path, input_zarr: Path, output_zarr: Path, overwrite: bool): """Apply a fitted LOT pipeline to correct batch effects in an embedding zarr.""" click.echo("=" * 60) click.echo("LOT BATCH CORRECTION — APPLY") click.echo("=" * 60) + click.echo(f" Pipeline: {pipeline_path}") + click.echo(f" Input zarr: {input_zarr}") + click.echo(f" Output zarr: {output_zarr}") + click.echo(f" Overwrite: {overwrite}") try: - config_dict = load_config(config) - apply_config = LotApplyConfig(**config_dict) - except ValidationError as e: - click.echo(f"\nConfiguration validation failed:\n{e}", err=True) - raise click.Abort() - except Exception as e: - click.echo(f"\nFailed to load configuration: {e}", err=True) - raise click.Abort() - - click.echo(f"\nConfiguration loaded: {config}") - click.echo(f" Input zarr: {apply_config.input_zarr}") - click.echo(f" Pipeline: {apply_config.pipeline}") - click.echo(f" Output zarr: {apply_config.output_zarr}") - click.echo(f" Overwrite: {apply_config.overwrite}") - - try: - pipeline = load_lot_pipeline(apply_config.pipeline) - click.echo( - f"\nPipeline loaded — n_pca={pipeline['n_pca']}, " - f"PCA variance={pipeline.get('pca_variance_explained', float('nan')):.1f}%" - ) + pipeline = load_lot_pipeline(pipeline_path) + var_exp = pipeline.get("pca_variance_explained") + var_exp_str = "disabled" if var_exp is None else f"{var_exp:.1f}%" + channel = pipeline.get("channel") or "(unspecified)" + click.echo(f"\nPipeline loaded — channel={channel}, n_pca={pipeline['n_pca']}, PCA variance={var_exp_str}") apply_lot_correction( - input_zarr=apply_config.input_zarr, + input_zarr=input_zarr, pipeline=pipeline, - output_zarr=apply_config.output_zarr, - overwrite=apply_config.overwrite, + output_zarr=output_zarr, + overwrite=overwrite, ) - click.echo(f"\nCorrected zarr written to: {apply_config.output_zarr}") + click.echo(f"\nCorrected zarr written to: {output_zarr}") except Exception as e: click.echo(f"\nApplication failed: {e}", err=True) raise click.Abort() diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py index c3c899194..6605cb5e0 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/config.py @@ -45,74 +45,64 @@ def to_dict(self) -> dict: return d -class LotFitConfig(BaseModel): - """Configuration for fitting a LOT batch-correction pipeline. +class DatasetSpec(BaseModel): + """A single embedding zarr plus an optional reference-population filter. Parameters ---------- - source_zarr : str - Path to the source AnnData zarr (e.g. light-sheet embeddings). - target_zarr : str - Path to the target AnnData zarr (e.g. confocal embeddings). - source_uninf_filter : UninfFilter - Filter identifying uninfected cells in the source dataset. - target_uninf_filter : UninfFilter - Filter identifying uninfected cells in the target dataset. - n_pca : int, optional - Number of PCA components for the shared PCA, by default 50. - ns_lot : int, optional - Maximum cells subsampled per dataset for LOT fitting, by default 3000. - random_seed : int, optional - Random seed, by default 42. - output_pipeline : str - Path to save the fitted pipeline (joblib pickle). + zarr : str + Path to an AnnData embedding zarr. + filter : UninfFilter, optional + Filter selecting the reference population (e.g. uninfected cells). + When omitted, all cells in the zarr are used. """ - source_zarr: str = Field(..., min_length=1) - target_zarr: str = Field(..., min_length=1) - source_uninf_filter: UninfFilter - target_uninf_filter: UninfFilter - n_pca: int = Field(default=50, gt=0) - ns_lot: int = Field(default=3000, gt=0) - random_seed: int = Field(default=42) - output_pipeline: str = Field(..., min_length=1) + zarr: str = Field(..., min_length=1) + filter: Optional[UninfFilter] = Field(default=None) @model_validator(mode="after") - def validate_paths(self): - if not Path(self.source_zarr).exists(): - raise ValueError(f"source_zarr not found: {self.source_zarr}") - if not Path(self.target_zarr).exists(): - raise ValueError(f"target_zarr not found: {self.target_zarr}") + def validate_path(self): + if not Path(self.zarr).exists(): + raise ValueError(f"zarr not found: {self.zarr}") return self -class LotApplyConfig(BaseModel): - """Configuration for applying a fitted LOT pipeline to a zarr. +class LotFitConfig(BaseModel): + """Configuration for fitting a LOT batch-correction pipeline. + + Source and target are lists of datasets so multiple acquisitions from the + same platform can be pooled into a single distribution before fitting. Parameters ---------- - input_zarr : str - Path to the source AnnData zarr to correct. - pipeline : str - Path to the fitted pipeline file (joblib pickle). - output_zarr : str - Path to write the corrected AnnData zarr. - overwrite : bool, optional - Overwrite output if it exists, by default False. + source : list[DatasetSpec] + Source datasets (e.g. light-sheet embeddings), each with an optional + reference-population filter. Pooled into one source distribution. + target : list[DatasetSpec] + Target datasets (e.g. confocal embeddings), each with an optional + reference-population filter. Pooled into one target distribution. + channel : str, optional + The bag-of-channels channel/marker these embeddings were computed for + (e.g. ``"Phase3D"``). Recorded in the fitted pipeline for provenance so + the map is not blindly applied to a different channel. By default + ``None``. + n_pca : int or None, optional + Number of PCA components for the shared PCA. Set to ``null`` to + disable PCA and fit LOT in the scaled embedding space. By default 50. + ns_lot : int or None, optional + Maximum cells subsampled per side for LOT fitting (compute cap on + covariance estimation). Set to ``null`` to use all pooled cells. + By default 3000. + random_seed : int, optional + Random seed, by default 42. + output_pipeline : str + Path to save the fitted pipeline (joblib pickle). """ - input_zarr: str = Field(..., min_length=1) - pipeline: str = Field(..., min_length=1) - output_zarr: str = Field(..., min_length=1) - overwrite: bool = Field(default=False) - - @model_validator(mode="after") - def validate_paths(self): - if not Path(self.input_zarr).exists(): - raise ValueError(f"input_zarr not found: {self.input_zarr}") - if not Path(self.pipeline).exists(): - raise ValueError(f"pipeline file not found: {self.pipeline}") - output = Path(self.output_zarr) - if output.exists() and not self.overwrite: - raise ValueError(f"output_zarr already exists: {self.output_zarr}. Set overwrite: true to overwrite.") - return self + source: list[DatasetSpec] = Field(..., min_length=1) + target: list[DatasetSpec] = Field(..., min_length=1) + channel: Optional[str] = Field(default=None) + n_pca: Optional[int] = Field(default=50, gt=0) + ns_lot: Optional[int] = Field(default=3000, gt=0) + random_seed: int = Field(default=42) + output_pipeline: str = Field(..., min_length=1) diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py index 7ecff69b5..2a7738f60 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/fit_lot_correction.py @@ -4,21 +4,32 @@ ----- dynaclr fit-lot-correction -c config.yaml -The fitted pipeline (StandardScaler + PCA + LinearTransport) is saved to -the path specified by ``output_pipeline`` in the config file. +The fitted pipeline (StandardScaler + optional PCA + LinearTransport) is saved +to the path specified by ``output_pipeline`` in the config file. + +Multiple datasets can be pooled per side, and PCA can be disabled by setting +``n_pca: null``. Example config (YAML) --------------------- - source_zarr: /path/to/lightsheet_organelle.zarr - target_zarr: /path/to/confocal_organelle.zarr - source_uninf_filter: - column: fov_name - startswith: - - "C/1/" - target_uninf_filter: - column: fov_name - startswith: - - "G3BP1/uninfected" + source: + - zarr: /path/to/lightsheet_organelle_rep1.zarr + filter: + column: fov_name + startswith: + - "C/1/" + - zarr: /path/to/lightsheet_organelle_rep2.zarr + filter: + column: fov_name + startswith: + - "C/1/" + target: + - zarr: /path/to/confocal_organelle.zarr + filter: + column: fov_name + startswith: + - "G3BP1/uninfected" + channel: Phase3D n_pca: 50 ns_lot: 3000 random_seed: 42 @@ -28,10 +39,12 @@ import logging from pathlib import Path +import anndata as ad import click +import numpy as np from pydantic import ValidationError -from dynaclr.evaluation.lot_correction.config import LotFitConfig +from dynaclr.evaluation.lot_correction.config import DatasetSpec, LotFitConfig from dynaclr.evaluation.lot_correction.lot_correction import ( fit_lot_correction, save_lot_pipeline, @@ -40,6 +53,76 @@ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") +_logger = logging.getLogger(__name__) + + +def _apply_filter(obs, filter_spec: dict) -> np.ndarray: + """Return a boolean mask for rows of *obs* matching *filter_spec*. + + Parameters + ---------- + obs : pd.DataFrame + AnnData ``.obs`` table. + filter_spec : dict + Must contain ``"column"`` plus one of: + + * ``"startswith"`` – str or list[str]: keep rows where the column + value starts with any of the given prefixes. + * ``"equals"`` – str: keep rows where the column value equals the + given string. + + Returns + ------- + np.ndarray of bool + Boolean mask with the same length as *obs*. + """ + col = filter_spec["column"] + values = obs[col].astype(str) + + if "startswith" in filter_spec: + prefixes = filter_spec["startswith"] + if isinstance(prefixes, str): + prefixes = [prefixes] + mask = np.zeros(len(obs), dtype=bool) + for p in prefixes: + mask |= values.str.startswith(p).values + return mask + + if "equals" in filter_spec: + return (values == str(filter_spec["equals"])).values + + raise ValueError(f"filter_spec must contain either 'startswith' or 'equals'. Got: {list(filter_spec.keys())}") + + +def _load_datasets(specs: list[DatasetSpec], side: str) -> list[ad.AnnData]: + """Load and filter each dataset in *specs* to its reference population. + + Parameters + ---------- + specs : list[DatasetSpec] + Dataset specifications (zarr path + optional filter). + side : str + Human-readable label for logging (e.g. ``"source"``). + + Returns + ------- + list[AnnData] + Filtered AnnData objects, one per spec. + """ + adatas = [] + for spec in specs: + _logger.info("Loading %s zarr: %s", side, spec.zarr) + adata = ad.read_zarr(spec.zarr) + adata.obs_names_make_unique() + if spec.filter is not None: + mask = _apply_filter(adata.obs, spec.filter.to_dict()) + _logger.info(" Filtered %s cells: %d / %d", side, int(mask.sum()), adata.n_obs) + adata = adata[mask].copy() + else: + _logger.info(" No filter — using all %d %s cells", adata.n_obs, side) + adatas.append(adata) + return adatas + @click.command(context_settings={"help_option_names": ["-h", "--help"]}) @click.option( @@ -50,7 +133,7 @@ help="Path to YAML configuration file.", ) def main(config: Path): - """Fit a LOT batch-correction pipeline on source and target embedding zarrs.""" + """Fit a LOT batch-correction pipeline on pooled source and target zarrs.""" click.echo("=" * 60) click.echo("LOT BATCH CORRECTION — FIT") click.echo("=" * 60) @@ -66,24 +149,29 @@ def main(config: Path): raise click.Abort() click.echo(f"\nConfiguration loaded: {config}") - click.echo(f" Source zarr: {fit_config.source_zarr}") - click.echo(f" Target zarr: {fit_config.target_zarr}") - click.echo(f" n_pca: {fit_config.n_pca}") - click.echo(f" ns_lot: {fit_config.ns_lot}") - click.echo(f" Random seed: {fit_config.random_seed}") - click.echo(f" Output: {fit_config.output_pipeline}") + click.echo(f" Channel: {fit_config.channel if fit_config.channel is not None else '(unspecified)'}") + click.echo(f" Source datasets: {len(fit_config.source)}") + click.echo(f" Target datasets: {len(fit_config.target)}") + click.echo(f" n_pca: {fit_config.n_pca if fit_config.n_pca is not None else 'disabled'}") + click.echo(f" ns_lot: {fit_config.ns_lot}") + click.echo(f" Random seed: {fit_config.random_seed}") + click.echo(f" Output: {fit_config.output_pipeline}") try: + source_adatas = _load_datasets(fit_config.source, "source") + target_adatas = _load_datasets(fit_config.target, "target") pipeline = fit_lot_correction( - source_zarr=fit_config.source_zarr, - target_zarr=fit_config.target_zarr, - source_uninf_filter=fit_config.source_uninf_filter.to_dict(), - target_uninf_filter=fit_config.target_uninf_filter.to_dict(), + source_adatas=source_adatas, + target_adatas=target_adatas, + channel=fit_config.channel, n_pca=fit_config.n_pca, ns_lot=fit_config.ns_lot, random_seed=fit_config.random_seed, ) - click.echo(f"\nPipeline fitted — PCA explained variance: {pipeline['pca_variance_explained']:.1f}%") + if pipeline["pca_variance_explained"] is not None: + click.echo(f"\nPipeline fitted — PCA explained variance: {pipeline['pca_variance_explained']:.1f}%") + else: + click.echo("\nPipeline fitted — PCA disabled (LOT in scaled embedding space)") save_lot_pipeline(pipeline, fit_config.output_pipeline) click.echo(f"Pipeline saved to: {fit_config.output_pipeline}") except Exception as e: diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py index bc56fed34..d318ed610 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction.py @@ -2,26 +2,34 @@ Pipeline -------- -1. Load source and target embedding zarrs (AnnData format). -2. Filter cells to the uninfected reference population in each dataset. -3. Fit a shared StandardScaler + PCA on the combined source + target cells. -4. Fit a LinearTransport (LOT) map in PCA space using uninfected cells only, - mapping source → target distribution. -5. Save the fitted pipeline (scaler, PCA, LOT) to disk with joblib. +1. Pool one or more pre-filtered source AnnData objects and one or more + pre-filtered target AnnData objects (each already reduced to the + reference population, e.g. uninfected cells). +2. Fit a shared StandardScaler on the combined source + target cells. +3. Optionally fit a shared PCA on the scaled combined cells. +4. Fit a LinearTransport (LOT) map mapping the pooled source distribution to + the pooled target distribution, in PCA space when PCA is enabled or in the + scaled embedding space otherwise. +5. Save the fitted pipeline (scaler, optional PCA, LOT) to disk with joblib. The saved pipeline can then be applied to any source zarr to produce a new -zarr whose embeddings are in the target's PCA coordinate system, corrected -for cross-platform batch effects. +zarr whose embeddings are corrected for cross-platform batch effects (in the +target's PCA coordinate system when PCA is enabled). + +Callers are responsible for loading zarrs and filtering to the reference +population before calling :func:`fit_lot_correction`; see +``fit_lot_correction.py`` for the CLI that does this from a YAML config. """ import logging from pathlib import Path -from typing import Union +from typing import Optional, Union import anndata as ad import joblib import numpy as np import ot +import pandas as pd from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler @@ -33,141 +41,174 @@ def _to_np(X) -> np.ndarray: return np.array(X.toarray() if hasattr(X, "toarray") else X, dtype=np.float32) -def _apply_filter(obs, filter_spec: dict) -> np.ndarray: - """Return a boolean mask for rows of *obs* matching *filter_spec*. +def _is_string_dtype(dtype) -> bool: + """Return True for pandas string extension dtypes that zarr cannot write. + + Covers both ``pd.StringDtype`` (nullable strings) and ``pd.ArrowDtype`` + backed by an Arrow string type, which pandas 3 uses by default and anndata + cannot serialize to zarr. + """ + if isinstance(dtype, pd.StringDtype): + return True + if isinstance(dtype, pd.ArrowDtype): + return pd.api.types.is_string_dtype(dtype) + return False + + +def _coerce_obs_for_zarr(obs: "pd.DataFrame") -> "pd.DataFrame": + """Return a copy of *obs* with string/Arrow dtypes coerced for zarr writing. + + Under pandas 3 / anndata 0.12, ``.obs`` columns are often string extension + dtypes or Categoricals whose categories are string-extension-backed. Zarr + cannot serialize either, so string columns become plain object and + string-backed Categoricals are dropped to plain object (recategorizing keeps + the string-extension-backed categories, which still fail to write). The obs + index is also coerced to object. + """ + obs = obs.copy() + obs.index = obs.index.astype(object) + for col in obs.columns: + dtype = obs[col].dtype + if _is_string_dtype(dtype): + obs[col] = obs[col].astype(object) + elif isinstance(dtype, pd.CategoricalDtype) and _is_string_dtype(dtype.categories.dtype): + obs[col] = obs[col].astype(str).astype(object) + return obs + + +def _pool_embeddings(adatas: list[ad.AnnData]) -> np.ndarray: + """Stack the ``.X`` matrices of several AnnData objects into one array. Parameters ---------- - obs : pd.DataFrame - AnnData ``.obs`` table. - filter_spec : dict - Must contain ``"column"`` plus one of: - - * ``"startswith"`` – str or list[str]: keep rows where the column - value starts with any of the given prefixes. - * ``"equals"`` – str: keep rows where the column value equals the - given string. + adatas : list of AnnData + AnnData objects to pool. Must be non-empty and share the same number + of features (``.n_vars``). Returns ------- - np.ndarray of bool - Boolean mask with the same length as *obs*. + np.ndarray + Row-wise concatenation of every ``.X`` as a float32 array of shape + ``(sum_of_n_obs, n_vars)``. """ - col = filter_spec["column"] - values = obs[col].astype(str) + if not adatas: + raise ValueError("Expected at least one AnnData object to pool, got an empty list.") - if "startswith" in filter_spec: - prefixes = filter_spec["startswith"] - if isinstance(prefixes, str): - prefixes = [prefixes] - mask = np.zeros(len(obs), dtype=bool) - for p in prefixes: - mask |= values.str.startswith(p).values - return mask + n_vars = {a.n_vars for a in adatas} + if len(n_vars) != 1: + raise ValueError(f"All AnnData objects must share the same feature dimension, got n_vars={sorted(n_vars)}.") - if "equals" in filter_spec: - return (values == str(filter_spec["equals"])).values - - raise ValueError(f"filter_spec must contain either 'startswith' or 'equals'. Got: {list(filter_spec.keys())}") + return np.vstack([_to_np(a.X) for a in adatas]) def fit_lot_correction( - source_zarr: Union[str, Path], - target_zarr: Union[str, Path], - source_uninf_filter: dict, - target_uninf_filter: dict, - n_pca: int = 50, - ns_lot: int = 3000, + source_adatas: list[ad.AnnData], + target_adatas: list[ad.AnnData], + channel: Optional[str] = None, + n_pca: Optional[int] = 50, + ns_lot: Optional[int] = 3000, random_seed: int = 42, ) -> dict: - """Fit a shared PCA + LOT batch-correction pipeline. + """Fit a shared (optional PCA) + LOT batch-correction pipeline. + + Both ``source_adatas`` and ``target_adatas`` are expected to be already + filtered to the reference population (e.g. uninfected cells). Multiple + datasets on each side are pooled (row-wise concatenated) before fitting, + so batch effects are estimated against the combined distributions. Parameters ---------- - source_zarr : str or Path - Path to the source AnnData zarr (e.g. light-sheet embeddings). - target_zarr : str or Path - Path to the target AnnData zarr (e.g. confocal embeddings). - source_uninf_filter : dict - Filter spec selecting uninfected source cells used to fit LOT. - target_uninf_filter : dict - Filter spec selecting uninfected target cells used to fit LOT. - n_pca : int, optional - Number of PCA components, by default 50. - ns_lot : int, optional - Maximum number of cells subsampled per dataset for LOT fitting, - by default 3000. + source_adatas : list of AnnData + Pre-filtered source datasets (e.g. light-sheet embeddings). Pooled + into a single source distribution. + target_adatas : list of AnnData + Pre-filtered target datasets (e.g. confocal embeddings). Pooled into + a single target distribution. + channel : str or None, optional + The bag-of-channels channel/marker these embeddings were computed for + (e.g. ``"Phase3D"``). Recorded in the returned pipeline for provenance + so the fitted map is not blindly applied to a different channel. This + is a label only — the caller is responsible for filtering the input + AnnData to this channel. By default ``None``. + n_pca : int or None, optional + Number of PCA components for the shared PCA. When ``None``, PCA is + skipped and LOT is fit in the scaled embedding space. By default 50. + ns_lot : int or None, optional + Maximum number of cells subsampled per side for LOT fitting. This is a + compute cap on covariance estimation, not a source/target balancer: + ``LinearTransport`` only needs each side's mean and covariance, so the + two sides need not have equal counts. When ``None``, every pooled cell + is used. By default 3000. random_seed : int, optional Random seed for reproducibility, by default 42. Returns ------- - dict with keys ``"scaler"``, ``"pca"``, ``"lot"``, ``"n_pca"``, - ``"ns_lot"``, ``"random_seed"``, ``"pca_variance_explained"``. + dict with keys ``"scaler"``, ``"pca"`` (``None`` when disabled), ``"lot"``, + ``"channel"``, ``"n_pca"``, ``"ns_lot"``, ``"random_seed"``, + ``"pca_variance_explained"`` (``None`` when PCA is disabled). """ rng = np.random.default_rng(random_seed) - _logger.info("Loading source zarr: %s", source_zarr) - adata_src = ad.read_zarr(source_zarr) - adata_src.obs_names_make_unique() - - _logger.info("Loading target zarr: %s", target_zarr) - adata_tgt = ad.read_zarr(target_zarr) - adata_tgt.obs_names_make_unique() - - _logger.info("Source shape: %s Target shape: %s", adata_src.shape, adata_tgt.shape) - - X_src = _to_np(adata_src.X) - X_tgt = _to_np(adata_tgt.X) + _logger.info("Fitting LOT for channel: %s", channel if channel is not None else "(unspecified)") - src_uninf_mask = _apply_filter(adata_src.obs, source_uninf_filter) - tgt_uninf_mask = _apply_filter(adata_tgt.obs, target_uninf_filter) + X_src = _pool_embeddings(source_adatas) + X_tgt = _pool_embeddings(target_adatas) _logger.info( - "Uninfected cells — source: %d / %d, target: %d / %d", - src_uninf_mask.sum(), + "Pooled source: %d cells from %d dataset(s); target: %d cells from %d dataset(s)", len(X_src), - tgt_uninf_mask.sum(), + len(source_adatas), len(X_tgt), + len(target_adatas), ) - if src_uninf_mask.sum() < 5 or tgt_uninf_mask.sum() < 5: + if len(X_src) < 5 or len(X_tgt) < 5: raise ValueError( - "Too few uninfected cells to fit LOT " - f"(source={src_uninf_mask.sum()}, target={tgt_uninf_mask.sum()}). " - "Check your filter specifications." + "Too few reference cells to fit LOT " + f"(source={len(X_src)}, target={len(X_tgt)}). " + "Check the datasets and their filtering." ) - _logger.info("Fitting shared StandardScaler + PCA-%d ...", n_pca) + _logger.info("Fitting shared StandardScaler ...") scaler = StandardScaler() X_combined_scaled = scaler.fit_transform(np.vstack([X_src, X_tgt])) - pca = PCA(n_components=n_pca, random_state=random_seed) - Z_all = pca.fit_transform(X_combined_scaled) - var_exp = pca.explained_variance_ratio_.sum() * 100 - _logger.info("PCA explained variance: %.1f%%", var_exp) n_src = len(X_src) - Z_src_uninf = Z_all[:n_src][src_uninf_mask] - Z_tgt_uninf = Z_all[n_src:][tgt_uninf_mask] - - ns_src = min(len(Z_src_uninf), ns_lot) - ns_tgt = min(len(Z_tgt_uninf), ns_lot) - idx_src = rng.choice(len(Z_src_uninf), ns_src, replace=False) - idx_tgt = rng.choice(len(Z_tgt_uninf), ns_tgt, replace=False) + if n_pca is not None: + _logger.info("Fitting shared PCA-%d ...", n_pca) + pca = PCA(n_components=n_pca, random_state=random_seed) + Z_all = pca.fit_transform(X_combined_scaled) + var_exp = float(pca.explained_variance_ratio_.sum() * 100) + _logger.info("PCA explained variance: %.1f%%", var_exp) + else: + _logger.info("PCA disabled — fitting LOT in scaled embedding space.") + pca = None + Z_all = X_combined_scaled + var_exp = None + + Z_src = Z_all[:n_src] + Z_tgt = Z_all[n_src:] + + ns_src = len(Z_src) if ns_lot is None else min(len(Z_src), ns_lot) + ns_tgt = len(Z_tgt) if ns_lot is None else min(len(Z_tgt), ns_lot) + idx_src = rng.choice(len(Z_src), ns_src, replace=False) + idx_tgt = rng.choice(len(Z_tgt), ns_tgt, replace=False) _logger.info("Fitting LOT (source subsample=%d, target subsample=%d) ...", ns_src, ns_tgt) lot = ot.da.LinearTransport(reg=1e-3) - lot.fit(Xs=Z_src_uninf[idx_src], Xt=Z_tgt_uninf[idx_tgt]) + lot.fit(Xs=Z_src[idx_src], Xt=Z_tgt[idx_tgt]) _logger.info("LOT fitted.") return { "scaler": scaler, "pca": pca, "lot": lot, + "channel": channel, "n_pca": n_pca, "ns_lot": ns_lot, "random_seed": random_seed, - "pca_variance_explained": float(var_exp), + "pca_variance_explained": var_exp, } @@ -179,9 +220,12 @@ def apply_lot_correction( ) -> None: """Apply a fitted LOT pipeline to an embedding zarr. - Transforms all cells through StandardScaler → PCA → LOT and writes an - AnnData zarr whose ``.X`` contains the corrected embeddings in the - target's PCA space. All ``.obs`` metadata is preserved. + Transforms all cells through StandardScaler → (optional PCA) → LOT and writes + an AnnData zarr whose ``.X`` contains the corrected embeddings. ``.obs`` and + the input ``.uns`` are preserved (plus a ``uns["lot_correction"]`` provenance + entry). ``obsm`` (e.g. ``X_backbone``, ``X_umap``, ``X_phate``, ``X_pca``), + ``varm``, ``obsp``, and ``layers`` are intentionally dropped: they were + computed in the *uncorrected* space and would contradict the corrected ``.X``. Parameters ---------- @@ -196,8 +240,6 @@ def apply_lot_correction( """ import shutil - import pandas as pd - output_zarr = Path(output_zarr) if output_zarr.exists(): if not overwrite: @@ -215,33 +257,34 @@ def apply_lot_correction( pca = pipeline["pca"] lot = pipeline["lot"] - _logger.info("Applying StandardScaler → PCA → LOT ...") - Z = pca.transform(scaler.transform(X)) + X_scaled = scaler.transform(X) + if pca is not None: + _logger.info("Applying StandardScaler → PCA → LOT ...") + Z = pca.transform(X_scaled) + else: + _logger.info("Applying StandardScaler → LOT (PCA disabled) ...") + Z = X_scaled Z_corrected = lot.transform(Z) - _logger.info("Corrected embeddings shape: %s (n_pca=%d)", Z_corrected.shape, pipeline["n_pca"]) + _logger.info("Corrected embeddings shape: %s (n_pca=%s)", Z_corrected.shape, pipeline["n_pca"]) - obs = adata_in.obs.copy() - for col in obs.columns: - dtype = obs[col].dtype - if isinstance(dtype, pd.StringDtype): - obs[col] = obs[col].astype(object) - elif isinstance(dtype, pd.CategoricalDtype) and isinstance(dtype.categories.dtype, pd.StringDtype): - obs[col] = obs[col].astype(object).astype("category") + obs = _coerce_obs_for_zarr(adata_in.obs) try: ad.settings.allow_write_nullable_strings = True except AttributeError: pass - adata_out = ad.AnnData(X=Z_corrected.astype(np.float32), obs=obs) + adata_out = ad.AnnData(X=Z_corrected.astype(np.float32), obs=obs, uns=dict(adata_in.uns)) + adata_out.var.index = adata_out.var.index.astype(object) adata_out.uns["lot_correction"] = { "source_zarr": str(input_zarr), + "channel": pipeline.get("channel"), "n_pca": pipeline["n_pca"], "pca_variance_explained": pipeline.get("pca_variance_explained"), } _logger.info("Writing corrected zarr: %s", output_zarr) - adata_out.write_zarr(output_zarr) + adata_out.write_zarr(output_zarr, convert_strings_to_categoricals=False) _logger.info("Done.") @@ -275,10 +318,12 @@ def load_lot_pipeline(path: Union[str, Path]) -> dict: Pipeline with keys ``"scaler"``, ``"pca"``, ``"lot"``. """ pipeline = joblib.load(path) + var_exp = pipeline.get("pca_variance_explained") _logger.info( - "Pipeline loaded from %s (n_pca=%d, pca_var=%.1f%%)", + "Pipeline loaded from %s (channel=%s, n_pca=%s, pca_var=%s)", path, + pipeline.get("channel") or "(unspecified)", pipeline["n_pca"], - pipeline.get("pca_variance_explained", float("nan")), + "disabled" if var_exp is None else f"{var_exp:.1f}%", ) return pipeline diff --git a/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction_test.py b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction_test.py new file mode 100644 index 000000000..28ca92307 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/lot_correction/lot_correction_test.py @@ -0,0 +1,152 @@ +"""Integration tests for LOT batch-correction fitting.""" + +import tempfile +from pathlib import Path + +import anndata as ad +import numpy as np +import pandas as pd +import pytest + +from dynaclr.evaluation.lot_correction.lot_correction import ( + _coerce_obs_for_zarr, + _is_string_dtype, + _pool_embeddings, + apply_lot_correction, + fit_lot_correction, + load_lot_pipeline, + save_lot_pipeline, +) + + +def _make_adata(n: int, d: int, seed: int, shift: float = 0.0) -> ad.AnnData: + rng = np.random.default_rng(seed) + X = (rng.standard_normal((n, d)) + shift).astype(np.float32) + obs = pd.DataFrame( + {"fov_name": [f"A/{i % 3}/0" for i in range(n)]}, + index=pd.Index([f"cell_{i}" for i in range(n)], dtype=object), + ) + obs["fov_name"] = obs["fov_name"].astype(object) + return ad.AnnData(X=X, obs=obs) + + +def test_pool_embeddings_concatenates_rows(): + a = _make_adata(10, 8, seed=0) + b = _make_adata(15, 8, seed=1) + pooled = _pool_embeddings([a, b]) + assert pooled.shape == (25, 8) + assert pooled.dtype == np.float32 + + +def test_pool_embeddings_rejects_empty(): + with pytest.raises(ValueError, match="at least one"): + _pool_embeddings([]) + + +def test_pool_embeddings_rejects_mismatched_features(): + with pytest.raises(ValueError, match="same feature dimension"): + _pool_embeddings([_make_adata(5, 8, seed=0), _make_adata(5, 7, seed=1)]) + + +def test_fit_pools_multiple_datasets(): + source = [_make_adata(40, 16, seed=1), _make_adata(30, 16, seed=2)] + target = [_make_adata(50, 16, seed=3, shift=2.0)] + pipeline = fit_lot_correction(source, target, n_pca=5, ns_lot=100, random_seed=0) + + assert pipeline["pca"] is not None + assert pipeline["pca"].n_components_ == 5 + assert pipeline["n_pca"] == 5 + assert 0.0 < pipeline["pca_variance_explained"] <= 100.0 + # PCA fit on pooled 70 source + 50 target cells. + assert pipeline["pca"].n_samples_ == 120 + + +def test_fit_records_channel(): + source = [_make_adata(40, 16, seed=1)] + target = [_make_adata(50, 16, seed=3, shift=2.0)] + pipeline = fit_lot_correction(source, target, channel="Phase3D", n_pca=5, ns_lot=100, random_seed=0) + assert pipeline["channel"] == "Phase3D" + + +def test_channel_defaults_to_none(): + source = [_make_adata(40, 16, seed=1)] + target = [_make_adata(50, 16, seed=3, shift=2.0)] + pipeline = fit_lot_correction(source, target, n_pca=5, ns_lot=100, random_seed=0) + assert pipeline["channel"] is None + + +def test_fit_without_pca(): + source = [_make_adata(40, 16, seed=1)] + target = [_make_adata(50, 16, seed=3, shift=2.0)] + pipeline = fit_lot_correction(source, target, n_pca=None, ns_lot=100, random_seed=0) + + assert pipeline["pca"] is None + assert pipeline["n_pca"] is None + assert pipeline["pca_variance_explained"] is None + + +def test_fit_raises_on_too_few_cells(): + source = [_make_adata(3, 16, seed=1)] + target = [_make_adata(50, 16, seed=3)] + with pytest.raises(ValueError, match="Too few reference cells"): + fit_lot_correction(source, target, n_pca=5, random_seed=0) + + +@pytest.mark.parametrize("n_pca", [5, None]) +def test_fit_save_load_then_apply_to_many(tmp_path, n_pca): + """Fit once, save, reload, then apply the same pipeline to several zarrs.""" + source = [_make_adata(40, 16, seed=1)] + target = [_make_adata(50, 16, seed=3, shift=2.0)] + pipeline = fit_lot_correction(source, target, channel="Phase3D", n_pca=n_pca, ns_lot=100, random_seed=0) + + pipeline_path = tmp_path / "pipeline.pkl" + save_lot_pipeline(pipeline, pipeline_path) + loaded = load_lot_pipeline(pipeline_path) + + assert loaded["n_pca"] == (n_pca if n_pca is not None else None) + assert (loaded["pca"] is not None) == (n_pca is not None) + assert loaded["channel"] == "Phase3D" + + ad.settings.allow_write_nullable_strings = True + expected_dim = n_pca if n_pca is not None else 16 + for i in range(3): + input_zarr = tmp_path / f"input_{i}.zarr" + input_adata = _make_adata(20, 16, seed=100 + i) + input_adata.var.index = input_adata.var.index.astype(object) + input_adata.write_zarr(input_zarr, convert_strings_to_categoricals=False) + output_zarr = tmp_path / f"corrected_{i}.zarr" + apply_lot_correction(input_zarr, loaded, output_zarr) + out = ad.read_zarr(output_zarr) + assert out.shape == (20, expected_dim) + assert out.uns["lot_correction"]["channel"] == "Phase3D" + + +def test_coerce_obs_for_zarr_handles_categorical_string(): + """Categorical-over-string obs must coerce to a zarr-writable object dtype. + + Real embedding zarrs store fov_name/experiment/marker/perturbation as + ``category`` whose categories are the pandas string extension dtype. Zarr + cannot serialize string-extension-backed category values, so the coercion + used by apply must drop them to plain object (recategorizing is not enough — + the categories stay string-extension-backed). Reproduces the apply-time + ``IORegistryError: No method registered for writing ArrowStringArray``. + """ + obs = pd.DataFrame(index=pd.Index([f"cell_{i}" for i in range(6)])) + obs["experiment"] = pd.Series(["exp_a"] * 6, index=obs.index, dtype="string").astype("category") + obs["marker"] = pd.Series(["SEC61B"] * 6, index=obs.index, dtype="string").astype("category") + obs["track_id"] = np.arange(6, dtype=np.int32) + assert _is_string_dtype(obs["experiment"].dtype.categories.dtype) + + coerced = _coerce_obs_for_zarr(obs) + + # String-backed categoricals became plain object; numeric column untouched. + assert coerced["experiment"].dtype == object + assert coerced["marker"].dtype == object + assert coerced["track_id"].dtype == np.int32 + assert list(coerced["experiment"]) == ["exp_a"] * 6 + + # The coerced frame round-trips through zarr, which the categorical did not. + adata = ad.AnnData(X=np.zeros((6, 3), dtype=np.float32), obs=coerced) + adata.var.index = adata.var.index.astype(object) + with tempfile.TemporaryDirectory() as d: + adata.write_zarr(Path(d) / "roundtrip.zarr", convert_strings_to_categoricals=False) diff --git a/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py b/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py index c08fdc40b..e70455b53 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py +++ b/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py @@ -13,6 +13,7 @@ ComparisonSpec, MMDCombinedConfig, MMDEvalConfig, + MMDOverTimeConfig, MMDPooledConfig, MMDSettings, _resolve_bin_edges, @@ -399,9 +400,12 @@ def run_mmd_combined(config: MMDCombinedConfig) -> pd.DataFrame: """Run pairwise cross-experiment MMD, faceted by marker and condition+time bin. For each marker, finds all experiments that share it, then for each pair - of those experiments runs MMD per (condition, time_bin) after centering - within that pair only. This measures batch effects between experiments - at matched biological states. + of those experiments runs MMD per (condition, time_bin). When + ``config.center_per_experiment`` is True (default) each experiment is + mean-centered first, measuring *residual* batch effects independent of a + global offset; set it False to keep the raw mean shift between experiments + (needed to validate a LOT correction that removes that offset). This + measures batch effects between experiments at matched biological states. Parameters ---------- @@ -451,8 +455,9 @@ def run_mmd_combined(config: MMDCombinedConfig) -> pd.DataFrame: obs_a = adata_a.obs obs_b = adata_b.obs - emb_a_full = emb_a_full - emb_a_full.mean(axis=0) - emb_b_full = emb_b_full - emb_b_full.mean(axis=0) + if config.center_per_experiment: + emb_a_full = emb_a_full - emb_a_full.mean(axis=0) + emb_b_full = emb_b_full - emb_b_full.mean(axis=0) conditions = sorted(set(obs_a[config.group_by].unique()) & set(obs_b[config.group_by].unique())) for condition in conditions: @@ -522,6 +527,47 @@ def run_mmd_combined(config: MMDCombinedConfig) -> pd.DataFrame: return pd.DataFrame(records) +def run_mmd_over_time(config: MMDOverTimeConfig) -> pd.DataFrame: + """Run combined cross-experiment MMD on pre- and post-correction embeddings. + + Runs :func:`run_mmd_combined` twice — once on ``input_paths`` (uncorrected, + ``correction="pre"``) and once on ``corrected_paths`` (LOT-corrected, + ``correction="post"``) — using identical settings, then concatenates the + results with a ``correction`` column so the batch effect before and after + correction can be compared over time in a single output. + + Parameters + ---------- + config : MMDOverTimeConfig + Over-time analysis configuration (combined config + ``corrected_paths``). + + Returns + ------- + pd.DataFrame + Same columns as :func:`run_mmd_combined` plus a ``correction`` column + (``"pre"`` / ``"post"``) and a ``pair_kind`` column (``"cross"`` for + source↔target pairs, ``"within"`` for source↔source pairs). + """ + base = config.model_dump(exclude={"corrected_paths", "target_experiments"}) + + pre = run_mmd_combined(MMDCombinedConfig(**base)) + pre["correction"] = "pre" + + post = run_mmd_combined(MMDCombinedConfig(**{**base, "input_paths": config.corrected_paths})) + post["correction"] = "post" + + df = pd.concat([pre, post], ignore_index=True) + + targets = set(config.target_experiments or []) + if targets: + involves_target = df["exp_a"].isin(targets) | df["exp_b"].isin(targets) + df["pair_kind"] = np.where(involves_target, "cross", "within") + else: + df["pair_kind"] = "cross" + + return df + + def _combined_record( marker: str, exp_a: str, @@ -789,19 +835,35 @@ def plot_mmd_heatmap_cmd(mmd_dir: Path, output_dir: Path | None) -> None: default=False, help="Run pooled multi-experiment phenotypic analysis (config must have input_paths list)", ) -def main(config: Path, combined: bool, pooled: bool) -> None: +@click.option( + "--over-time", + "over_time", + is_flag=True, + default=False, + help="Run pre/post-correction combined MMD over time (config needs input_paths + corrected_paths)", +) +def main(config: Path, combined: bool, pooled: bool, over_time: bool) -> None: """Compute MMD between explicit condition pairs in cell embeddings. Comparisons are defined as explicit (cond_a, cond_b, label) pairs. The analysis is always faceted by obs["marker"]. """ - if combined and pooled: - raise click.UsageError("--combined and --pooled are mutually exclusive") + if sum([combined, pooled, over_time]) > 1: + raise click.UsageError("--combined, --pooled, and --over-time are mutually exclusive") raw = load_composed_config(config) output_dir = Path(raw["output_dir"]) output_dir.mkdir(parents=True, exist_ok=True) - if combined: + if over_time: + cfg = MMDOverTimeConfig(**raw) + df = run_mmd_over_time(cfg) + out_csv = output_dir / "over_time_mmd_results.csv" + df.to_csv(out_csv, index=False) + click.echo(f"Saved: {out_csv}") + if cfg.save_plots and len(df): + _save_plots_over_time(df, output_dir, cfg.temporal_bin_size) + _print_summary(df, mode="over_time") + elif combined: cfg = MMDCombinedConfig(**raw) df = run_mmd_combined(cfg) out_csv = output_dir / "combined_mmd_results.csv" @@ -869,6 +931,19 @@ def _save_plots_combined(df: pd.DataFrame, output_dir: Path, temporal_bin_size: plot_mmd_combined_heatmap(df, output_dir / f"combined_heatmap.{fmt}") +def _save_plots_over_time(df: pd.DataFrame, output_dir: Path, temporal_bin_size: float | None) -> None: + from dynaclr.evaluation.mmd.plotting import plot_mmd_pre_post_kinetics + + has_bins = temporal_bin_size is not None and len(df) and not df["hours_bin_start"].isna().all() + if not has_bins: + return + for marker in df["marker"].unique(): + sub = df[df["marker"] == marker] + safe = marker.replace(" ", "_").replace("/", "-") + for fmt in ("pdf", "png"): + plot_mmd_pre_post_kinetics(sub, output_dir / f"over_time_{safe}_kinetics.{fmt}") + + def _save_plots_pooled(df: pd.DataFrame, output_dir: Path) -> None: from dynaclr.evaluation.mmd.plotting import ( plot_activity_heatmap, @@ -897,7 +972,24 @@ def _print_summary(df: pd.DataFrame, mode: str = "per_experiment") -> None: click.echo("No results.") return click.echo("\n## MMD Results Summary\n") - if mode == "combined": + if mode == "over_time": + keys = ( + ["marker", "pair_kind", "condition", "correction"] + if "pair_kind" in df.columns + else [ + "marker", + "condition", + "correction", + ] + ) + summary = ( + df.dropna(subset=["mmd2"]) + .groupby(keys)[["mmd2", "p_value", "effect_size"]] + .agg({"mmd2": "mean", "p_value": "min", "effect_size": "mean"}) + .round(4) + .reset_index() + ) + elif mode == "combined": summary = ( df.dropna(subset=["mmd2"]) .groupby(["marker", "condition"])[["mmd2", "p_value", "effect_size"]] diff --git a/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py b/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py index 561406aee..296c59db9 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py @@ -180,15 +180,57 @@ class MMDCombinedConfig(_MMDBaseConfig): Conditions are auto-discovered from the data intersection — no explicit comparisons needed. For each marker shared between a pair of experiments, - runs MMD per (condition, time_bin) after per-experiment mean centering. + runs MMD per (condition, time_bin). Parameters ---------- input_paths : list[str] Paths to per-experiment AnnData zarr stores. + center_per_experiment : bool + Subtract each experiment's own mean embedding before computing MMD. + Default True detects *residual* batch effects independent of a global + offset. Set False to keep the raw mean shift between experiments — this + is required to validate a LOT correction whose main job is removing that + offset (centering would delete the very effect being measured, so a + genuine platform separation would collapse to a small MMD). Default: True. """ input_paths: list[str] + center_per_experiment: bool = True + + +class MMDOverTimeConfig(MMDCombinedConfig): + """Pre/post batch-effect MMD over time in a single run. + + Runs the combined cross-experiment MMD (per marker × condition × time bin) + on both the uncorrected embeddings (``input_paths``) and their LOT-corrected + counterparts (``corrected_paths``), tags each result set with a + ``correction`` column (``"pre"`` / ``"post"``), and returns one combined + DataFrame — so the batch effect before and after correction can be plotted + as two series over time instead of living in two separate output folders. + + Parameters + ---------- + corrected_paths : list[str] + Paths to the LOT-corrected per-experiment AnnData zarr stores. Should + cover the same experiments as ``input_paths`` (matched by + ``obs["experiment"]``, not list order). + target_experiments : list[str] or None + ``obs["experiment"]`` value(s) of the target/reference platform (v2). + Used to tag each experiment pair as ``pair_kind="cross"`` (source↔target, + the batch effect being corrected) vs ``"within"`` (source↔source, the + within-platform baseline). When None, all pairs are ``"cross"``. + Default: None. + """ + + corrected_paths: list[str] + target_experiments: list[str] | None = None + + @model_validator(mode="after") + def _validate_over_time(self) -> "MMDOverTimeConfig": + if not self.corrected_paths: + raise ValueError("corrected_paths must not be empty") + return self class MMDPooledConfig(_MMDBaseConfig): diff --git a/applications/dynaclr/src/dynaclr/evaluation/mmd/plotting.py b/applications/dynaclr/src/dynaclr/evaluation/mmd/plotting.py index 9828f0711..d5a233c40 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/mmd/plotting.py +++ b/applications/dynaclr/src/dynaclr/evaluation/mmd/plotting.py @@ -68,6 +68,96 @@ def plot_mmd_kinetics(df: pd.DataFrame, output_path: Path) -> None: plt.close(fig) +def plot_mmd_pre_post_kinetics(df: pd.DataFrame, output_path: Path) -> None: + """Plot MMD over time as four lines per condition (mean MMD² per bin): + + * **v1 vs v1 (uncorrected floor)** (solid gray) — source↔source on the RAW + uncorrected embeddings. This is the fixed within-platform reference: the + residual distance between same-platform acquisitions. The corrected + cross-platform curve should approach THIS line. + * **v1 vs v1 (after LOT→target)** (dashed gray) — source↔source AFTER both + were LOT-mapped to the target. NOT a stable baseline (it lives in the + corrected PCA space and LOT maps v1→target, not v1→v1); shown only for + reference / honesty, not as the floor to compare against. + * **v1 vs v2 (pre)** (red) — source↔target before correction: the batch effect. + * **v1 vs v2 (post)** (blue) — source↔target after LOT correction. + + The batch-correction story: the blue (post) curve should drop from the red + (pre) curve toward the SOLID gray (uncorrected within-platform) floor and stay + flat over time. When ``pair_kind`` is absent all pairs are treated as cross and + only the pre/post lines are drawn. + + Parameters + ---------- + df : pd.DataFrame + Over-time MMD results for a single marker, with columns: + condition, correction, pair_kind, hours_bin_start, hours_bin_end, + mmd2, p_value. + output_path : Path + Output file path. Format inferred from suffix (.pdf or .png). + """ + df = df.copy().dropna(subset=["hours_bin_start", "hours_bin_end"]) + if df.empty: + return + if "pair_kind" not in df.columns: + df["pair_kind"] = "cross" + df["bin_mid"] = (df["hours_bin_start"] + df["hours_bin_end"]) / 2 + + cross = df[df["pair_kind"] == "cross"] + within = df[df["pair_kind"] == "within"] + + conditions = sorted(cross["condition"].unique()) + n_conds = len(conditions) + if n_conds == 0: + return + fig, axes = plt.subplots(1, n_conds, figsize=(max(4 * n_conds, 5), 4), squeeze=False, sharey=True) + marker_name = df["marker"].iloc[0] if "marker" in df.columns else "" + + def _line(ax, sub, label, color, linestyle="-"): + """Mean MMD² per bin as a line, with BH-significance stars.""" + sub = sub.sort_values("bin_mid") + if sub.empty: + return + agg = sub.groupby("bin_mid", as_index=False).agg(mmd2=("mmd2", "mean"), p_value=("p_value", "min")) + ax.plot(agg["bin_mid"], agg["mmd2"], marker="o", label=label, color=color, linestyle=linestyle) + for row, s in zip(agg.itertuples(), _bh_significance(agg["p_value"].to_numpy())): + if s: + ax.text(row.bin_mid, row.mmd2, "*", ha="center", va="bottom", color=color, fontsize=12) + + # Two same-space comparisons (MMD is only comparable WITHIN a space, since raw + # is 768-dim and corrected is n_pca-dim): + # RAW space → cross/pre (red solid) vs within/pre (red dashed, floor) + # CORRECTED space→ cross/post (blue solid) vs within/post (blue dashed, floor) + # Read post's success as blue-solid approaching blue-dashed, NOT the red floor. + for ax, condition in zip(axes[0], conditions): + cond_within = within[within["condition"] == condition] + cond_cross = cross[cross["condition"] == condition] + # Raw space (pre) — warm. + _line(ax, cond_within[cond_within["correction"] == "pre"], "v1↔v1 raw (floor)", "lightcoral", linestyle="--") + _line(ax, cond_cross[cond_cross["correction"] == "pre"], "v1↔v2 pre (raw)", "tab:red") + # Corrected space (post) — cool. + _line( + ax, + cond_within[cond_within["correction"] == "post"], + "v1↔v1 corrected (floor)", + "lightskyblue", + linestyle="--", + ) + _line(ax, cond_cross[cond_cross["correction"] == "post"], "v1↔v2 post (corrected)", "tab:blue") + + ax.set_title(condition) + ax.set_xlabel("Hours post perturbation (bin midpoint)") + ax.axhline(0, color="gray", linewidth=0.8, linestyle="--") + sns.despine(ax=ax) + + axes[0][0].set_ylabel("MMD²") + axes[0][-1].legend(bbox_to_anchor=(1.01, 1), loc="upper left", fontsize=9) + fig.suptitle(f"Cross-platform batch effect over time — {marker_name}") + fig.tight_layout() + fig.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close(fig) + + def plot_mmd_combined_heatmap(df: pd.DataFrame, output_path: Path) -> None: """Plot combined cross-experiment MMD heatmap: markers × experiment pairs. From c9d67ecb44d4c5407d828b96a5d3d669074a8545 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 10:02:19 -0700 Subject: [PATCH 18/89] test(dynaclr): cover MMD-witness weak labeling for linear classifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five integration tests exercising the real code path: - well-prefix match rejects C/1 vs C/10 confusion - witness scores separate control/perturbed clusters - dead-zone drops ambiguous near-origin cells - empty AnnData when a reference group has no cells - end-to-end run_linear_classifiers witness mode → metrics + summary PDF Co-Authored-By: Claude Opus 4.8 (1M context) --- .../linear_classifiers/witness_labels_test.py | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py new file mode 100644 index 000000000..d72b62b78 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py @@ -0,0 +1,139 @@ +"""Tests for MMD-witness weak labeling of linear classifiers.""" + +from pathlib import Path + +import anndata as ad +import numpy as np +import pandas as pd + +from dynaclr.evaluation.evaluate_config import ( + LinearClassifiersStepConfig, + WitnessLabelSource, + WitnessSettings, +) +from dynaclr.evaluation.linear_classifiers.orchestrated import run_linear_classifiers +from dynaclr.evaluation.linear_classifiers.witness_labels import ( + _well_prefix_mask, + build_witness_labels, +) + + +def _make_separable_embeddings( + path: Path | None, + n_per_well: int = 60, + n_features: int = 16, + experiment: str = "exp_A", + marker: str = "Phase3D", +) -> ad.AnnData: + """Embeddings with control well A/1 and perturbed well B/2 well-separated in feature space. + + Control cells cluster near +2 on feature 0, perturbed near -2, so the + witness cleanly assigns positive scores to control and negative to + perturbed. An unrelated well C/3 sits at the origin (ambiguous). + """ + rng = np.random.default_rng(0) + wells = ["A/1"] * n_per_well + ["B/2"] * n_per_well + ["C/3"] * n_per_well + total = len(wells) + X = rng.standard_normal((total, n_features)).astype(np.float32) * 0.3 + X[:n_per_well, 0] += 2.0 # control + X[n_per_well : 2 * n_per_well, 0] -= 2.0 # perturbed + # C/3 stays near origin + + obs = pd.DataFrame( + { + "fov_name": [f"{w}/000000" for w in wells], + "t": [i % 5 for i in range(total)], + "track_id": list(range(total)), + "experiment": [experiment] * total, + "marker": [marker] * total, + "hours_post_perturbation": [float(i % 5) * 24.0 for i in range(total)], + } + ) + # pandas 3 defaults string columns to ArrowStringArray, which anndata's + # zarr writer cannot serialize — cast to object (matches orchestrated_test). + for col in obs.select_dtypes("string").columns: + obs[col] = obs[col].astype(object) + obs.index = pd.Index([str(i) for i in range(total)], dtype=object) + var = pd.DataFrame(index=pd.Index([str(i) for i in range(n_features)], dtype=object)) + adata = ad.AnnData(X=X, obs=obs, var=var) + if path is not None: + adata.write_zarr(path) + return adata + + +def test_well_prefix_mask_no_spurious_prefix_match(): + """'A/1' must not match 'A/10/...' — matching is on path components, not string prefix.""" + fov = pd.Series(["A/1/000000", "A/10/000000", "B/2/000000"]) + mask = _well_prefix_mask(fov, ["A/1"]) + assert mask.tolist() == [True, False, False] + + +def test_build_witness_labels_separates_control_and_perturbed(): + """Witness scores gate the two separated clusters into control/perturbed.""" + adata = _make_separable_embeddings(None) + labels = build_witness_labels( + adata, + [WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"])], + WitnessSettings(dead_zone=0.0), + ) + # Every cell labeled (dead_zone=0), two classes present. + assert labels.n_obs == adata.n_obs + col = labels.obs["witness_state"] + assert set(col.unique()) == {"control", "perturbed"} + + # Control well cells score as control; perturbed well cells as perturbed. + is_ctrl_well = labels.obs["fov_name"].str.startswith("A/1") + is_pert_well = labels.obs["fov_name"].str.startswith("B/2") + assert (col[is_ctrl_well] == "control").mean() > 0.95 + assert (col[is_pert_well] == "perturbed").mean() > 0.95 + + +def test_build_witness_labels_dead_zone_drops_ambiguous(): + """A positive dead-zone drops the lowest-|score| cells (the ambiguous C/3 cluster).""" + adata = _make_separable_embeddings(None) + labels = build_witness_labels( + adata, + [WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"])], + WitnessSettings(dead_zone=0.3), + ) + assert labels.n_obs < adata.n_obs + # Dropped cells should be disproportionately the near-origin C/3 well. + kept_wells = labels.obs["fov_name"].str.split("/").str[0] + assert (kept_wells == "C").mean() < (1.0 / 3.0) + + +def test_build_witness_labels_empty_when_reference_missing(): + """No control or no perturbed cells → empty AnnData (skipped downstream).""" + adata = _make_separable_embeddings(None) + labels = build_witness_labels( + adata, + [WitnessLabelSource(experiment="exp_A", control_wells=["Z/9"], perturbed_wells=["B/2"])], + WitnessSettings(), + ) + assert labels.n_obs == 0 + + +def test_run_linear_classifiers_witness_mode(tmp_path): + """End-to-end witness path: config → weak labels → trained classifier + metrics.""" + zarr_path = tmp_path / "embeddings.zarr" + _make_separable_embeddings(zarr_path) + + config = LinearClassifiersStepConfig( + label_source="witness", + witness_labels=[ + WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"]), + ], + witness=WitnessSettings(marker_filters=["Phase3D"], dead_zone=0.1), + split_train_data=0.8, + ) + + results = run_linear_classifiers(zarr_path, config, tmp_path / "out") + + assert len(results) == 1 + assert results.iloc[0]["task"] == "witness_state" + assert results.iloc[0]["marker_filter"] == "Phase3D" + # Separable control/perturbed clusters → classifier well above chance. + # (The ambiguous C/3 well, sign-labeled, caps this below 1.0.) + assert results.iloc[0]["val_accuracy"] > 0.8 + assert (tmp_path / "out" / "metrics_summary.csv").exists() + assert (tmp_path / "out" / "witness_state_summary.pdf").exists() From c643e950b47a51a13a3d81d9a636b2f95e175abc Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 10:19:44 -0700 Subject: [PATCH 19/89] config(dynaclr): add witness-labeled LC recipe for infectomics Mirrors linear_classifiers_infectomics.yml but with label_source: witness. Per-experiment control/perturbed wells replace annotation CSVs; wells are templates to edit per plate layout. One classifier per marker (G3BP1, SEC61B, Phase3D, viral_sensor), dead_zone 0.1, track-level split. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...linear_classifiers_witness_infectomics.yml | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml diff --git a/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml new file mode 100644 index 000000000..d0d2217f5 --- /dev/null +++ b/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml @@ -0,0 +1,65 @@ +# Linear classifier settings for the infectomics benchmark — WITNESS label source. +# +# Second option to `linear_classifiers_infectomics.yml`: instead of loading +# per-cell labels from annotation CSVs, this derives weak labels from the MMD +# witness score. For each marker, the witness is fit on control-vs-perturbed +# cells pooled across all listed experiments, every cell is scored, and the +# scores are gated into control/perturbed pseudo-labels. No annotation CSVs +# are needed — only which wells are control and which are perturbed per +# experiment. +# +# The control/perturbed wells below are TEMPLATES — edit them to match each +# experiment's plate layout (mock/uninfected wells → control_wells; infected +# wells → perturbed_wells). Cells in neither list are dropped, so dead/empty +# wells never leak into the perturbed reference. +linear_classifiers: + label_source: witness + witness_labels: + - experiment: "2025_01_28_A549_G3BP1_ZIKV_DENV_G3BP1" + control_wells: ["B/4"] # mock / uninfected + perturbed_wells: ["C/2", "C/3"] # ZIKV / DENV + - experiment: "2025_01_28_A549_viral_sensor_ZIKV_DENV" + control_wells: ["B/4"] + perturbed_wells: ["C/2", "C/3"] + - experiment: "2025_01_28_A549_Phase3D_ZIKV_DENV" + control_wells: ["B/4"] + perturbed_wells: ["C/2", "C/3"] + - experiment: "2025_07_24_A549_G3BP1_ZIKV" + control_wells: ["C/1"] # mock (pseudo-control well) + perturbed_wells: ["C/2", "C/3"] # ZIKV + - experiment: "2025_07_24_A549_SEC61_ZIKV" + control_wells: ["A/1"] # mock well for the SEC61 layout + perturbed_wells: ["A/2", "A/3"] + - experiment: "2025_07_24_A549_viral_sensor_ZIKV" + control_wells: ["C/1"] + perturbed_wells: ["C/2", "C/3"] + - experiment: "2025_07_24_A549_Phase3D_ZIKV" + control_wells: ["C/1"] + perturbed_wells: ["C/2", "C/3"] + witness: + # One classifier per marker; None (omit) = every unique obs["marker"]. + marker_filters: + - G3BP1 + - SEC61B + - Phase3D + - viral_sensor + label_column: witness_state + control_label: control + perturbed_label: perturbed + # Drop the lowest-|score| 10% as unlabeled (analog of the annotation + # path's `!= "unknown"` filter). Set to 0.0 to label every cell by sign. + dead_zone: 0.1 + # None = median heuristic on the pooled (control, perturbed) reference. + bandwidth: null + # Bound kernel cost: subsample each reference group to at most N cells. + max_reference_cells: 5000 + use_scaling: true + use_pca: false + split_train_data: 0.8 + random_seed: 42 + # Track-level split — no track lands in both train/val. Same rationale as + # the annotation recipe (temporal-contrastive SSL leaks on cell-level splits). + split_groups_by: + - experiment + - fov_name + - track_id From bfc19367624d50b35b3ccbc542ca98f41d05fd46 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 10:22:27 -0700 Subject: [PATCH 20/89] docs(dynaclr): witness-score LC DAG with flow diagrams and mock plots - witness_score_classifiers.md: full DAG (why, step-by-step, gating rule, config, notes) plus embedded visuals. - visuals/: three Graphviz flow diagrams (data flow, gating, pipeline) as .dot + png + pdf, and four mock example-output plots (witness-score histogram, per-marker metrics bar, ROC, F1-over-time) with a reproducible generator (mock_witness_plots.py). Mock plots are watermarked synthetic. - Cross-links from evaluation.md and linear_classifiers/README.md so the new DAG is discoverable from the annotation path. Co-Authored-By: Claude Opus 4.8 (1M context) --- applications/dynaclr/docs/DAGs/evaluation.md | 2 + .../docs/DAGs/visuals/mock_f1_over_time.pdf | Bin 0 -> 24045 bytes .../docs/DAGs/visuals/mock_f1_over_time.png | Bin 0 -> 60112 bytes .../docs/DAGs/visuals/mock_metrics_bar.pdf | Bin 0 -> 26342 bytes .../docs/DAGs/visuals/mock_metrics_bar.png | Bin 0 -> 44174 bytes .../docs/DAGs/visuals/mock_roc_curves.pdf | Bin 0 -> 26285 bytes .../docs/DAGs/visuals/mock_roc_curves.png | Bin 0 -> 90478 bytes .../docs/DAGs/visuals/mock_witness_plots.py | 129 ++++++++++++ .../DAGs/visuals/mock_witness_score_hist.pdf | Bin 0 -> 26703 bytes .../DAGs/visuals/mock_witness_score_hist.png | Bin 0 -> 55656 bytes .../docs/DAGs/visuals/witness_dataflow.dot | 50 +++++ .../docs/DAGs/visuals/witness_dataflow.pdf | Bin 0 -> 40647 bytes .../docs/DAGs/visuals/witness_dataflow.png | Bin 0 -> 199478 bytes .../docs/DAGs/visuals/witness_gating.dot | 30 +++ .../docs/DAGs/visuals/witness_gating.pdf | Bin 0 -> 39358 bytes .../docs/DAGs/visuals/witness_gating.png | Bin 0 -> 66884 bytes .../docs/DAGs/visuals/witness_pipeline.dot | 23 +++ .../docs/DAGs/visuals/witness_pipeline.pdf | Bin 0 -> 35886 bytes .../docs/DAGs/visuals/witness_pipeline.png | Bin 0 -> 47573 bytes .../docs/DAGs/witness_score_classifiers.md | 193 ++++++++++++++++++ .../dynaclr/docs/linear_classifiers/README.md | 5 + 21 files changed, 432 insertions(+) create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.png create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.pdf create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.png create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.png create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf create mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_dataflow.dot create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_dataflow.png create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_gating.dot create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_gating.pdf create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_gating.png create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_pipeline.dot create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_pipeline.pdf create mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_pipeline.png create mode 100644 applications/dynaclr/docs/DAGs/witness_score_classifiers.md diff --git a/applications/dynaclr/docs/DAGs/evaluation.md b/applications/dynaclr/docs/DAGs/evaluation.md index 5322c9202..f29316c37 100644 --- a/applications/dynaclr/docs/DAGs/evaluation.md +++ b/applications/dynaclr/docs/DAGs/evaluation.md @@ -128,6 +128,8 @@ configs/viewer.yaml # nd-embedding viewer config (also valid input │ -c linear_classifiers.yaml # reads per-experiment zarrs directory + annotation CSVs │ # joins annotations on (fov_name, t, track_id); trains one LogisticRegression │ # per (task, marker); marker_filters omitted → auto-discovers all markers + │ # label_source: witness → weak-label from the MMD witness score instead + │ # of annotation CSVs (control/perturbed wells). See witness_score_classifiers.md │ # writes trained pipelines to linear_classifiers/pipelines/ (in-run staging) │ # if publish_dir is set: atomically promotes the bundle to the central │ # LC registry as {publish_dir}/vN/ and updates the `latest` symlink. diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bb1725f6139e99cf9e24cb72c13e07c1c14d2315 GIT binary patch literal 24045 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

J-7qXqK;f)U?QCjyQ6PXbBI5;b% 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 UkJTuYhX)SDV`P+8mBITz0PFTH2LJ#7 literal 0 HcmV?d00001 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.png b/applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ad9940927a7b05f9a1a8375a24ab50cb685323 GIT binary patch literal 44174 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 literal 0 HcmV?d00001 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b3f13818a6969b634f9c3a7b3a65be84b3673bf8 GIT binary patch literal 26285 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 literal 0 HcmV?d00001 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py b/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py new file mode 100644 index 000000000..e94ead63b --- /dev/null +++ b/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py @@ -0,0 +1,129 @@ +"""Generate MOCK example plots for the witness-score LC DAG doc. + +Synthetic (illustrative) data only — matches the plot types and Wong palette +produced by the real orchestrated.py so the doc shows what outputs look like. +""" + +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np + +OUT = Path("/home/eduardo.hirata/repos/viscy/applications/dynaclr/docs/DAGs/visuals") +WONG = ["#0072B2", "#E69F00", "#009E73", "#CC79A7", "#D55E00", "#56B4E9", "#F0E442"] +rng = np.random.default_rng(7) + +MOCK_TAG = "MOCK — illustrative synthetic data" + + +def _mock_note(fig): + fig.text(0.99, 0.01, MOCK_TAG, ha="right", va="bottom", fontsize=7, color="#B00020", style="italic") + + +def save(fig, name): + """Watermark ``fig`` and write it to the visuals dir as PNG + PDF.""" + _mock_note(fig) + fig.savefig(OUT / f"{name}.png", dpi=150, bbox_inches="tight") + fig.savefig(OUT / f"{name}.pdf", bbox_inches="tight") + plt.close(fig) + print("wrote", name) + + +# 1) Witness score distribution + gating bands (motivates the labels). +def witness_score_hist(): + """Mock witness-score histogram with control/perturbed humps and dead-zone band.""" + ctrl = rng.normal(1.4, 0.7, 4000) + pert = rng.normal(-1.4, 0.7, 3600) + scores = np.concatenate([ctrl, pert]) + t = np.quantile(np.abs(scores), 0.10) # dead_zone = 0.1 + + fig, ax = plt.subplots(figsize=(7, 4.2)) + bins = np.linspace(-4, 4, 60) + ax.hist(ctrl, bins=bins, color=WONG[2], alpha=0.7, label="control-well cells (X)") + ax.hist(pert, bins=bins, color=WONG[4], alpha=0.7, label="perturbed-well cells (Y)") + ax.axvspan(-t, t, color="gray", alpha=0.25, label=f"dead-zone (|w|≤t, t={t:.2f}) → dropped") + ax.axvline(0, color="k", linewidth=0.8, linestyle="--") + ax.set_xlabel("witness score w(z)") + ax.set_ylabel("cell count") + ax.set_title("Witness score distribution & gating — witness_state (marker=G3BP1)", fontsize=11) + ax.legend(fontsize=8) + fig.tight_layout() + save(fig, "mock_witness_score_hist") + + +# 2) Per-marker metrics bar chart (mirrors _plot_metrics_bar). +def metrics_bar(): + """Mock per-marker AUROC/accuracy/weighted-F1 bar chart (mirrors _plot_metrics_bar).""" + markers = ["G3BP1", "SEC61B", "Phase3D", "viral_sensor"] + auroc = [0.94, 0.89, 0.82, 0.97] + acc = [0.90, 0.85, 0.78, 0.93] + wf1 = [0.89, 0.84, 0.77, 0.92] + metrics = {"AUROC": auroc, "Accuracy": acc, "Weighted F1": wf1} + colors = ["#0072B2", "#E69F00", "#009E73"] + + x = np.arange(len(markers)) + width = 0.8 / len(metrics) + fig, ax = plt.subplots(figsize=(max(6, len(markers) * 1.5), 5)) + for i, (name, vals) in enumerate(metrics.items()): + ax.bar(x + i * width, vals, width, label=name, color=colors[i], alpha=0.85) + ax.set_xticks(x + width * (len(metrics) - 1) / 2) + ax.set_xticklabels(markers, fontsize=9) + ax.set_ylim(0, 1.05) + ax.axhline(0.5, color="gray", linewidth=0.8, linestyle="--", label="Random (0.5)") + ax.set_ylabel("Score") + ax.set_title("witness_state — classifier performance per marker") + ax.legend(fontsize=9) + fig.tight_layout() + save(fig, "mock_metrics_bar") + + +# 3) ROC curves (mirrors _plot_roc_curves, binary control/perturbed). +def roc_curves(): + """Mock per-marker one-vs-rest ROC curves (mirrors _plot_roc_curves).""" + fig, ax = plt.subplots(figsize=(6, 5)) + ax.set_title("ROC — witness_state (per marker)", fontsize=11) + aurocs = {"G3BP1": 0.94, "SEC61B": 0.89, "Phase3D": 0.82, "viral_sensor": 0.97} + for i, (marker, target_auc) in enumerate(aurocs.items()): + # Build a smooth ROC with roughly the target AUROC. + fpr = np.linspace(0, 1, 200) + k = np.interp(target_auc, [0.5, 1.0], [1.0, 12.0]) + tpr = fpr ** (1.0 / k) + ax.plot(fpr, tpr, color=WONG[i % len(WONG)], linewidth=1.8, label=f"{marker} (AUROC={target_auc:.3f})") + ax.plot([0, 1], [0, 1], "k--", linewidth=0.8) + ax.set_xlabel("False Positive Rate") + ax.set_ylabel("True Positive Rate") + ax.set_xlim([0, 1]) + ax.set_ylim([0, 1.05]) + ax.legend(fontsize=8, loc="lower right") + fig.tight_layout() + save(fig, "mock_roc_curves") + + +# 4) F1 over time (mirrors _plot_f1_over_time). +def f1_over_time(): + """Mock per-class F1 across hours post-perturbation (mirrors _plot_f1_over_time).""" + hours = np.arange(0, 49, 6) + fig, ax = plt.subplots(figsize=(8, 5)) + # control: high, flat; perturbed: rises as phenotype emerges post-infection. + control_f1 = np.clip(0.9 - 0.02 * rng.standard_normal(len(hours)), 0, 1) + perturbed_f1 = np.clip(1 / (1 + np.exp(-(hours - 18) / 5)) * 0.9 + 0.05, 0, 1) + ax.plot(hours, control_f1, marker="o", color=WONG[0], linewidth=2, label="control") + ax.plot(hours, perturbed_f1, marker="o", color=WONG[1], linewidth=2, label="perturbed") + ax.set_xlabel("Hours post perturbation") + ax.set_ylabel("F1 score") + ax.set_ylim(0, 1.05) + ax.axhline(0.5, color="gray", linewidth=0.8, linestyle="--") + ax.set_title("F1 over time — witness_state (marker=G3BP1)") + ax.legend(fontsize=9) + fig.tight_layout() + save(fig, "mock_f1_over_time") + + +if __name__ == "__main__": + witness_score_hist() + metrics_bar() + roc_curves() + f1_over_time() diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ff5ef30862f4cf12ef8b8e52b80e815d46bdb816 GIT binary patch literal 26703 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 bhi;v#n~96tfhmN-`T1e^EG*KhGWh==RIcI= literal 0 HcmV?d00001 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png b/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png new file mode 100644 index 0000000000000000000000000000000000000000..70ecd2316f0c2f70939653054cddb468fc4ee822 GIT binary patch literal 55656 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 v_nvWitness-score linear classifiers — data flow (per marker)>; + fontname="Helvetica"; + fontsize=14; + + emb [label="embeddings/{experiment}.zarr\n(AnnData: X, obs=experiment/marker/fov_name)", fillcolor="#DCE9F5"]; + pool [label="POOL cells for this marker\nacross all listed experiments", fillcolor="#EAF3E1"]; + + subgraph cluster_ref { + label="build references from wells (fov_name path-prefix match)"; + style="rounded,dashed"; color="#9A9A9A"; fontname="Helvetica"; fontsize=10; + ctrl [label="X = control_wells cells\n(e.g. C/1)", fillcolor="#CFE8CF"]; + pert [label="Y = perturbed_wells cells\n(e.g. C/2, C/3)", fillcolor="#F5D6D6"]; + drop_ref [label="cells in neither well set\n→ dropped (no leakage)", fillcolor="#EDEDED", style="rounded,filled,dashed"]; + } + + fit [label="FIT witness bandwidth\n(median heuristic on pooled X,Y\nunless bandwidth set)", fillcolor="#EAF3E1"]; + score [label="SCORE every cell\nw(z) = mean k(z,X) − mean k(z,Y)", fillcolor="#EAF3E1"]; + gate [label="GATE scores → pseudo-labels\n(sign + dead-zone band)", fillcolor="#FBEFD6", shape=box]; + train [label="train_linear_classifier\n(logistic regression, group-aware split)", fillcolor="#EAF3E1"]; + + subgraph cluster_out { + label="outputs (identical to annotation path)"; + style="rounded,dashed"; color="#9A9A9A"; fontname="Helvetica"; fontsize=10; + metrics [label="metrics_summary.csv", fillcolor="#DCE9F5"]; + pdf [label="witness_state_summary.pdf", fillcolor="#DCE9F5"]; + joblib [label="pipelines/{task}_{marker}.joblib\n(+ manifest.json)", fillcolor="#DCE9F5"]; + reg [label="publish_dir/vN/ + latest\n(central LC registry, optional)", fillcolor="#DCE9F5", style="rounded,filled,dashed"]; + } + + emb -> pool; + pool -> ctrl; + pool -> pert; + pool -> drop_ref [style=dashed, color="#9A9A9A"]; + ctrl -> fit; + pert -> fit; + fit -> score; + pool -> score [style=dashed, label="all cells"]; + score -> gate; + gate -> train [label="labeled subset"]; + train -> metrics; + train -> pdf; + train -> joblib; + joblib -> reg [style=dashed, label="if publish_dir"]; +} diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf b/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eb823cec095e6839e40e5b4d4d4e87ea43161806 GIT binary patch literal 40647 zcmbrl1ym(Tv#1MA<4z;p=*HdM-QC^Y-K}wl#@*d%+}+*XrEzz^F!RqjbMAfjt+&>L zy(2TCqB650i(Ez2mxM9`LX@;rj8KGirxnjo3;-H{wVoLi2M2&!!qCdt!34ngVUmLa z007iNrWOu{b|0mMu7ja~p}w_&Aru!El)ZzUp{^yAOWK;cxi!|1?JHM@9@ka($dZ?{ z^d^N4Ya}9m$TxK!y)syzW+cK>xg3g^QWw9M`!u$>HF|q$(lyPa*zCRX6fcGQ(b*jZ z$x1BPw{-QFxL^IRD_fO*SbG~m$~y~aMjnpz4-DHK$g4^A-?>{cslj_pzhCVh>hu_d z+u|z7zD^Ty_t6Fpjn~v%Kir&N-0n58GXHwJv3s~X>2TT~PitFJwRdW%xjR`qTWzUH ze7H4cF5R#{RB)QuFo%1ossYXgH~k?v82=)(sx}Sk&6<>}@O41TBEdH+Ylhl+p7h=> zxvYm|dt!WVt%=g@);FxUOg-5(JExy2H0-c|dpkou-j2xgTv=U{=@GT3By0JFQ@E`E z)wSVyWM%`l!?n4!%0MV_?4h1EI}Ua&N*8W7rz;pauHQGK-v=(YMhSz*JTW9K+*#C? z?>@c9jokMKn+=2@aRON}9bc6K;W9<)RNLIxx!^>B!OO|2%jnBiL*stbWbek-dCS%c zBH0rf$!1tr%H_t}I(8opGZC}&HOGF^?DkW91Q-~x+}!Mmp4o)+ddAw&1cu4v(p*@2 zYPM;P0glrwxr63a%E|PDl$Q=YRcN^@I-X0qdy`0Gf~~c4{+-8aK-rgy#;lhzBe*6N}DjJ-{m_25}EYM&q1F?6N{ z^1D~FM!Hf>=JXS`CNZu>L7oRwlKQaa6aE%voz#`E`8f@6&}N+&8Zrp|)_k5u(OBT# zC_h!g5v;vukWb$d_l=C4sf4qdJei`!;{MWgM-5|k<5HhxsH!yz*~36-VXaIda~(&c z(ozs51O1Uth~e0ma#gNrBbdoZQF=LSyhT$&AYzj*d{eU__PBkuO39MSVNEz<DcKQB;$Z;Ic&-yqTD6oMf-eo4OdNL7h*JX=S;nxVNmQtD@%2YR8sL#)w{ zPzp4G)U^4@Wscs#?cQyCxe@4Ce!Xu^HLQnewitQq95>b+?z@^qtT<6>F;0~q%$c1U z1I}_L>w7%lu3YQtaRmbHI86gbQjssp)A?c}^$TdSTF#a(0tBVQuogFY9+Y`-XsMWG z(sRFYGnCJ~&4q?J+=ql+QA|djRjzE&TUOKRTf3>;zy*YO?xsQ3JmOXBra z+4VRF;7$g-u^CTt9^d+o(cXlJuBctmb&NF*ohtA{%b8rZ9K9Ix%BZ24cc_50DcR#{ zD4w5iKaF!?cl0<#LC0-Hbe4A&>z-7y^{7Pzq@h0PYO%tklsKs*`)4W~6IZ!2wzYEx zF-W=R2(tF|3j~ z!+y%tj0$U8*v&?^RX2Y&jP?|5p!p27&*AC~=4@wOri&EGM2k_V_cdiC#JRo&WZ=2@ z{!A9!S?S54=@3K^`>@)h+Z`yY&5!J_Q?C_MBfM@ z?d19LYNjNqf+6^yRIaP0@QrkM_la?gW#=~wzxp}ofp#J(JH&vkI5SC48LACma?mD( zmS5cYG+U}AO{5l+ mXv9HVcqvnzw5*P=Bk{NQ8r0M%&L@>$B#EY}3N(?lcVMpxv zl81`y`PuXw_(>^|ZKDf^qF|JBE~5N>0|;vucud>9IYjMYZX(d8wRqJ5n-(NbPFfrk zrpg54@+f*Id<5_9sAx%fgpJnCpP^4ehgMV-XnDJ~hf}pd zO?EBI3`=GM@^EsA+_tQ&sAzG0Jc#0quHO+`Q(kNzkDGxhCYpxqkaA3lRMHOzvbm=? zyF8WOQ_DN)QvFmbo@sD<2Hcr6>94*eDN2bbVrq0~O>PU8p{FQKs@>MP34}ddbWNPU zSkKU(+=_i9W>egMHz$H`u2S3L-=!9AV4b8M{vPz0ib%n|^n9fS@065`cD5^5Vbs<3 zD?db=+PoL1x-DUYs0pzoMwR8@i{Hg0SErbCF0D6-c6k1Q-;06HY#2s?B0~*ejb_=N ztf+t3w5#=zwkj|_nC~nSuu02FEdP2;T+}^G;wPZsBZm4X1?3ED!u_f-59$)DL6ZQJ zsmm@#SY}=N`oO$tIPy&Z*Y&?eRL7Q1{WV_o)-!r40^U!8U?x zQyaEHnXe*R ztL-#w1VPDdCfv@Ag^!%o9$6=vComi} z8esEIp0N`xn)ZPg7|)b(8Unx^xcG0Op!U;8L^j}1uqoAxRZkC~+xziLeW{Pq6r9zd@vpXVq*9jhG^nkF6M+g9ZSGyEMUFi1tPQ%eiktv++et$9DXsb zz4xpJQyIZj&V7=x$y!5qgS6bO>Dcd58my@hZ|DoQ*YqkTm4=z4(kmIjxtEX#%*83` zIu3LaS)*`Z1Lz9gst=9VGjgtl1@w~F=0J|5wL58$1aDDZ;iqD2uB*QzVQ?idK929U zbdluOui?K0q;@!f4pN|&#g_G8g`@v;V(;;Q8?jhyDs$M?%nXU(l16G@q7Q&7J4nlJ zf)R<*CC7a%Q)E42DXdH2cFPB@%$lfJ(Ol}ep59gq%lJjT%e!msyus1gp{J5*{0v>sOe(LEBWYgboR@1 zts7NNZ28>v5|w9d%ju@@A34H{DwN;G`+fLWcJ5EAduyZkYI4A38t9*Yy;4YBLqyd8u>wi5%6pj?!rSJ{5hzuy0#& zBVKG3fa6FK_U}zciqu}ltQtlvrGn57ZzDG)8ro$gNtQJXAo&G?PervvE@q+HnM?s|F7)K(Y z;s`&kci)vVj#Bj36?Fpg;U6o;E1?!^WyY;c?@*A(M%CT${U2OUmm&lZa6p3YhaQRxT@ix5@GBO*RB) znI0nBF4fM>nf*a9iTeUf5hbfbX0C6~DNNX@D26iRrZXEJ1aoh?M}s;_HaLXYwL;g4 z^5-pTU_saJii)B`l&2>q$ah#5pglzjU@l|V8-id{PV%HV9m+-KAb>v zxhYrV+WUA68emzXMn@Y7J$@x+kd2W$_@ZF*u}B|jF=2LN*1**duVfVsADT{$|Fuku zn47ek=6I;A-5uJ>h00{shAOhTw}f$Gr%3)%RqeB6x0H>NHxa<%GNd0Mx^Cz19+?3N z{Vq<~IWl*WL<mk<+rZ+9uAU+RrVuA|I@|g2q*i z+`fmNm*a~dCu>TU7znpqq&pn4>{xMM2)2g=uiR2Gx~sTyti_au`BYoZ$kbDeiKgOW zS8`exy}a%+Bk)+&d>UKFdB6?r_m*qLe)9_%g^HL8xuuu?VV&}4RE`&ebfiq@oL*yUY`k(G1Wd9fdOeLO%|SWclr zJcO6i0x(UQN{GY@ChF(5y(AQ*gWg(zc70%1+^5X0#N@jee^@i0AOKY z0%-o_&HpjF4|jXOXNKkPbc(Q@wWH0yM*Mdle_8*zWciPt?R2f|Z9e7|L_tpbTZX9loRIrH2xd7p#Lk&L;E@8|9hI} zGkkyNgJ=P?3@pra|0D4;<__(lFgJI;{M`Cy)SW2Sb&uAVZb%|VR78ZxpND|>mjKO| zttFTgqzpb!gs)%A`2>(5xjKoV;!RMBmg$m#q+H^P%9n$1isnk@COjxne-})nBF2xd z)=hk!_40aMTYqlp*htq+ufsX+z%j3EyIUp(K>(WMC$`C=XmrvVz$th+fCidi0%9}n zf+dgdXn*ar2Cg0l63In#)^I%iwyKQ<yb6;UBT|0j$H4N2U-u3 zk%@1PY!I;JZl-EL1lNbc5#76|8 z2re0Nd=YMK6n^6;cCI510*QdV5-r52?~z4@m|H6IMI?D?GTXGC-ahQ%M^@KvMDTU4 zYyA9L6EdmpVmmp3+__KX50czViHj$Uj1Ln9oLzrPmPjP95!YQtsF@;d-knk5f`k)d znO-R1&2e<1_AI@&CjeeCK4}QmB#&Q7-7~+7vg5uBhQ_kTP!n#5w^L(sSnqIgMwPxA ze&fC@{hIPE;Mw+fW}l-GQ(ZIsu0n^J@ytT8U6=&>UeYQxP9k?aXyz(aWo*lDo=pKr z*vJ|n*)0|rKsBtSJhDK@f;_3n!`FJ;=WF~{VP)mgVmuBDw-ff+`qqSRWpymBwY=4> zr%yIu=y<%%nJzKJAwk?c54(?*R6Bm!ZP25rp2blIN?XF4KFV`=pvXv*XsZ25)bzrX zF~r|i)9Alx+`)j`E*1MVvZ_vt3px8D%zde_QXcKA!#{Smt@ygNliVAfMedFpdzukn zvSjjW0_prwGl zZFHp4I#O}MBr+nMsjvaDBZNQF5@x{$ayWR{#fSbD^)}Jv@cG5I03zm zJxh8#${ZE#FEp4yU&VSJMMFiOi1k@(2*V&M`-zUnt5h?XtMUdUY8*_FaSfdDakqv> zJGGrb@S)#6%o!3^e6JRuo4GG6Hr-Nll!lFTe-0839==NBH3!%6F6gitq6jc8V5S-{ zg$VfpH-HfG5*LYi8BqU1X4-bFakT%lCtTmyvR2#Uxh%=;_^n|bfg$Bv9*Gs*k7R#P z+%+M)Wsz{~g^MSu4s7K`HWNcDuv4kM?*8ve;VP7Hi9l2-aSFVVDkAJOaVDrW)6Ht_k#lXAHA z;RaJiEEjWfHRV#WnUWNNJmU&p!f0;t09}(rnnopIgVc@@nw?vNS#8oOcpCtp%s9iR z!jzCD^#pciXVtTD4X%alg<>Lcf&C#>waj(aSa8{V;}s(Kj355ZU69K4C2$RD2SxU! zO{g9Qs+ZB0rTSI?HD>=?Pn`kYEdO*^umFZgyYK<(;EW$m0Cb?LZmk|LEN5EO^YLu6Xsyb!Xqdk+G(-tenumZ7DbE zeB+60D%$31Z#P<M66S!9DwVuytbOd#=AcVY z)sBI+==E3e#7p&iPbcfl636dBhLPIAo2)I}=d)I>Tm>ANYYboIJxUm>T5E^ZXlM0y z4J-@fRG7r%0=1Nvo$I{T7n!PMmA%U0QYeKz@f@wHA+H8fgVB)@Xpu)(vmvM*^nr+E zX5@0Rc6xjM%iLsm)bo)QG#bYkcTMLe9MU&Y+cJSs@jR~?brsA@c>7WKn?HYQUU(|0 zxzq1m>RM9*LP{kK6tv{mnPxy-I|iRMGY%%`Q(V?ZPz|}40Sa{HCyw#+(OY}dSFWIl z{d1Vq%YY0aGE-a+_Cta~Nbug!CZ}%mm>*Jc0^m!)9LXb@e@HhJOl(RPYip+S^;)P< zFdT~&=MWRov3;U^@jP@|P!lH@7wSg)T8vy3Z%|RGO)m>tXIJ8In_i^maDrLpEVO&~ z32<0KYUK8S1J0D1KXcoSGvwy9sF`I_e`rfDmO`(=l>)kP`hPvkFU|O8VU1;68*TdO4RBYzT zpL!~C5U(FhFgaIS)k=@s*OQMHt?>S9kZt82}PVo8hWZ`+DEN@p1J$FPgR$M$Kq!j>Kk^M zNYaN=hH1kb#75!c-_u=_U+Iiv+08V};`$d$)90+L>Z+EjWgB~{hiV4eSC)*qH{-@q zxJN^2+BeRElgK5@dSfxB8v9Ubu?RRa1j!X!#(>B|eM9YJnm~J!{Jz&+s^m8kbc;># z4NFkyinWYU3`Gstm@muHaSyE;JdEmmkJyq}386vn`ceO&>-0b|AV|FYW$TFsM_~+8 z9(yrK+wKZK)|-|(sre?sQMrMl9|+gu&z$^F2g3d)G|sd%nbGL#(@iv%G6nhbBlOoD z47_OHwG)bz$YytBM`ONI&N8W&;IvuKj7wLdce+=RET^c=Xwz9XcKv$#Bd;W@Zw-go zOhmiPhdBQCyUY>90kPY{M8{TZ)(6&oABl<|0gBC%+hAD6Js@}$%#DkzTbqQ!5xbTX zM}s-4M%*6e%_9?1M#jEQjoZ7+3DPEtd-?NycYWjeRvEQ|mxGog!HZK&xHG^$tuH!0 zOt&Qv@;x^d*;wBA(YuvgP1KLBXtLPNXU*U^t!K?aChM94Ct~d{hZ`}}RwAda784m~ zSnhW;>Px|aN)gv}-Pm;(x;}$Obo?q5Wk`*5?xn6FO+DkshGA32dH3k@LTbioW_kTz zHOo;&k%TZYQEmeO>U(xg{C^D3*`B4?)6KPwH_W-M_D_`{szsVb7-g14n^j3HOONrF ze!E(>PXNocAY6yZ?w zUB7z{_H^o8(`{(+(9q*zs}!Sz3q$tZ;mVq2pmJyHhbJHlgiLR$Y7%c!3YX{xdLTd= zw2QbF`qc zx_2dPFirS=&3H|d$hSY7(TgWUxalYmf&Cbr$*fw4%a+D5;KK3yHXe=^TAQ(C1^-*n znT?(`Xl8FQxqJU%7!vV_n6RagsaI<;LfrU_U0L#j1m&WjWW}EOdi)|Lo7n z7eaTnoyFbbJ1FNOr<0RV9mhAkdgs*xnm3A8A6hrQ^6!1IdRo89>Pa>ZQ5{}nna3QK zi+av#lj$jR7>yCC_Ffmk?DR}e)dXJ_1$xAX@oY$)( z<$7j_y8-r@FtkArYTYb0O72sUXT5zrdrF(6n*bN_2v$>ilLHJIZRAk63)K#Kc%mVb z3wE@j(^wcZSf$ely@*lkZ{KxA113;NdwNu8m-8{gCME8`mGZg`o6@>i@F27IwDw9k zt=5(-XGTht#G}L=nT=2>rE2+pa&=CY(7uA04GRA6IwSZGY1JWJ!VdjrJ&t{0cJV zCvqDDF!qW4bH&s}&bq77HfA!bHT)HZQ6qvSI+es~r#@uawX}k?i6I9ZWCFU?V)DA5 z;^IoG3)lT7Sx6rU>hYHh8QKwlf3yr`cx>vXoLe!CYzjeTAmfXfZbYk4$8sa$*)B7b z^RV*~Gx~;)^8<4L^PcmKH9B`-)Z@2q$VdHG$Q`r`P;Vs`;`I2A$xOxssE}EVk~Bu5 zZtzQ7V!3lOR?d+h)U&`NA!-O&ZH!0Cl1Vg058u7~=v^OR$M?)Y-TkgB*N1}-8|a?s z?6KA-HK-~pw1|wty#?GTzyDI$40g@r<0#O+L9|}pUQ$oqN~AkFKqc18zPn&3JECwsIhvOIaGczD8q;=SxhOq~+1sQ?WfFP)QMq;m7OihKk zi+W&<6o{qObAM#jJA8y4lHiJcdA5Ik*qjtd(Ycvxxk=7eHNjUVE}SAf5HFj|OM#|r zVJ(TBvMbYBz`}Jk;rp7&gClc$#T~wa(vI2l{MEB);cj;B#8PKvb26EiJP^*&qIx+j znCF+0D!q`RvRukxRZE(~)OC~tby8cl_l#nq0%**PR7^)Eo);f@B!RZL*LkHh(Y=Ls zT6>hWlvWRSS>M;Ciy}5XhyGp|vnU(Jf|PI(B@pHz2GU&I<1h->dY0$yc2087=zRW; zy*Z|$-n-%>hVNylH6xYRPMaCED9K|9S#u?p3jE*>deRwb%{PxfdA%uxzRZ6^;{#YG zni*4&#Z`GWt7yE8d4{%=V}VxLz(@HYtdwY<&yER{y)3~$K}cqto3}ZWfkBMBqfTgh z5BQdz?fS8+p%T5*v&M0xdK8+@Pm)$t-Y4VSo_YD6&eb(4H*Br)8(82VXHz!lmi^St zrqqy>q>lD>J+5>}Ls@ z_J}Usn=u5oe!c*64`%Lje+wX;MqbmqM17<3V(;L0QSeZ3Qz*Q@bfO&%3efwlLN*_t zobqVI8)-8b)BFg*AP z?fk2a$_z!yjmiX-GyL|OA^4ww1@k(WwzZ;`z9(o+%ct85yGHCDJXFhSv~DlFB_>4p zWPv{fI0OwlsBb4sTbqwHyMeo7{2RpQ)?!?xYoNqg+OmutbfS(yCL-P_ZDQ{bj$|%_y`7;@ZzepBXa&;x{ zwk3se);ZfBmv(C(CMGx2u z0fI)qTn%7V#m{o7r|#pjPO;zG{Wp{c$yKZ$h+WzH?GB@Es8?ROpMRZ`(BmG;z<)XN@g$KWG8+WuV0(qp z@g6~v6+h8`_NFO#$Kg)!M*Hc`t)9E~E5cTZiRY*nRE$b8UB@weCdwAkZkpjGq^IV> zT}}k~fKb~PdVDJb9WqwtIWu?uz8irf*qrtoHlEX<@Np7oEJPHYZey5*^x7{r(8Ny9 z3xOlnYOY&``3-35E>_Gg@2jsRx=_OtzJfNP&j>li!nXO$Ib#t4hrN*3o0KnJf%=U& z*grF4Q|0%ol)=je?#!~WkK`u`$N6bj`>YYy6YhJISOj`!eP`ur4>=BUwr16f;QL+a6SY6`>OCk7+Y$VXMtcmH8Ic_08n|*#_1t0{s;p^Aaen zOYvem`CGT`!Y`s}38L8ZX!0Sv>I@5l`)Er8c30mQ7~`MzSxAwb632%yY*AD>7zaXb z%xgg#emBZDlut}pz0oa=Kxlu2>|z^PS5a0SS%TtM)vQ3woXGDPoWtGy*oQoOVYf?~ z9yGjAyuZCebfj(!bP#D!X@qSM`{(j?T%_1&u~5rRksE~>Zo~I-ZsQQjq7&AMq~>ji zzE3*-vXBeMS8hL~ECRpRq4G>&dvG*BfVP8(;xZ=px5YBrtat#QwH4Jsqc0u!Z72sn z!9g2LhQt3NW(QF+NUfVL!g_P~0@OR??(ow)TI-ATHtXJ&v;6{OM}e0MSlbk;TM{>P8`2<= zKvsKb-S_WxXatNG*lA*1{izSBQ{04`xEfQ&TOe289%3Mhr6*f#4 zRl?pcv8_g7-vAbZ2Xt@{FE2__2QHbrOdq~TL4r@^^yd%PPA6hgkWf5DX98$hr2^YyqY-Dvh&BW^u_+y࿛riE^LdM#hU(y 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 EUn7r=j{pDw literal 0 HcmV?d00001 diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.png b/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.png new file mode 100644 index 0000000000000000000000000000000000000000..d6da6e40770a036707643ec12efba1cb18f1eb7f GIT binary patch literal 199478 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

9cVP(boS|9CPHftz$v#tko$#l7!nuA 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[YuaizjR;}#)^?#DN`A!3kJ)tb7sC;8{ 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 gating — score band → pseudo-label
t = quantile(|w|, dead_zone)>; + fontname="Helvetica"; + fontsize=14; + + score [label="witness score w(z)", shape=box, style="rounded,filled", fillcolor="#EAF3E1"]; + + hi [label="w(z) > +t", shape=diamond, fillcolor="#FBEFD6"]; + lo [label="w(z) < −t", shape=diamond, fillcolor="#FBEFD6"]; + + control [label="control", shape=box, style="rounded,filled", fillcolor="#CFE8CF"]; + perturbed [label="perturbed", shape=box, style="rounded,filled", fillcolor="#F5D6D6"]; + unknown [label="unknown\n(|w| ≤ t → dropped,\nlike annotation != 'unknown')", shape=box, style="rounded,filled,dashed", fillcolor="#EDEDED"]; + + score -> hi; + hi -> control [label="yes"]; + hi -> lo [label="no"]; + lo -> perturbed [label="yes"]; + lo -> unknown [label="no"]; + + note [shape=note, fillcolor="#FFFDF0", fontsize=9, + label="dead_zone = 0.0 disables the band\n(plain sign: every cell labeled)"]; + unknown -> note [style=invis]; + { rank=same; unknown; note; } +} diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_gating.pdf b/applications/dynaclr/docs/DAGs/visuals/witness_gating.pdf new file mode 100644 index 0000000000000000000000000000000000000000..77e246f2cb266cc1c02d2fb11d7d1494e26e3d81 GIT binary patch literal 39358 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%0Dm*X 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 PF$9FHwzj$LZrMKoE~*Tg literal 0 HcmV?d00001 diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_gating.png b/applications/dynaclr/docs/DAGs/visuals/witness_gating.png new file mode 100644 index 0000000000000000000000000000000000000000..4d1b2749c6637ab110b2449ba4d0a9013b0a5c66 GIT binary patch literal 66884 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
)M+q!|trCtaO{sbgTq@S2`zr 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 LC — pipeline dependency (Nextflow eval)>; + fontname="Helvetica"; + fontsize=14; + + predict [label="predict\n(GPU)"]; + split [label="split-embeddings"]; + lc [label="run-linear-classifiers\nlabel_source: witness", fillcolor="#FBEFD6"]; + append [label="append-predictions"]; + plot [label="plot"]; + + predict -> split -> lc -> append -> plot; + + note [shape=note, fillcolor="#FFFDF0", fontsize=9, + label="witness mode has no annotations →\nLINEAR_CLASSIFIERS stages empty CSV set;\nresume cache keys on YAML, not CSV hashes"]; + lc -> note [style=invis]; + { rank=same; lc; note; } +} diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.pdf b/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cb616e92004f3567f1f03df9357688bf56dc885e GIT binary patch literal 35886 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 literal 0 HcmV?d00001 diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.png b/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..bda1ce4fc7f8c4b4c0459319f017c7fcbdb11b3c GIT binary patch literal 47573 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

-m>y@@;)FxA*kaao)^Ncd=}8dU)^qJEi&`0^e*% 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.dot -o .png` + +`dot -Tpdf .dot -o .pdf` to regenerate; PDFs alongside for print). + +**Data flow (per marker)** — pool → build control/perturbed references → fit → +score → gate → train → outputs: + +![Witness data flow](visuals/witness_dataflow.png) + +**Gating** — how a continuous score becomes a discrete label: + +![Witness gating](visuals/witness_gating.png) + +**Pipeline dependency** — where the step sits in the Nextflow eval: + +![Witness pipeline](visuals/witness_pipeline.png) + +## Mock example outputs + +> ⚠️ **Illustrative synthetic data — not real results.** These mock plots show +> the *shape* of what the step produces so you know what to expect. Regenerate +> with `uv run --package dynaclr python visuals/mock_witness_plots.py`. + +**Witness score distribution & gating** — the two well-defined reference groups +separate along the witness axis; the dead-zone band (gray) is dropped as +ambiguous. This is the plot to sanity-check first: if the two humps overlap +heavily, the wells are not separable and the pseudo-labels will be noisy. + +![Mock witness score histogram](visuals/mock_witness_score_hist.png) + +The remaining three mirror the panels in `witness_state_summary.pdf` (same Wong +palette and layout as the annotation path): + +**Per-marker metrics**  ·  **ROC**  ·  **F1 over time** + +![Mock metrics bar](visuals/mock_metrics_bar.png) + +![Mock ROC curves](visuals/mock_roc_curves.png) + +![Mock F1 over time](visuals/mock_f1_over_time.png) + +## Why the witness score + +The empirical MMD witness function is the RKHS direction along which the control +distribution (X) and the perturbed distribution (Y) differ most. For a cell +embedding `z` with the Gaussian RBF kernel `k`: + +``` +w(z) = (1/n) Σ_i k(z, x_i) − (1/m) Σ_j k(z, y_j) +``` + +`w(z) > 0` → looks more like control; `w(z) < 0` → looks more like perturbed. +The magnitude is the per-cell contribution to MMD². It needs only two reference +groups (control vs perturbed wells), not per-cell labels. + +Implementation: `viscy_utils.evaluation.mmd.witness_function` (shared with the +MMD eval), wrapped for pooling/gating in +`dynaclr.evaluation.linear_classifiers.witness_labels`. + +## Step-by-step detail + +``` +embeddings/{experiment}.zarr (per-experiment AnnData; obs has experiment, marker, fov_name) + │ (produced by predict + split-embeddings — see inference_triplet.md / evaluation.md) + ▼ +dynaclr run-linear-classifiers -c linear_classifiers_witness_infectomics.yml + │ + │ for each marker (witness.marker_filters, or every unique obs["marker"]): + │ 1. POOL cells across all listed experiments for this marker + │ 2. BUILD references from wells (obs["fov_name"] path-prefix match): + │ X = control_wells cells, Y = perturbed_wells cells + │ (cells in neither well set are dropped — no leakage from dead wells) + │ 3. FIT witness bandwidth (median heuristic on pooled X,Y unless set) + │ 4. SCORE every cell: w(z) via witness_function + │ 5. GATE scores → pseudo-labels: + │ t = quantile(|w|, dead_zone) + │ w > +t → control (obs["witness_state"]) + │ w < −t → perturbed + │ |w| ≤ t → unknown (dropped, like annotation `!= "unknown"`) + │ 6. TRAIN logistic regression on the labeled subset + │ (same train_linear_classifier, same group-aware split) + ▼ +output_dir/ + metrics_summary.csv (one row per marker: accuracy, F1, AUROC, …) + witness_state_summary.pdf (bar chart + ROC + F1-over-time per marker) + pipelines/{task}_{marker}.joblib (+ manifest.json) → append-predictions + [publish_dir/vN/ + latest] (if publish_dir set — central LC registry) +``` + +## Pipeline DAG (process dependency) + +``` +predict → split-embeddings → run-linear-classifiers (label_source: witness) + │ + ▼ + append-predictions → plot +``` + +Same shape as the annotation path — the witness label source is a drop-in swap +inside `run-linear-classifiers`. In the Nextflow eval +(`nextflow/workflows/evaluation.nf`) no module changes are needed: witness mode +has no `annotations`, so the `LINEAR_CLASSIFIERS` process stages an empty CSV +set and the recipe YAML drives everything. Resume-cache invalidation for witness +mode therefore keys on the YAML content, not on annotation-CSV hashes. + +## Gating rule + +Default is **sign with a symmetric dead-zone**: + +| Score band | Label | +| ------------------------------ | ----------- | +| `w(z) > +t` | control | +| `w(z) < −t` | perturbed | +| `|w(z)| ≤ t` | dropped | + +where `t = quantile(|w|, dead_zone)`. `dead_zone: 0.0` disables the band and +labels every cell by sign. The dead-zone is the honest analog of the annotation +path's `label != "unknown"` filter: cells too close to the decision boundary are +ambiguous and excluded from training rather than forced into a class. + +## Config structure + +A ready-to-edit recipe lives at +[`configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml`](../../configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml). +The load-bearing fields: + +```yaml +linear_classifiers: + label_source: witness # "annotations" (default) | "witness" + witness_labels: # per-experiment control/perturbed wells + - experiment: "2025_07_24_A549_G3BP1_ZIKV" + control_wells: ["C/1"] # matched against obs["fov_name"] by path prefix + perturbed_wells: ["C/2", "C/3"] + witness: + marker_filters: [G3BP1, SEC61B, Phase3D, viral_sensor] # null = all markers + label_column: witness_state # obs column + the "task" the classifier trains on + control_label: control + perturbed_label: perturbed + dead_zone: 0.1 # drop lowest-|score| 10% as unknown; 0.0 = label all + bandwidth: null # null = median heuristic on pooled (control, perturbed) + max_reference_cells: 5000 # subsample each reference group to bound kernel cost + use_scaling: true + split_train_data: 0.8 + split_groups_by: [experiment, fov_name, track_id] # track-level, leakage-free split +``` + +## What lives where + +| Data | Location | When written | +| --------------------------------- | ------------------------------------------- | ------------------------- | +| Per-experiment embeddings | `embeddings/{experiment}.zarr` | predict + split | +| Control/perturbed well spec | recipe YAML `witness_labels` | authored per benchmark | +| Witness pseudo-labels | in-memory `obs["witness_state"]` per run | `run-linear-classifiers` | +| Metrics + plots | `output_dir/metrics_summary.csv`, `*.pdf` | `run-linear-classifiers` | +| Trained pipelines | `output_dir/pipelines/` (+ optional registry) | `run-linear-classifiers` | + +## Notes + +- **Wells match `obs["fov_name"]` by path component**, not string prefix: `C/1` + matches `C/1/000000` but not `C/10/000000`. Give wells as `C/1`, `A/2`, etc. +- **Pooling is per marker across experiments.** The witness axis is fit once per + marker on the union of all listed experiments' control/perturbed cells, so the + learned "perturbation direction" is shared — this is what lets it generalize + across datasets without per-experiment annotations. +- **`center_per_experiment` is not applied here.** Unlike the batch-QC MMD mode, + the witness is fit on the raw embeddings so the control↔perturbed contrast is + preserved. If cross-experiment batch offset dominates the witness axis, apply a + LOT correction upstream (see [lot_correction.md](lot_correction.md)) before + this step. +- **Labels are weak.** Val AUROC/F1 here measure separability of the *witness- + gated* classes, not agreement with ground-truth annotations. To compare the + two label sources head to head, run both recipes on the same embeddings and + compare `metrics_summary.csv`. +- **The witness score is unsupervised in labels but supervised in wells** — the + quality of the pseudo-labels is only as good as the control/perturbed well + assignment. Mislabeling a well flips the sign for every cell in it. +``` diff --git a/applications/dynaclr/docs/linear_classifiers/README.md b/applications/dynaclr/docs/linear_classifiers/README.md index a0486d299..fc46bedfd 100644 --- a/applications/dynaclr/docs/linear_classifiers/README.md +++ b/applications/dynaclr/docs/linear_classifiers/README.md @@ -189,3 +189,8 @@ Examples: `linear-classifier-cell_death_state-phase`, `linear-classifier-infecti ## Further Reference See `annotations_and_linear_classifiers.md` for the full specification of the annotations schema and naming conventions. + +For an **annotation-free** label source — weak labels derived from the MMD +witness score using per-experiment control/perturbed wells — see the +[witness-score classifiers DAG](../DAGs/witness_score_classifiers.md) +(`label_source: witness`). From 361a8a50bafe6b93fb8373d87dd030db2c4e7c84 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 11:43:19 -0700 Subject: [PATCH 21/89] fix(dynaclr): evaluate witness LC against ground-truth annotations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The witness label is a deterministic function of the embedding, so evaluating a witness-trained classifier against that same label is circular — it reports a trivial ~1.0 (verified on real infectomics embeddings: 1.000 vs witness label, 0.712 vs true infection_state). WitnessSettings gains eval_against (default "infection_state") and eval_class_map ({control: uninfected, perturbed: infected}). In witness mode, val metrics are recomputed on the val split against the ground-truth obs column via the class map; the summary records eval_source. When the column is absent, metrics fall back to the witness label flagged as eval_source="witness_label". Adds two tests (annotation eval + fallback). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/dynaclr/evaluation/evaluate_config.py | 21 ++++ .../linear_classifiers/orchestrated.py | 106 ++++++++++++++++-- .../linear_classifiers/witness_labels_test.py | 31 ++++- 3 files changed, 149 insertions(+), 9 deletions(-) diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index 77fb9e81e..7e76803a3 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -239,6 +239,25 @@ class WitnessSettings(BaseModel): If set, fit/score one witness classifier per listed marker. None (default) runs one per marker discovered in the data (all unique obs["marker"] values), matching the annotation path's behavior. + eval_against : str or None + obs column of *ground-truth* labels to score the trained classifier + against, instead of the (self-referential) witness label. The witness + label is a deterministic function of the embedding, so evaluating the + classifier against it yields a trivial ~1.0 — meaningless as a measure + of biology. When ``eval_against`` names a column present on the cells + (e.g. ``"infection_state"``), the reported val metrics are recomputed + on the val split against that column via ``eval_class_map``, and the + summary marks ``eval_source="infection_state"``. When None, or when the + column is absent, metrics fall back to the witness label and the + summary marks ``eval_source="witness_label"`` (flagged as circular). + Default: ``"infection_state"``. + eval_class_map : dict[str, str] or None + Maps witness class names to ``eval_against`` class names for scoring, + e.g. ``{"control": "uninfected", "perturbed": "infected"}``. Required + when ``eval_against`` is set and the class vocabularies differ. Cells + whose ``eval_against`` value is missing/``unknown`` or not in the map + are dropped from the evaluation. Default: + ``{"control": "uninfected", "perturbed": "infected"}``. """ label_column: str = "witness_state" @@ -248,6 +267,8 @@ class WitnessSettings(BaseModel): bandwidth: float | None = None max_reference_cells: int | None = 5000 marker_filters: list[str] | None = None + eval_against: str | None = "infection_state" + eval_class_map: dict[str, str] | None = {"control": "uninfected", "perturbed": "infected"} @model_validator(mode="after") def _validate(self) -> "WitnessSettings": diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py index fd7c73bbc..ce4963886 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py @@ -39,7 +39,7 @@ if TYPE_CHECKING: import anndata as ad - from dynaclr.evaluation.evaluate_config import LinearClassifiersStepConfig + from dynaclr.evaluation.evaluate_config import LinearClassifiersStepConfig, WitnessSettings def _annotation_run_specs( @@ -147,6 +147,83 @@ def _build_labeled_adata( return annotated_parts[0] if len(annotated_parts) == 1 else ad.concat(annotated_parts, join="outer") +def _evaluate_witness_against_annotations( + pipeline: Any, + combined: ad.AnnData, + idx_val: np.ndarray, + witness: WitnessSettings, +) -> dict[str, float] | None: + """Score a witness-trained pipeline against ground-truth annotations on the val cells. + + The witness label is a deterministic function of the embedding, so val + metrics computed against it are self-referential (~1.0). This recomputes + ``val_*`` metrics on the val split against ``witness.eval_against`` (e.g. + ``infection_state``), mapping the classifier's witness classes to the + annotation vocabulary via ``witness.eval_class_map``. + + Parameters + ---------- + pipeline : LinearClassifierPipeline + The trained pipeline (predicts witness class names). + combined : ad.AnnData + The labeled AnnData the classifier was trained on (obs may carry the + ground-truth column). + idx_val : np.ndarray + Row indices of the validation split (into ``combined``). + witness : WitnessSettings + Provides ``eval_against`` and ``eval_class_map``. + + Returns + ------- + dict[str, float] or None + ``val_*`` metrics (accuracy, weighted_f1, auroc, per-class f1) computed + against the mapped ground truth. None when evaluation is not possible + (no ``eval_against`` column, no class map, or no val cell has a usable + ground-truth label) — the caller then keeps the witness-label metrics. + """ + from sklearn.metrics import f1_score, roc_auc_score + + col = witness.eval_against + class_map = witness.eval_class_map + if col is None or class_map is None or col not in combined.obs.columns: + return None + + truth_raw = combined.obs[col].to_numpy(dtype=object)[idx_val] + # Map witness classes → annotation vocabulary; keep only mapped truth values. + expected = {class_map.get(witness.control_label), class_map.get(witness.perturbed_label)} + keep = np.array([t in expected for t in truth_raw], dtype=bool) + if keep.sum() == 0: + return None + + X_full = combined.X if isinstance(combined.X, np.ndarray) else combined.X.toarray() + X_val = X_full[idx_val][keep] + y_true = truth_raw[keep] + # Pipeline predicts witness class names; translate to annotation vocabulary. + y_pred_witness = pipeline.predict(X_val) + y_pred = np.array([class_map.get(p, p) for p in y_pred_witness], dtype=object) + + classes = sorted(expected) + metrics: dict[str, float] = { + "val_accuracy": float((y_pred == y_true).mean()), + "val_weighted_f1": float(f1_score(y_true, y_pred, average="weighted", labels=classes, zero_division=0)), + } + per_class = f1_score(y_true, y_pred, average=None, labels=classes, zero_division=0) + for cls, f1 in zip(classes, per_class): + metrics[f"val_{cls}_f1"] = float(f1) + + # AUROC needs the positive-class probability mapped to the annotation positive. + pos_witness = witness.perturbed_label + pipe_classes = list(pipeline.classifier.classes_) + if hasattr(pipeline, "predict_proba") and pos_witness in pipe_classes: + pos_idx = pipe_classes.index(pos_witness) + proba = pipeline.predict_proba(X_val)[:, pos_idx] + pos_truth = class_map.get(pos_witness) + y_bin = (y_true == pos_truth).astype(int) + if len(np.unique(y_bin)) == 2: + metrics["val_auroc"] = float(roc_auc_score(y_bin, proba)) + return metrics + + def run_linear_classifiers( embeddings_path: Path, config: LinearClassifiersStepConfig, @@ -277,12 +354,15 @@ def run_linear_classifiers( trained_pipelines.append((task, marker_filter, pipeline)) click.echo(f" Pipeline saved: {pipeline_filename}") - # Replay the same split to recover val obs (hours_post_perturbation). - # Must mirror train_linear_classifier exactly — same seed, same - # splitter (Group-aware when groups is set, cell-level otherwise). + # Replay the same split to recover the val indices. Must mirror + # train_linear_classifier exactly — same seed, same splitter + # (Group-aware when groups is set, cell-level otherwise). Used both for + # val_hours (F1-over-time plot) and, in witness mode, for scoring the + # classifier against ground-truth annotations on the val cells. y_full = combined.obs[task].to_numpy(dtype=object) + idx_val: np.ndarray | None = None val_hours: np.ndarray | None = None - if config.split_train_data < 1.0 and "hours_post_perturbation" in combined.obs.columns: + if config.split_train_data < 1.0: try: idx = np.arange(len(combined)) if groups is not None: @@ -300,14 +380,26 @@ def run_linear_classifiers( stratify=y_full, shuffle=True, ) - val_hours = combined.obs["hours_post_perturbation"].to_numpy()[idx_val] + if "hours_post_perturbation" in combined.obs.columns: + val_hours = combined.obs["hours_post_perturbation"].to_numpy()[idx_val] except ValueError: - click.echo(" Could not replay stratified split for val_hours; F1-over-time plot skipped.") + click.echo(" Could not replay split for val evaluation; falling back to witness metrics.") + + # Witness mode: the witness label is a deterministic function of the + # embedding, so val metrics against it are trivially ~1.0. Re-score the + # trained pipeline against ground-truth annotations on the val cells. + eval_source = "annotation" if config.label_source == "annotations" else "witness_label" + if config.label_source == "witness" and idx_val is not None: + anno_metrics = _evaluate_witness_against_annotations(pipeline, combined, idx_val, config.witness) + if anno_metrics is not None: + metrics = anno_metrics + eval_source = config.witness.eval_against row = { "task": task, "marker_filter": marker_filter, "n_samples": combined.n_obs, + "eval_source": eval_source, **metrics, } all_metrics.append(row) diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py index d72b62b78..5dd5e7914 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py @@ -39,6 +39,9 @@ def _make_separable_embeddings( X[n_per_well : 2 * n_per_well, 0] -= 2.0 # perturbed # C/3 stays near origin + # Ground-truth infection_state keyed to wells (A/1 control, B/2 perturbed, + # C/3 unlabeled) so eval-against-annotations has something to score. + well_to_infection = {"A/1": "uninfected", "B/2": "infected", "C/3": "unknown"} obs = pd.DataFrame( { "fov_name": [f"{w}/000000" for w in wells], @@ -47,6 +50,7 @@ def _make_separable_embeddings( "experiment": [experiment] * total, "marker": [marker] * total, "hours_post_perturbation": [float(i % 5) * 24.0 for i in range(total)], + "infection_state": [well_to_infection[w] for w in wells], } ) # pandas 3 defaults string columns to ArrowStringArray, which anndata's @@ -132,8 +136,31 @@ def test_run_linear_classifiers_witness_mode(tmp_path): assert len(results) == 1 assert results.iloc[0]["task"] == "witness_state" assert results.iloc[0]["marker_filter"] == "Phase3D" - # Separable control/perturbed clusters → classifier well above chance. - # (The ambiguous C/3 well, sign-labeled, caps this below 1.0.) + # obs carries infection_state → metrics are scored against it, not the + # self-referential witness label. + assert results.iloc[0]["eval_source"] == "infection_state" + # Wells align 1:1 with infection_state here, so agreement is high. assert results.iloc[0]["val_accuracy"] > 0.8 assert (tmp_path / "out" / "metrics_summary.csv").exists() assert (tmp_path / "out" / "witness_state_summary.pdf").exists() + + +def test_run_linear_classifiers_witness_falls_back_without_annotations(tmp_path): + """No eval_against column → metrics fall back to the witness label, flagged as such.""" + zarr_path = tmp_path / "embeddings.zarr" + adata = _make_separable_embeddings(None) + # Drop the ground-truth column so eval-against has nothing to score. + del adata.obs["infection_state"] + adata.write_zarr(zarr_path) + + config = LinearClassifiersStepConfig( + label_source="witness", + witness_labels=[ + WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"]), + ], + witness=WitnessSettings(marker_filters=["Phase3D"], dead_zone=0.1), + split_train_data=0.8, + ) + + results = run_linear_classifiers(zarr_path, config, tmp_path / "out") + assert results.iloc[0]["eval_source"] == "witness_label" From 1d8bffa5f0ed5a6ea6c2ffbdbe874f3344a367c9 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 14:21:51 -0700 Subject: [PATCH 22/89] config(dynaclr): add eval_against to witness LC recipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Score the witness-trained classifier against infection_state (not the self-referential witness label) with the control→uninfected / perturbed→infected class map, matching the leakage fix. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../recipes/linear_classifiers_witness_infectomics.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml index d0d2217f5..4906daa3a 100644 --- a/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml +++ b/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml @@ -53,6 +53,14 @@ linear_classifiers: bandwidth: null # Bound kernel cost: subsample each reference group to at most N cells. max_reference_cells: 5000 + # Score the trained classifier against this ground-truth obs column on the + # val split, NOT the witness label — the witness label is a deterministic + # function of the embedding, so scoring against it is circular (~1.0). Set + # to null to fall back to witness-label metrics (flagged eval_source). + eval_against: infection_state + eval_class_map: + control: uninfected + perturbed: infected use_scaling: true use_pca: false split_train_data: 0.8 From 1af4f03fe2023b869d4a3699b9c5ade289216ef3 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 14:22:29 -0700 Subject: [PATCH 23/89] docs(dynaclr): reflect annotation-based eval in witness DAG + visuals - witness_score_classifiers.md: add "Evaluation: avoid the circularity trap" section with the real per-marker metrics table (scored vs infection_state), eval_against/eval_class_map in the config block, and step 7 in the flow; refresh the Notes caveat (metrics are honest now, check eval_source; note class imbalance). - witness_dataflow.dot/png/pdf: add the EVALUATE-vs-annotations node. - mock_witness_plots.py + bar/ROC png/pdf: use real run values (viral_sensor 0.87, SEC61B 0.76, Phase3D 0.58, G3BP1 0.49) instead of invented all-high numbers; watermark clarifies bar/ROC are real, hist/F1 synthetic. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../docs/DAGs/visuals/mock_f1_over_time.pdf | Bin 24045 -> 24794 bytes .../docs/DAGs/visuals/mock_f1_over_time.png | Bin 60112 -> 62754 bytes .../docs/DAGs/visuals/mock_metrics_bar.pdf | Bin 26342 -> 27099 bytes .../docs/DAGs/visuals/mock_metrics_bar.png | Bin 44174 -> 48137 bytes .../docs/DAGs/visuals/mock_roc_curves.pdf | Bin 26285 -> 27513 bytes .../docs/DAGs/visuals/mock_roc_curves.png | Bin 90478 -> 111329 bytes .../docs/DAGs/visuals/mock_witness_plots.py | 18 +++--- .../DAGs/visuals/mock_witness_score_hist.pdf | Bin 26703 -> 27452 bytes .../DAGs/visuals/mock_witness_score_hist.png | Bin 55656 -> 58276 bytes .../docs/DAGs/visuals/witness_dataflow.dot | 4 +- .../docs/DAGs/visuals/witness_dataflow.pdf | Bin 40647 -> 41944 bytes .../docs/DAGs/visuals/witness_dataflow.png | Bin 199478 -> 230645 bytes .../docs/DAGs/witness_score_classifiers.md | 53 ++++++++++++++++-- 13 files changed, 62 insertions(+), 13 deletions(-) diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf index bb1725f6139e99cf9e24cb72c13e07c1c14d2315..e8bf4dc7760de2a1e4fa5a47ddd0b33242db53eb 100644 GIT binary patch delta 5711 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 delta 5202 zcmZWrc_38n7w$^QRx(pjLszLRW$vAsduJw+C0lk{6v;AUXBZ5vQ}IhB#OrI3R4OG?c%j(zFv3&?BkNzc1*bW4W)5TmBKrn#sr z(D}%tYhKQQ)W?r&!ODm4KQY$exu$J>-kM)0A1T^Z%Qo$uk#O>0l}-K{r%LU#=eOG_0XI2^d>G575KX%o_><~<-`Gli!C9$y6K(o zzN_^Jk_4S&k59{(dEP##NYJTzoV2EGf1TFx=AI`X%WED@uH5lc`)GMXxqX9fDQoln zUur$qo*DVSvptyYFwt~&f?mk@^fuxCg0;OLr`dmfA9303NI>%QzuS5^v76$`Ze5;K z*R?xfXLq$hSZbz^oI`TOGuDCrv!@1j-D{1{RBh6~pd8Mr)MeUD3q4o7=;WyBas`*K)`er6exZ?@{}7Nh>U4ZXm0&{mmsU-`a__%MS{Z z?G5Sc`zIT^L~VMs_iFN1n?hUv6&?YHt|k$G$eTnc7-Zt6^*)m;P-9zOanW72zWTlQ z`;{B@W~Eyi3a(Sk9=vaE(|~HYZ=V*Z=a+u(&}p%J_v3n-!@GbG)LnSm^Y#a}Y2J68 zV6RGZ7Z;OkvWse1z!%qYla;Y$(^S)i^z)jN{4xV>dn@Q}e>>HAyy~&U^o*J%L6e|M zHT5<&dDG}22c7Hgv)@y~Pao^6n!4h+O1+!Q)4JnWac=HA1-*fVw_MMLe@UbTdErm8 zN+Nm;y3(h%E?>e~UATSGNeh&htQZukHnUYT(0x9-dECs!ulR{)D8+hjZk866?h6x^ z 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 joLxi!adV8k5Z6L6p-ze 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 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf index b3f13818a6969b634f9c3a7b3a65be84b3673bf8..ac3a972c3d3d3141066b478d6b9a47222afcc697 100644 GIT binary patch delta 7817 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 delta 6859 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 ND5Ruhu-8!N{{R(86v_Yq diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.png b/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.png index 8950bb8b4a951d41a67835c7f3c9a805cb1419a0..089f39e959bdac085f0fe1f0d876261c5d7089e8 100644 GIT binary patch literal 111329 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%0A5gXQx8mS{Q;@)~$$fkt zEY?DiqP>E+i&QOIuKJFB#2V+ca#sdHZMxR4s7abas>-CJ+U+usXB{_HcWh1?%{ zzzl(v?QKjjh}o#k9ZRQNJJ{%(lo#XV0Ao_p-6gA5O`KkAW(z@R>x>GlXT5*sWNp{K z@lCsrlgr^6N3@WVoLphzi{r1o?ZWOC-ofjQqlOlqk?h+>os~=h)n%xpf^y+2*(gA}nn zT58F}3B@lukr=v>J_XCZ(0^)xov9Mh!tZswzi-^QH9J)n7PGYbhmeAi{V(H#N#1oA zVL?-0qm`D5DmLrJnvgCNwWR02ZRX2Lk~0uibZ`^f097A85y`SiLjKN$-sP6dd^6>6 zavGX=U&& z

(J%WP$jfAieZU&U@CfjdhlA~RVa0FBw&X642~;bdzGM*}m?%WXU+p>8Cz?N+VLJS?*jN6W?0FmSn;-GZXIw~-|Gxe8n%03k{7Z%ubj+UIjwITz z*(rt7!X;GAOS)>yn{Gi_)kPxgGLcLX8a3~gP^fCMHXU~SXOfU%`9*cvOYW++TWN>n zOMm*r4Q21ZaoJMKb(Z4dAraRQ9kpNKqP}h>5Hc3aiPzyfPUplIm9BHAD=L!8^N@sgqWL87x8^em)L~q7GSu#n;F%13vX*f4G*$Nb#`0kJNWD58)}E zN*${{@4nqb(6dx#S5D!UyS%-j|B$G(J72KLcvO|d(&T(LqOOmLQeyOT-e4}X?6|NT=T0SsR{uhKN* zALg8;2iF~AzBzd9VOGV846q2Ki(zpPax>C>Nq8Uam!>GO;PvY^tzJf*#3$c4uBOX; z+^gfoC=Z;flDasOAUK5!tFi8srrbT-bM5g9bFFFb`M_0#hGUTGK^b>|b#vDS=2MBxUd?;u z$8UMeX~OQ6P5Z@_3pH78`Zx?Qw|9sr`Qr)8i)lV?s&~fdYxBdMIvW4HE+L^hGunTpf?fOVE`%xhb;5(nO7F@Q^f`+of}Xpbqlvsz_33os zm^&THdfGc|V>)kc9A>!gBw7>7%TVzluG0SdM-4=%3o>409r9zR-@z$FgmFf!=yIH* zabZgperOsOOlwl4kJ~yHO&Mlg!;jOktGcx|G;f-d+Pp>{KyfOpd+EH!Vbj6NXYsr- zg|l8yT4KSEVaFKWnLSHE{F?33v7v9J!b5!aRS7a5udZ|H* z^;B3Q_c@_K;GlY#4zD&ILuJb&&7OdNJL_Bg-Tn;s8F18*OFfvOxsY{i2ek5v`c&#!KWq930BjdSVVf(oTNA`AP6ibD+SjTZiT0(IV|IaiPfX~t} z7ggnI?I!*uhlBRs$X>~H>YuH(jx-&ZGhKaHf75FF_LuUfY6$o7blwxI49oSY8rc~! zcnXjP6rn4gz5c}AwQ;Dym2Zc++MAusp&TS=I>j+?1SGK zHAZ6&mzS1|5N{8V*}-00BWvrvUf#~Z6agI!vWz0>)9CY;E}kNG7=?jh(K`=uloV&w-E9K!4LI2hrMfAIR4lWU zKbHO8rSE@8^v=Ta2jyaX*Gb~)xw^70*s(iL_QJ6PiphDccpdq6@|Y4$v?A@eSM_~_9*=(#38vo+q?Jh8cFDD~^v zx=w9-!wSh0oQ8m+U-w-dJRZ>3?pw8($w;U_x9iL?9%<6E2P3g`Mc1>@a>}WV{?{LL z=AYkpSwZc|F&4uP89f6*5LZ;eNVJc31yL(5WQ{G)YO?Mt0MR zcX+!DmMYrIu9<=h@u_`?DioI(2W=nuFlOt=hWjO^al>jmImvHSAw(c)$}j3 z)}|XHR9EpmgLNI{Z7E(PB@f~BYdR|@-(9B3k zRJmPD>f`C@iMUF`M}q#k5S^w1G+VIA9?>boXF6}5>fn4XRoq9=bKPkE^udDStmw+O zrG~Pm0>TgVpQteiEhA+ajrOX#6WzA)Z!=n0hU<~9nd(vzyd5s$4rg>;?W@-ndhJ{i z`1jy*Tl*IS|Bd{Iss-wZo(cx_@QI0u--55__V(O`I~}t@r6vX#{f-U|k^xUjJVi zUP8<6AEz9g4qLYRuJ4Pua+wm0)!Rc8f0W?fV$H5P(=Sjp*61(;d`#V_FNj(5P+Pf~ zYEmnoS@3OwkSeQ_3Y^nX1+DlIvwD? zq#lxR>?;e-Kd!r^qc+@Z_DeBVg3?}l0Im6T$-CdntjH;EEL?7PF|g$-q0|$K`V1<% zEFdRA-`R>-f*@Ws{qnj&w+V-X?teM@D=Asn+={$4@S<7(RIwY<&<$$p>v6IHxTMVK zwm++pW@&0_s?xPjRp|ysHl|k&*@IGEOv~X%R+#Bi$~1$|-fE+WR5G&7?r$%KVD}4q z&;W9@99{KAZ?1-&cc?maKVDjB;Ay9p){&{WdMg`Ep!;ZdWD~XWn1ovZ8>t|K_@$MV z1qAO6CZs<;jSmjxE%pl6DNtp!^Bl`4N~G>l=rP>A&wro5^yuqLiyh0V?l^mmD}kE1 zpA{p6WwGOATRs<-BS&cg_r;;XU>kOYfO_YU+u%^F-1>lhiEKzf%~xadRh8^zD^FXS zzasXr@$rGM+P(6{FnS!bh8U~^mxmAph}mii9HtU5?ELfR&x@(ron|3+*zR`pXBzQS zsd8FZmAj$-hT^1Ax4-Swy#q^7LQ(bNHW|IZwTC=AqfTxT>S7efepB~5CfB7M3xWLy`FD62IDC=>E%w(ggYyxJ{vEfO^sL2~xzCY;=!dWl);J ztq(7q!TZ)YS+;zI^u^|66=F#n4U@%&o_{gn-4H=Rka;Qbi{|G?b>@=T@t6eum51PxFf*)hx@?;F1x9R$Yv2zX7v`N1j<*ThZ2bH zf`@=EXw&iIQu^zfX|x1^1Gq)X=E_N(;FBz zb%z*g_*3j86=o7?RS9gp~dU8I9(&P{yN$KyGPfScqhe`o~+l5Y6 z>3f!^m}DhJH1dUf9P4g@-?}gD()nfm!-o&&;DEx`<-bEeWn=hGgO=`2OSGlxx{hdGZ(dS&#(6z0j@z{;wF(4B(SEh8;|^_PO}F#G zPjR>ihG=dm{7&Air0We<-pAuC=+d-hN>eb`q!bnLk@fW-1l`HN`ZTDV>nNBgI#+d^ z>E*%i@2CqMF&@Cdd2A<1=%vE%ZI=fI27<~)0u=Mc&pVX#uF;>ba9EA!YIH5E^**iA zt|R*n5oB0z`JTG=%M~GW>gJ$-$!c<$tvucRdZlB+iepWCxeNH#H4{cBr*o~C7+k&f z+eLgll(%+2cIG#frmNrb2z-5ghbk@AU_UxQikU=N%93+Ycf@xu+~HWS4rTc1hF2>6 z-Mz!TW&ed>HA%#Bf`-Gi1ydTw61hE!I5#98V*^e_f*IUG~EEd^-Yr8#KJ}0qvUU z;D<+?oO81PPCTltuA}GV+@2=L6q9SN$G$=0I)3+0`_9t7cvbg%dmU=Gz>%Ld;u4bU z7h$jECth81x3fI#_?FPeSz>9{|Kb;pHSt>Gj{(&slbHdLGZJSa9oc}cH#dL%K5q2X zs@0KwWgh$50tz=^NQ2_yNDnuD_(ACw3aL7JtXwtfhG>#se^1q4w4__O=NUb3W12}u zhbBp!52oR|wMd%W?UONuIcbIzy!{2ClC+Y~nG2>KoQ9iRkL; z`UG_XJtrY4X}JfgG0hFn{74t^ZmFO>IA>ozxBA5Ogb?lcm&NWMDhKlBBeL6f z&4Bl*=IVslk^bu7Q_K|A(Y6^8@+4lu4Fz20MR}3-RyAEq$!x~`IcfX&i1r_l11^|) zP5kda#LeC$PeMXH8-h8kCr<~){GsdalAaQd$1Jf#fl<=Yg=#&Se9)aeOUIe2=;B8< zjn7{HM?y#{=aQN;zcPn4Zl}SB3S31Mvb@ z3eu)o#yX%~Y3meo8`zMkQD-Lg{JVS;aml=U_bwu+0C0fV!dti&-l zH+NM~zmA8eoFhxxpU8EkShq?H(hl;Ecr-%#dNu5%+;m!3deS801j40$@0_($?<+(Y ztuJPtb_C~Z-@rhl@-^N0ejyMn*EdF9syTM+a@~N~_DRkU;w@Km%DaX?BArxPDNUu^ zP7=G|^U=VPJ?V>%PXQefLJqEP4xXzKrPYu8qE$-J6=iM6IZ8v7N*c@QExvd z_BsZ1_t8S;o=YJP3M}m+zATvUnMp4`-}EnN4T}1ZRaaWxs7A_aHQ|@%IpRf!jSh3v zWi~&!VP;n~#@!?&7+2g{!!I@UHt>AAC5EJaJ3Bj8eJme7d?>S@6|$YKR_}3jyY*%N zjPJ%S_h$5K-phehc|-iuz}qI-QWA8sUwT?A#a+txP4I9h?F4J>Fwdq6D8J~*R|6MM~Nsa-+)ps^(Mi!YkqZgJdt;9%5^o8T8$^^_|e|mJJOhR(+c#d zpksDaH?939Fd)18*y59 z++0xB(e`Q5sdE0PMfY=VBlUJ!gaxq~z?1w^_OeYa)pVnC`r!_bm}&o54@vXrdib3` zPInSy><#Hd=wSf|G%AlYi?q?0n3!y)Ka(mX^AG~inQ0;l{brtG$~F)*RTHonC(!NS z_dLkxq*PUjfJWRwwhguWosQ3FjkPy_FEJDvY%6;B0;X@nmG!KEcu|y{R5w8l^!&szU zjt)glCk$R0+j9)WIsi2-7EQzSqsHD4ENg4>?bYZCKE*~6yD)F*-Y*R6xA=>hUhc=( z&$5?5sUqWaDfzf-vnj;SCDl4yhhp7SG3X=t^n_%LoG{wlT$aEw>kIzd4Ws&#y4_ub za+C)W^jJ5inWSQJmG7mOmLd|%zL>#CyFv6JicvUh|7%a_U3OT`YvTxpIO;~Ga)@9J?L&*RrF2Rs( z9j?J*{Z80@Oia!Nvbfqu_2a!in9wl#SLzQ09-^^VLBVPFlDmR=?uUf^(dsZg4Buga zC%*(Ft?1a7{LI3FKY;teB_}q^*ImvsPC|NKI%f1pHEu5B zueLVz7ZRrZqsjyiCf!WlzS1K9Ew^X%emz8q^Y_^yy8A_)-h{DA*6rh-5GLwxUM^r$ z0HSgfhd~3fHu0aWWJ`+(aHhebRRrwl-@bhthEP0J>u{y*^2h;~fS|kX)h{F{A|>*x zmHt+6V@_9RHny<|2Us1^6yMXUE*fj{j$uAs!@&+cOY$x%`eU_NEfnYUfsl|CGe9*! z@>kZ*=6wB|q+(Z2S>Sie()F&Zf8rv&Q{N`{b)f4gqni_;oO_rDImLkhT@-Hym0JI8 z)lRdYTx*7Ymm1bW;L6F#0mG?R%)v!S*f@=K)2I3TkgBb%Z4Q7G9OAp_Fqr>TPOd#e zJP0fcuD)I$yB>sp{vejuYV;m5bPhD}H{Aus>2*2LI9$2s9lyM`_T{e|YkXJnFA9G8 zpDzug>F7I7|BhU!jV2O}j3aed^`CvFNu~y-hwG-xofET|F6}VK679(z8jWIViJoxT zQ0Qwi;dmob1Zb)|6oZ1FatqaBpTTI6F?__mUtbH)=ir`kFl&{B!_pu$sPXlxZA3*x zyftIk*f8RVencLwJhN!BNva{efwEbz6)E|8g*Z6-w^=p%?Cqwo%M=f9e%*^!5v9QU z#Zz2WRRZ$YojjcrG=U9X=!0J4Ks3KAF(UfDXYn|N`1JaY5z7fJz^m;uyors^r)m6QQN7p z1Ub==Y^&MghvCysS0ZD0-7l&R%i0%GtCTxi_5J&Cl^FBO7G}&4EU@=c65P@0x17zM zS`$=qd+YQLpv8hAWKyWq0-%NkKKd$rJf!=J$tDmoo`bXjz&QKM4UGobxXkILG?;=1 z@2i$?--x@qx}dq}fbx6icxREot#NhrMfhz&O0a1NjE*MU*w{cC$uk(o(;YmUZEPap z;jia*{8qeIqVPqpeN-h`AtOY#g6X~vwzfOzGp%RC_fVVtc;y+*{O>$Qls{q46=y%> z5k6)Tsw)%ELcOji>l}8@Dr<7}f{IEMN`#?CcNb*6A@EckPz&pzhzLo=GR7zyt?x%> zWEdM8To}TtI`Cm75ET`@XV~-veq10tBeISc61y}cZT)A@W={_Qg~tE{;JLXuqL;o- z#n%ybljG%L*L7sN6-aaTPo>LFK*g`m|7ln+O1M7SwfT1T(wdHLr$8M&9y;^j#yFZ!r8QBdI5C;e2A{|s;0>=)#TLQ{bP{JD9MZiy_2S@}CMH}4Z zJv?K&Hjl)UvX&7C@7RY8@6ExByRzii#3jJ~6zf`bR8@ zEY2k*<-59VgZu!DMTG#M^_PbYFkmh7P=c;blrLia zIr;->uvoBq)nlp^f0=1Ss3 zv+$0iqoPoN^rHS(9nqs!9+LmYZT9GIXD`-Q3@8#*DwP%o(}W}6zb65o0!tEa)ZErq zBu~N^wyDb`ohSEw(n5D=DBt?O`?prKL4;)T}Rtt}iRpX@v<*K{T4* z+9E*J0#y&G2RY!DKOmS64-f0gFE-bCo}c)4H+N|oD^SY=Z4G>QQ&<*` zIB_5#fog#S&|AZKN;L29XVv>@JC267xLKKMem|Cr%C8OeN{Ya7w>K6qTWM7v17YNN z9i`N0DR!NHvfK|kHDX9i&KO;imf;%Q9Dc%7hZ54itmvLgG4{d~KB8x$6%e2jzC3&I z5624$&Xqtzm!rF=&%Fa#iW zTlSg3dkP2T+-WRS{`=gX-hSa{-=R^yyId6Z1dHy(9z(R{WKKJL_SLlz3eaA8G~U{A zuc5qWVaY1y9FV7cf9L1Gw?qmz5*^tCvop|qbdA3VOfuLsGB;vS4OAyWC>8|M-(BvT zUtJ9bPpfp`JN}g#CNiI}-~QcYL_zUW+2Ziv!2=XD&XWR$Ece&au23O0B0MzGo58n` z1~oXWUkHEzkq$%Pa5~98e3)sxwf8-Fj$cwzl5|3C$Eoxyx_RC>n*Z6kn5ITbEk$a6 zqHE%NA0=o1`R(a95nMN^h*{S(s5T292S+72>$~re(+}FVS4j-~7;tkQ)Sj83F{swh zRSe*Vc>rWyvRJRC3)I(hbF|Z5{1OtXw7qo7|3Slx5`iHq@G)7PuIERk!$L=d6=-X3 zcUb;HinM01bwe?FWw-Pct&Ze<{)BP?25Td^-+e4Q5Yf5mVF=dY5hvF#)H|ngK8RU~ zoRF7fH#ZRQowZBxQB^L8NLr&>ke(WUwU#JfGd3i1E2&LRYn5|>hV&nQhFZK zz?InG;B1MQEoFl$zn-3TIq-{sS3SMRV)vv9EUj|91zc{0nY-#h7j||?!GzDTQ0u2wcmjKCO z%)AUb7)`&q$f>CGki(cG8#nV;xAgX2^*2RBYNhkDzk38S2aPzPcuP&kc{Ij~xl9-u zp7i>1k-Jgrd$B5XT=(BIpE;ihH3<;Cvk5raNCbV0aAm3Q&cL&bQl^0)MF}$V$etP_ribH<5n;;<_jsl!|7ZGH_*QE zUDO0gMEOby-*F+ln#r4+UxPC%1y-(h8B9gwB^6MbUeWj1cK0hO~rgFF?1vC zV`F3v4i4A^1kwOYk9HZdU17_o$>3XFVZ+4yG-AktZpN*xVNCdxArP8@ib%=1U$N1Y z3bO+d`~L{%k+PWQKZTwE4`Cr%U(SL6t`$kV@qE8Ks|m7s;m>p?a+w+#8~Xr0gJiFN z0FsrA<8(|$#vQ~wAJqcxV|#}v^ve&NWMpK4pbW|94K;)>{ZX?x6hhv|#`vZtp}X>l zDA607&Kug(0yGWpnO9LOaXio*ujf4AE-Eu}hTf$BL(&tznYPTB|v#mwB zWVTbiu2w`!zaa76ESy{%TZ%ufEDvM2CL(&HYJ_d?h6%gn$Q8FSr%@supxW zO|RVS=~3);y%gIQ_tDNxG}Moz-w&cSHNM>Z;Susw&8-^!8^LTRjRlQ}a_PB|sg*b2 ziAL%6n7w}@!ekX%@~;WJ+_lW^(&lBsDta+O718&3Rfvl@nq9xv8z8C7XZ6ncIu&;Z zQw3RmcnL^IFotMapf4^i4#V+z&!UA7F|ZqfiA*{}8+Jhb-2%H8pnzrBZ-grc{ZKHp z%w)N_xyMjb6A}~qL3vNdz<^OwQnGcf3*ssSOJ5mGxh5bWaO;sH2EW~uNEjI#(yqLP z@#lcSquF~g_ToJ?twRee?+)G)yk&*5I(BWLO|;!1V@mo@?RcT1UGNQHlz9=^b9@L!cK8Ny>IttaZzqFk^Oze0?#%`J+CVaF=Uw+bs$_rC)l$^!F z*k9QFyI7>|NJ?CD>=|DBS6gxAx8YsdS58BQ%pEQLH%*x=;b@ZoBOSj=MiBt|x z9}5btCA)$5nn)&{^O?buK>O>RSxc-D<;DjecJ#)7-!rwkCuaUHXZ8)KCAx0jb5#GW zu&HoxeLG7@`mJV!!mNxtVAxlo9G(K=2O6eMNSm{T7950xYw=^u;o!nRm71FBiaGZa zv2B7zc=EGt$o5B>w~AnvJk=1BLcVq*4%bo#SANEes*l}j@SO&;sqZpc)o@9uA9j+Z zwDtlqo^Z|eRt}!if@-Jzj@_<{m*r)IcOEeJB6SuR7qA*MkRy}c30_dqtLd_!(+GQp z14-Ms=fIsL4}LHsig76zW|JN$5yjk~$;6}wVIrBCFS+T^+G_&o?%r23-Ke+IwiGI28dA$BY~FAsr)z;F{0At6)`2&5ZM zLnI^^D2p5W$}1~bx0eQx{N6f~8kEwI`Lr?NX!eng{ViOzVa;*)u`f6(NRLlis9A+* zih&_9_5JSAFf;=FXp=ufhsi`;F}B>una)(XmtXk-X6h+q;^&5{!dM`8Ua{c}b28pT7wAn5-oN84~-CAI&y7=)(TD+hG7n8JD@L zoICI?5K1n1ulMFRXe_@2zRTsf^b~3&W6*<~ok~hD+|hOKegKRhSl(a*o!^ zTkJfD$MGn6uz~sFbz>6wdnuVt*G=JT6mygRm#Q*daZiuR+<$(T`{(Xzo!ob|zt`jI zy34vawdEX>BOkmbHqA2JsZ^WTi|=>HKk1_sqXxj0$b>UA66sKfAps(p-<}--S@&sP z6l2@WHVGnpSO0(f1iaQ`{;*NyIX49|-~ckQIVdWXnd1`qY$-w9^Rf7+Y%1Mmn&-1e z_WM{oiy52}0nUFeuHpT3qmIT|qcqK;)a>8@nr&o41e#(FwdgR4aeUK@;s?r&!)x7x zJ$PZ1hay{FoE{2@>mGpzgHdk`ofS7Wh?b-s4*H^bZ)Y>(<(b=K*{rqt ziYhM6{Sx}MSmCe2?%wmis^<-?8oIR8z8GjvlnHD^3`hcI;Ro&_SK)vlE@*H-5X>T2 z$a{>x1*|KPvjZ@A@Vj?`vN>WP8JJsI%HGtIfsVq}9ov921k1CIBEIb+bf(^7A}e$Y6UULoPQy8H)7C1gaXNnc4kqs4a-SzI1Z- zI&QUD`6I>|5qSuObX#C9`_WWQAeyT7;PPMcWHoNW; zP|QUTOSkE^yM%+Ra)$}jOjvJ0F6{Zl;xaf`76sxL=m$H~Mg4}5R)$4OQIA6}voRU7 z|ND#UtvTrumy+sYkFVM{1^#1v0f{UtlV;PFdws|u7ag`$Lfu+gqX@FfS3Ff6pSmtq zH}s#uk7N)4Gq;nYtw`vU7LkcdAcc@dEA9rw9dR+Smz$Hk^2t0npu0gxMz{S{=m`GE zx&G%H?~CqD0fFZT2#X6`w_s?Z?g&aZa(#yRzxb2!Krd?P=ua#Sg*cI%OB?a%N*A|Q zR0#IgodT+&WzfDdFn33>)gz zd)&8Fj^x*mI7aWG`u@{4@%nFG`+Ft9H4$cRfZDu4>IwDAzh5iDE&66}qoGZ}{g4%? z1aHvL(891b6ibJ z=yo6m2998?=?kq{>%c(qJv)|1p_SP8UTyB;il8_Pjp$9JBs%qAis-5xo@mQwqX_h} zaybh9vLksx#f5D zy02^wI*QC2>LTsv6ntD1R5S>whoDN8l|0abVL~x0`TF&1l^IPUkVbOzqKE3=tg4Mp z33!M@Nct*|j+pb!9`6&`LDev-)!yxPX${W0ID!485f)HKXsX@{{4ni1ce9+*6u|28 zS{0?k^DMgPm>6FG01>|o1m}Waq6#Yi-9fzY$l)GRPexm{0Ll#GP1GJAFW|K zRM+`>XDL@Ssn9d#V>uff(QzR7k!hKgt5Q}xo~1g2=jpClW5 zEE+Nms(H!`4DU+x!IA0$*Zu~Xet6_C`TB4DD}9tlVvp3C?&M`wfLg1gqr+&vEdUa9 z5krCsF|d=vx`c_z$)V6Pm;jQ17-c|D4DYhN}%c*)jZx)x7EUZn7)_+CB{~58#$s|(OeslT+7+i1G95LJeFGJ+l zsPemV~HTRfDE4i`Dm! zmg{7Up^%l5TE-LnzFa@Zu(#W<&VvF8hogea(VhIFi6W0{to|K6L<}(sWX_MJA&4WE zb7M;L!&}M%uCbW{Tuq*}>r4+U*==(cIKYVb;^G42CauZ5*5KdSf*uu|{~oy77gkoG zP;Z+6iSlA3JEYp6x|RqT*I1bd z$S=I9vqtXWz~n3uWEqY~k3Z%%s`P#+HqL+X+`_^XDev%dI$Ifw3> z?)NVA?ej(}f{eFWg8p0t0U4^R&ia%m_IRxaOa)dBb$CF38?xMLv| zx4wC@?v%C?jqs^+TkUTJ+60+BYkEs$3JjU6hhSm??u+-dN~Y@5B;az-U}@QbtN{%* zT53oG7`N;ARv~biG;Y?tYGz%c%GrC*m6^Z8``S$>fBW}{4R|jrQ+B?)6BDyo5iD}A z0$Bz5sSusl$aWZ|DU2gd3^fRU)Jb5QOOlx61!pA~sy02@Upp&{4-VdayKV0ek05q8 zb{^PSxZ?fwo`$kHyVC^B86>5X;@j}Ya$+95+S#dIpP)EYH?@hf$7KDjLmc0A74u2O z@in*Gexeq9^xSNsY%oW?t*|+ET&+k&{(EQOA?bC{OCdek%*+fD!@*gS>(z=p0*qvu ztoY4_Aah6%;$iN=YJA_W)D46A5%JR;Wo3DylM(_cdDUFqDHzI?JGag%t%-e_M5+ji zJ{qye`R+1eD4M>{fEIf_`A8C|M2DCx)p-~iM^=;0~5r9+hP!BH7|7} z1AQtJ*vG+Q>+2xJs6c+hQD#+A3$PR&-QCT54$%EWi8qG)ow#%K&?)|-E4y&HbR6TD z-@4zs_38>eZ`WgWr zyUQx&CX#qme0W!`;&}t$1A~tsm0NxL=mlg^JiKhz-)UpmU(gk=F3Z!#-6Ak}o<19B zr0ty+?CamX zXFXtyDXGq#;2yepz^oe*|JrQv#ukdPb;-g~hK14-NtvA^(Dc`@nlQWjaFw+d zBA^baE8fSEtrGO%_IQNHn&ZkNM2M1LGEYDHY=p?4$@saVOjROv$u(znV%(Q*XE(OA zJowLH-nFu8!k=nd!h9kuq-CfVUZ-QMk z&`U+SRUgY9#l7*B#W5SS-n{drOb|n6yh+c2{L>>67-qKGNnU0xbBf~lm=L?7`YS@^ z4*4sGo2swA6L{UXHwmd4ILR!I&3l@pVhIk|IO5{st7~h<0IMKF6~D(zIgn;e9sR{7 z{RZ61_blCvDqG?OoPM-R{DJ%ns>(6xFrEifaWJZQ?;j3Po^PR$NGWR{&&tR|Y|8$p zE%$Vsr|Yvukq@bYTl?UIJTOc=buL@C;msoRPzWIm5sDQR68zM<@({mRGK_4}t$;`= zy140i{N|mfR=4w>XKtO`_~4%I`7|`d=T?~eSXT9_}wrZAKfE;^>ySEmys#9q69;J=kbx#N`s1O_iQvo$p*B@q(rR)=_G1LdJgJ#<&M*iRVB1v)P%EN_X*5r6 z5ULFSotxzbFaN*e)^68CaXPJ;{KRs7Gf01mEAKd6h|-~tJ?s3YejrxxDdK)L!C^pY zMb*O)QZZS@yHbV|A#4O;HxQDr2g3)ll&~)U#+r2Hg3`+k%`8^UqXVSEivD!^Qfq-G zb89W4HtkT1&F-VU_a*P@yZPG(jQoW%Bg8jv2EmI%kgZyeQ$FXd88)!mkWdd@&Bktc z^$QVS?$(ImuQDyp?E#2d^awq2te4-h)-e;EhvyQTh z-z82zBb!J)(Q6Mc_bQFQnDV8I)_4gzA0G`$iN!toQ`X25{4vCi5}ctqfw=%;*AS6S z<-Z@EBpHP)H2>>Y5cC!WolFo?q3}a&g+V0G1>LBM6+r+R&}MJWf3nBo*n6=?V`mbL z46l0q;>B&*jhA}Jo6B{#@@1?(+VPVI*HhjVcPtg2!v9rjRo(ITU0OMcAtjOD{anZC6pA;}Gl#xrZn#)Mp=KXWM-{zs6&4g>m? zuQp=;YO~gfTkHQFi3{X0)0s)-s6dSgtY9Ftpy^OSBl1>>ocExVw4Z4R0k))lh|?=_ z2n4WqVtbKo7f9~`Fqsdu(ZJVb01Whw_T}BEP#Ty{IFfD~c3n1$K;Q0LiE}V{P2WrE zjT=Yw&5s~sGkTd4(+ITi`dfTHn6zsh-wU;%*+NGld#xS*?wTQ;9s@JfxL?zRgJNT2 zby>kI2+$Z9)uKir&^JT|X;>m&ZO0;LzkxG4lZF}&lKIjjIL5bJLfsQg+Sn`t=X|R3B?`gdLnW8rz!Cs+!Z>1t2Cw>u~;S z{Y{>(vu9R|rCn%ceB223Yx{p=7*q8w1fU1&fD>S3X2yslmw!(H8V!;(k3)*L9v-ln zCIGziTc)ONfjw29yS$wpdx=3~C^X_$>(wJ!iMr#?`b4fZt-0_JQYFO%~>yX z%x}8xAT%2@>=)?$I&?I8)t^yNBMf|97D4vOHB#ZTq{zd#MhS@1pV{U}pzRPqXlYgu zGJ`X`1b`ihe0+Q+z#aYelQ$?JixGWDb#=9CCIW$N+*PBc%a$glZ(?FcC!0rZ6z` zVxlYq&g7#uDnX*ZXkdgz?3)6KbD zd?OWTWIU@^)jZnKQ)RNB5fTz&V`I0#wh15O_Q2L3KPUzV2miHU`YIA(J;`tAyX|y;Th_X~I0OCLfzkxy~q@lU|!vxYm(kqg?gB(muSWv3I z0nrJn1pqPllU9Nla%_VbG5DZfdelw7y*dA~s#gB$*;F~@-5ST(QBOudZnzh?yD&GU z;#JSD#GN<~PE0w{>a*F*?%L38_V)I+BD={D2oA3FM58-DM8JRIUWqNqLP;Y{O)~TT zL?7rq=RpBwWxBGmLN4gW({y>^PEI9PxK_!BW8dHqH*{(rlelk#8EAXG^KkulP>wd` zy6K(>&Fl9Q7tQ-qHgDY=ZjMP~T8Arq{DJ=J?c)|@CEW+f3<|!q|7J_v_U}P74+KR* z%kZ!YBI^sD`EP87%gn+e3!L|n{e7r(=gmnNN`e(!;;;!8p#l)O7NC{gu(`(w;!m-j z92gM^5o3p>v^^cU5u4~^S6DiC6082NJ~_lI4EkusHnZEmum%tlI{Ns$;+I!dZz#lx zi)_bkK4TucasL5x1fc3d7ssY=TwRT!eH$A86+{40hfVi%-~h9d9=woavK| z+kz*FADH$tG~-vb6^ZwvHBWaEz9|s6icj1R@t`kgM|KE-v?>ggI)o}2mbP~}0ZId0 zY3#+doyQ>lsI>V(1ZuHyXu+f_Br)>P3$~HwtLgM^yg|517}Z)If@(Yu zT4e6=l>9rjFg*%NQ-yC$s|E$RNbL+_ObU?k-h|BE3EMV8A-aSiXfOQ9V(iiJacL>3 zE&-Z^=xCLXM@kJk7>-J`q?)Qy_~>JneBLgVJY9c`om84lD3FUwl_j>_8$kV3EB~wc zSXH67EgMz_x6&m8HNa2JXS>L10xD2EAk!ECMKvRtoX|30>|;56JA_%P^|};-R`%N4 zevlr%++C76K0bbdL{5`1?2*)b|JSb#Tq#`A3e&oN!tks!L1EBt{K%saeu&=49L}e0 z+K6(8@5u`ZZYjnc--Km?>}H!Hz`er=pz3_h;^c`nxVn}RW6s$gyS+?3{rwVs@oY$w z1QLjq2s3kY6Bt%P>{k%qOSk0+8*uuGvz+6~U;1 zlFDPu{E^WK6fcB~>KaP_OWut^^r+VhfJpZY3I*{jvfST)H3r!?#Ejb4~0Vfb! zhyxXDjOXO%mj<;A0;0p5$|68nNLb|ry(#ExMf7UoZpX=fencF(Z{r=7 z($zXWq6lmTkBpq0_~pep7pNdWe}xA48DcCzmr0j%aG-&J<)sFV>B{%E7do$@yo-x% z3xx1ioZ6@!_~_%yZn3avh0N=fn!+a9o;^N)3+t7JIj_1l+18 z@=BUv^*R$Kd9yc0%No6AV2O7p|cy>+v=V?U8(S)b8wah*>ksfFeFFZt!32kgs|Z^bvo8y9X=2Yvcur z0~!redr4npCq4E#L6Ag)eUTGIb395avSaF^hQA_a*q+VL0IadbuW2Iy)d z#2B@R8ta&%9{B9v1|Z-52*kDRpPjdN*$us0QEFISI6iu%{RVGCY&b zA+nvUY=s?Q$g~qOCIS`NzY2)qBS`NtMZHrETJ0B>E}J`44pv5_M{Ef1EK(VtNXweD zyA?Nv5)ZCrarZ+WKH zn6ytKs8}TVJlXAzUgwwF_2+?^$-Fk=?GWGCcfG!HFeP|%B;KgzGBWRiyuacI#yb>619tB~BDkNXpEZi%HYg@L%5O0$AcSx(LV@eoNk@dCCM>c#x3C$sBN? zh}v-bo09Oq^Q=<7-zrIabm~D%s)Uu}Wz%>_?#E{+Uc!Cphvrd-$Kp2L8s#=fXMT7= z^l4;xYyavkFQN>x8g8Dhu9frQ-n!37E(XFDnF)Lg)&-F4+n|-?0&`mgjG<$$&MqG; z!Wd4(dH=M+exxM8f@UsEkbmbSyA~$cobe07sx>w`z|E z|FpmNq$qpGDQdq!of)}mpppUI8rDaxf3=jvWuRpGJIh;m?WVhOq@p*FlC}R)iO?&} znGtL(VLKED6AN-8$E2^Cjs|S@sD9)yz5cQwLPO!S1tvtZ0TT-e4fQQ7WC0~W8SF`f z1m6z2$A3j*sGy?w+9X8rC*-;N%J($Ls|4k#8fnkfuvY9CL{P*NumIPPRri(OD zU(C4s3xDG1>EWB8J(X01o)cF7K=a!T>L)Z5=wJ|}EqI350^Lde)!isbBg?3yLW#@k zcaBfZKj=1yN?LEwsL(jCbT+?~?H?4GRik>j(JNS03zH4imE^(Iim6CI=tbn~AP+VI zdM;nTjvI^?82&|tn07kl?wKbPags*82_KTe>3g!*XvG5%ga|{i7rtLVN@MLz~ZRnEb7*r?W+T$#?Eif6e<7}zmfAnhbU#_(v!fx z0?s9BY1jvQKi+O_S}Tx4-SWDO&ZC>Dq9bR)n4%X|h%(MJ`;;#XB&aC>MrsJguE`jB zX0x=QAT=fjuoVh;EV3s2@^GWFZ@+U3>&VXvp=>yt0QH3?J)g;kwyiqzF9%}#f|6&f z|F}qdzv-Y&PF`WahTpl|f%fgT8uj4^ZW7KH`1u-$uVl7^mW61XS#(1Z-p7_fM@!gy znJ-_ep%ZO|419ims@ZLb$oQdHf$wCF+cB-yQ=HAi@xlizPf(j~Elxd3;rUvZbwTsE z7W81*<3O&UG;G>xw3hawtQ24E?#2v&u>ytE7gZfJ;zL5}JUCp}@F}Ur%Gwfi$?fH#jbW-^?z(*Avs=H~ zD9o+v-oUKLwSKc|_2l&4<_~#uqvxHO%}<<6t(q%zx6!!SE8V)Cbhc82Q%Eui+|_1b zG$?a^P@SOQpoVAXinM@dLN;N-djA;z6uzNZ;U#7?CuFdFkYn>^B`H2pjm$=o>il9DVaOh=8)G?Az;1!ZJd5Spscr_-|C z99)$cJ$S z5t71^E6?+fELO7qJ1{8`?c&$TIH0e@tMi2|Bf39g?~T&<&lgZwK+L~}WtLim8@FBcsjpLIlLM=KnmXLbV zV%}rjjqIwbssyuqXEH&>vKOp6T!k%p|1M5+c2}Aw3P8d_2WR*^@M{WTv4F|93YvVy z3!g3V^ueB+{&7)9OD~Nk{DT{`UuTBTg;i_{bETTyp-kJjdNECx;{IXBp!1z(6U0GS zW+K__hiEX%bpnfi@E3&B#1f+Ubi&M5mT3FKAv)X$Dl#B5oisE?3DjTTKV5~$aklsH zjox%BTlcD0m*agz4XO8?EscN^bXrxnTbIpDW`XoJ$r7JHBR0N-vB~$B@5VmioG{PY z+`fHc)dj!7LeJ>rt;)(9&`MUL#?pat4pe;R%QY6A=@x2>)_3pJ&~+X7MJ4T+a(?Hj zNvVx=BtL_u$$ORmY1J;n3uO@kE>>rBJWo2is$10b%N@I()?gTRB<(!|GRPIPNACkD z>h@SywSrJ`OT2jTf~pbht!!-mB|U$LOTT>I!6v!^uDAmC)Uv+Zb*Jitb^jQTOkM6YC~$&jZcOm6nQe<)inR zK2;4|!@t-Y8C`e6INv!X_nefJBPWSxl46F`f#>pZ{C`h6SN>h>m2Q!Ub3BD&j*Nze zXRfvl=I(pcIlP^w@l8It>YKsZpM}qnBH7clO!s_$c|EL^=Q;eElz* zcaydDIwhWNx!xNQ5~>l`!Rq2%XOw0@{OyQV9pD_%O+Yt)Fk#|ObTqAsiV6)aEuj|? z_E%z3QkMHK`)_@v{LnQyxwtIEckpeqXU7g<#s@Yi3yRm=D~jP{Pl<-_)+cl!9@~ye z?JldKcbIciJ7TUhH>!O2$exiQ36{IF>QcQ=?(<&y!yJ&q!h2r7^+TcS)<7oit5`ol zHj<-dViRNbUZe2cv!X#xx;C}5SbCg1N0fcYuA`p^C<-brVl2S4NI}#;l~NY zr3Al=C2ZOQ+#f&*NX8qKOn3zrQLm*yuyF;ia|jX%82v9E$(Z~0L5?hxg$!N;4s;CB zL&u8q*BcyFVhnq`FNO8TmnP}-qIn&u-ohpo4@%e=YfDmOMeB6GHCmNj8M(&Y>01f4 zQA|vX@OF#brod<>5&arGn+eNd{=qX4h%qWKP3_p>v#3N6`_G?OIyyRDJCEB%DTDXi z;=3_#?1KVmRf#)ck~+WGc<;>~MK;mhL|f9qEV)K+D`mmqPE8B+F(hXei*9upl9gl^0|*Aq%q$gso0esHguo4c><3!~$^w?!Pk$GMq9 zP;(`5PMADhufRMlcKh)an7?4PgXM3RUn{~S!TfmVDBZQgJruX^B3Y6IAu~e1z`q@z zqhl9xV^J#FU;MdxJLca2*U9t|CyT-B#B4>-QbQ++kwjJqE+%zN->tZ~2pOB|c*t5} zWNi;5ID0(aZbw}ntJ+F;^$OB{`QTKm{p7iA_106Weby!vI|+_;7wul|qZ&P9cWvrH z^$X<3n~=pI*As#o+3Z4rMH@86qR$pqcD(a&eF|j2($O&zmJlgAGw%PdByUMx(wiSc zul?HEOfbH)I(5o8^&=Fbx|T|iEg{$4^`g!{aYX)QghqddzOH@NmiKS0yZZ~OUa)xT zUBbS3voQVhUll#NcUbv!4>-x^u%a;dLSPuo;WsHLaFV+Lys+{x8lPYWxIo0ed^vTD zkp~F^@tjY}G@D3}2KxKspLKI~p;wwLU0PU!7_tw*X4t$pf}&D`%7%c6h}4;PV><7t zx`Spp-%$S;cq5dp@|SBoS;;8OHC=}TJ=fKI-zfB;@TQ|CZl@U~e;E*QjEmj3K{qeH z+{PGa;(8|~CFT6mo0$G8-?Hpm2dO{R`~|1-Qe9E@b&AeH>Cc``qBgY`eg(}v$GYZw z0-v09TBS_+UNKQXu`-XC^%Nd5^Yx=RS0bLjrDNp16jV|) zJFG3N2F(&FqK%A%@Zy{(hOW;(XRbg-qCR50GF%)UkB`=YfHJeCH=V(qWYToa4w3-8Zd+iPI_ z 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 literal 90478 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 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py b/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py index e94ead63b..2c01f4f67 100644 --- a/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py +++ b/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py @@ -16,7 +16,7 @@ WONG = ["#0072B2", "#E69F00", "#009E73", "#CC79A7", "#D55E00", "#56B4E9", "#F0E442"] rng = np.random.default_rng(7) -MOCK_TAG = "MOCK — illustrative synthetic data" +MOCK_TAG = "illustrative — bar/ROC use real run values; hist/F1 synthetic" def _mock_note(fig): @@ -57,10 +57,14 @@ def witness_score_hist(): # 2) Per-marker metrics bar chart (mirrors _plot_metrics_bar). def metrics_bar(): """Mock per-marker AUROC/accuracy/weighted-F1 bar chart (mirrors _plot_metrics_bar).""" + # Representative values from a real 2D-MIP-BagOfChannels infectomics run, + # scored vs ground-truth infection_state (eval_against). Strong where the + # marker carries infection signal (viral_sensor, SEC61B), near chance where + # it does not (Phase3D, G3BP1) — the useful discriminating signal. markers = ["G3BP1", "SEC61B", "Phase3D", "viral_sensor"] - auroc = [0.94, 0.89, 0.82, 0.97] - acc = [0.90, 0.85, 0.78, 0.93] - wf1 = [0.89, 0.84, 0.77, 0.92] + auroc = [0.554, 0.838, 0.536, 0.815] + acc = [0.491, 0.764, 0.580, 0.865] + wf1 = [0.388, 0.768, 0.466, 0.861] metrics = {"AUROC": auroc, "Accuracy": acc, "Weighted F1": wf1} colors = ["#0072B2", "#E69F00", "#009E73"] @@ -74,7 +78,7 @@ def metrics_bar(): ax.set_ylim(0, 1.05) ax.axhline(0.5, color="gray", linewidth=0.8, linestyle="--", label="Random (0.5)") ax.set_ylabel("Score") - ax.set_title("witness_state — classifier performance per marker") + ax.set_title("witness_state — performance vs infection_state (per marker)") ax.legend(fontsize=9) fig.tight_layout() save(fig, "mock_metrics_bar") @@ -84,8 +88,8 @@ def metrics_bar(): def roc_curves(): """Mock per-marker one-vs-rest ROC curves (mirrors _plot_roc_curves).""" fig, ax = plt.subplots(figsize=(6, 5)) - ax.set_title("ROC — witness_state (per marker)", fontsize=11) - aurocs = {"G3BP1": 0.94, "SEC61B": 0.89, "Phase3D": 0.82, "viral_sensor": 0.97} + ax.set_title("ROC — witness_state vs infection_state (per marker)", fontsize=11) + aurocs = {"G3BP1": 0.554, "SEC61B": 0.838, "Phase3D": 0.536, "viral_sensor": 0.815} for i, (marker, target_auc) in enumerate(aurocs.items()): # Build a smooth ROC with roughly the target AUROC. fpr = np.linspace(0, 1, 200) diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf index ff5ef30862f4cf12ef8b8e52b80e815d46bdb816..fb84bce976bd1d80ccc98c26606dd04b214a0174 100644 GIT binary patch delta 7565 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 AMF0Q* delta 7047 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 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png b/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png index 70ecd2316f0c2f70939653054cddb468fc4ee822..5d234612ad2b2c9b594cbfbe63bbf6d0a7e8e096 100644 GIT binary patch delta 23528 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 delta 20888 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 diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.dot b/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.dot index 16217fefd..629a0ac2c 100644 --- a/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.dot +++ b/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.dot @@ -23,6 +23,7 @@ digraph witness_dataflow { score [label="SCORE every cell\nw(z) = mean k(z,X) − mean k(z,Y)", fillcolor="#EAF3E1"]; gate [label="GATE scores → pseudo-labels\n(sign + dead-zone band)", fillcolor="#FBEFD6", shape=box]; train [label="train_linear_classifier\n(logistic regression, group-aware split)", fillcolor="#EAF3E1"]; + evaln [label="EVALUATE on val split vs\nground-truth annotations\n(eval_against, e.g. infection_state)\nNOT the witness label — avoids circular ~1.0", fillcolor="#F3E1EE", shape=box]; subgraph cluster_out { label="outputs (identical to annotation path)"; @@ -43,7 +44,8 @@ digraph witness_dataflow { pool -> score [style=dashed, label="all cells"]; score -> gate; gate -> train [label="labeled subset"]; - train -> metrics; + train -> evaln; + evaln -> metrics [label="val metrics vs annotations"]; train -> pdf; train -> joblib; joblib -> reg [style=dashed, label="if publish_dir"]; diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf b/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf index eb823cec095e6839e40e5b4d4d4e87ea43161806..954b46c0e0f38f7239d22047257c73b0e6f68478 100644 GIT binary patch delta 23284 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%0A(Py21%9o_Kt z%Gv&`Ai$EAT{dTU(H8zu{q|k+rI&T`Q?Bz^Z%C*u@TpYKapk>M&%p<$)pPKF(SDig z`NY&hq-pgWqLVGv^U2PAw7d1-XXa8l@URuJXXD|KcDXJ6tnzN+5iL{z3zs?$C>NDO zBG|arKZYtQo4PeGp@Ly*>y(P9!X>S6RGwVNy_Ck7<^e2^m~u1oZU2N^ zN^dkDwA5BxKW|v%ZL1E}Z$qPhD(9@(obj@Laf+QHjPqUaL~&OYtSrA`NwBurOykq0 z#&ub-xNf^ri7Vuz4rTTq*L7>%K8|}+L>Bw2mB;IKA?2|ZQci)n&Iw#T&L4{BwPsiL zu3@8AANolKELH;0qSh28cSnpOFVPinfxH-YVw(OX3>eaKbal<3SJi}nYwv8s^Y{a) zkZ#u>OoeXBBShl!suarRBuM#-IYqjET#UTe?uSK9c-MO%h~vLwFof$-558@j#W{@y zm8`~>9(ytnlHW1npqJv`-L!yOUQc!VdZb4c7mO=>4szc-9Zy|H1e3?Z%nCn7&RvK9q`L zS3SsF1%0>nDi6g9KNLg73pV(y035vI8TKu888T1n=MIi|oD;ILrxIbiy3afPP@S}+ zd6n5g+}BhNM@kO3smu9*#{6OZr5?Pk<<#m{iwmt@EQtr|V*R>*ZcYYixmHFiSo4&t z`UQTw_JH$VU-G`x8?~b>548+doRQp0n5HT0J=lL;)X~oLSXNwCxyut=cFj}{lqc3vM~IUn(cSnOhcQ1jsmJ`X0s`WPNtUOPKxjQ&5?#w_!6JXCX} zCr*yFXbIV3Ct5^*kr*#oPQ>?|h`K`Hcb#M`vmeh-vXj`J;G6UXJhMpyd}p$f!HB1q z1Z|QeP#i_FX_yR>GA6PPdzjQa@6CDftTS#Q!8MF}4yf_oO?wK9k?HZYg}f* zx?WT+nFZJS@hDXkP6z1%dOiqz$BVL;6(<;pDJxr%I#X1CmI>@B+v` zY5UAaen(ka|H=dkodugP$SKd`o{6{bGr)qo9ckqdWa_?Hmn@@|;Q_tU37s}sGgf^uw0YyJy z@XRMogM)SzzMYRNA)p-*{l2CUou=3`=y*kI@Qi_f;pxEsvz>1`wK`jFt3~A5THBK4 z5ZxS{kIe;wJYw0;Y=8l@CwM_vby&EL|RpQC7}ozoI-Br7iMYpwUYCg`;pnJ6Z!9 zXd%jfC7nDv6-V2u92Zby8V!`oc`g+(``Y36LOysU)b@|g5g(ws3{7W&QDb6(M-fk&W}8^ z11}CV`s}iHusjh5^}{I}6y*~?5XxUp{oJ@uQ*viS1|J9BO(r3t_1esh#vTino?Fa;IORwa_0l$2s}iwQz~oc zs8Fn-Rt`ObFz%1kex?_5>3NvTw44RY8NwVnjjOqPPKS z6;xzM0)|LSs?-Ig(7IJx1r-$AMrn(*3Yqym=gxrGZ@<6K`~LgR&2#p9?>WzT)^pAc z!U!RM88ssY`KK?Ow)oSQ-O~^nqDP2KnZ9C~fBWJ;tUzek9EAA3vlh=@xH5b1cL?3| zC_-Fp_JY;34wQDK!4qCWNNt=mbJ~pK(aT#AsvZSp{pUb|Wug8zkZ*>3)|`dQR_RLZ zFGK!W$X{KsX!lvm(E=L*L_PqhkO@wvIh~` zSq?7ft$>EM6MT*rI>-*yia7QhgJgj{hmlLnR(fTq>*T zDMRIuR<1(OuW!H{Foz((h(%ZZOxIPFLsyWWh1U_ZA#s8Aah*s(>F7iCCCuqfSzIxH zSHW@RzL<~re3{-t?@;ez-&P-AYAv&uxkuYayBov?L#?U7eye+)xWF*Sbi4g_cbD&D z{b|Q(*XP#1IsWGQBK@o{;dA-9d{e%yFIR3-xzVN?ZWebs{X6ytMX#h8S)L$|mlv>{ z<~8b^S*LVZ(W$ydU9*nWEyLzQRLF#X2swmN+9Y)`lfZ z%c_)~OXrlHh9%voyt^F!%u%x?+OIEaz)+2r1cK&5bN|AE43egxU}iRBbJzkBgrqF zlFmx3G{!ZuhE8+Cl1R4#0Zo*D^_d$8ZgvE#RFBGD_SfqpgtO_u@vP+DE9HdISnrCm}?>XOcXNEammNj_<@ z)GX~x6`hq5lI(*qVSy!##4~qc#B)3=^FoM2>`rzU8)LiJvn=1mUStVk{p=~ovFsS} z+R&GlQR*%)EAOV`r85nK#tloCSc?nO80h?__V#x6uP0AlNn^v%MLJ&)lxTh2d>Cv?!w}tod|cKi+~g4N2kzP;I=XB^p|kh4bDckofO|#&O`U;0 znl(OrQ6{V*WGFHWH*m$aV((-!K^|ut@10F%a5JUpwnlH4?|trn1M4TQ^VajWZykSi zo!9uy?(;?5lu0Yylw*ZHB+JmpUP_7$m88Nj!giB)vOLK!+i;%$%KihsWK?h(W7H`o z;6t5YM!<-S&RK|2$ZQHJ$|*Be%&NK3+-zpe%Pd((g_FV=AtA6npk8M9PA~Rs-vAaK5KJWc|8@K1T z_4r>}zT%gA?p(Ed!&95DJhKxsEn|le;}0W<<=x}IdE@lE$6#L9g8+Mj9Sk_TR~?vd z#R`jqY!N$*9na2Um$AHL79~+KSk00FF(TGcSp&={!m`d)}wAw-RJ;2J}A-=KNE2h7Wnu$V8@rYmg@D zW-{}+Mbb=vov0uMDRsc!pncraysP+CX_v`m zr-JO*WNN%ybWizhjrX~#a0{Cb=d|1|!+;@{sn2mfNjusP3xwuE`J0z#X)_mBg% zsTnqZ37cH`uD&kSwb=Ez{t3fggXlKo7-Ft27we+q%W?aP(nSNKH+f~8Mj|#V%K&NY zv|(Gqslvr(2<}7ybQ#N_cR>yVcZ1$l%DR>A?glEI z%7EL;yMa}!H8FO|yStmmZ)9fjxvHTCFaikcg46u>XP~CtRFh?}j!6^-8 z=gpEV%7P5KDQxDA9&C~=9!#|pAO$2OiZm?I7z!kX-C<8N2hBxc4KL=6PcQ$Zad(X( zx983u)$}|Yeq?{e;;MqRJxyf8?F)y0YpO4%wvXUm1o_0(cqHjk-5w~Zn=K3q1o`9{-AV7KB(8*^&9kG5XP7})wIZT zw}~-f$~-sqMJlR6jc762igv-EFCqzC>FqYSZO^lx829sk$TukYXYXWxF;-BUKb@a%o_9>VJ#ubjl`SbhnU zb^CXF<}Y~ty$@fzN28r`z|#SLz*ArRv3e()Y0E5?ZkC2;P0E~^xkh?Ox-aWF>x+4> zF$T%ub~^f2=6&ekJY*svN&%Lg^6=glFU|3ogrQ&ia}BgnnDjBYD5*`>ecI|4jC?Ic?$JhVAKp9f?m>OGb2x zm>tr~tlJ+>V|9Jf-4x|spR3$e?V9S^?>gz?O)j5nk?V|$^|`jVi0f4{5#%R=W{QGU zOcZs!ZF3xoq(7VDQ8ygFMx(Y~nL305qP+%UjkUz_mc?RKfR1G5B)$f`(TvBxjxPR#w zjqIsJyc9`I;}V$12+;FV%}8B(4n!mTfrNA$&IXAw~q1ru`pBfYZ>&A}F&^ZdzjWsCK|yDz;HGtBMy}5BMMm8@+c~a&TuIQI zZk4kNhpefevUBo(OH@V^G#vC@Hqe>@xzKOa28$rO^dtFE;v~LKoXyV_#UiEDQfe=D zRw$L0N_&N~o~xI}DGine`#9%9ZlN?oS!h{kpW$4I(%YCdjww7ceuqneqa? z?C`RJ*{jppvIIJP)~rxbUjZXQ5&WRK`knD$4=s05Q&yAOE<8G5!+)!s#N&t%}i8N%r!IT;P?ESy}6Sm*^ z*9XqTyAQW++|b&#eq$@K;_Qc3#6Rmf@z;BB1~$C=&bz<=?K{Vl@|zC>UV`Y2!$?Z} zQ5^!pI>erTW6w#SInMjJ54g*I;t>5o$?5S+3=_=o@@X(kYR&}RE=4{S!dpYTLL}sH zxQ(H$W^C@j4F{c}t#rV+L3JTg7!2W42veCw=zy!i8g^xcI`FEt05wu0W0JlUm^zml zx-T{KjH#G8{7Z13s4fGARt2VAx7nc?J}Dt?)Z46oVVmCU!4^YWN_elM`aW{a6tX*L zaHH_1DM(N;zPsSLc`F|A-F5t_7utgLgBSn2y>`avb){_h7h|U0R@=G%P){~_a>3Nn zUp(9M2x(ols^+nWdp_1=77zaME8u@w$N^O`*>En~OC~p)Z!te)X3SLCNxndl;!V#k zl4x#!pD(M3<@J)4_ed@a$0C;3NjjrwQIM6f30~2oO9xU23Atj#ScHm&QgNVhI5U!0 zg(|U9H_SBBe6!^i(>Tj~VTL%{vYNj`SSB9kJ57fy-|<(Z9Gy7_tRl=%2@X$Q~VN6m*F1G`_`t$bT$tB^lhOaWSrGk#*x~B4EVGQopx29{AP|Cii^*g(BH4CXG2li6Rd|e{13$0I{%RS2 zFOu(;iQGXZs#3KXFEZb4CT5z`DI9Lp{2zvc#s~3b>*ZOR*mG4~YG`nRSc5;es|V6w z0>w~4lnm)*Nm5PJqFK%mZmjAP*|_%D#y-yf8G-QHXjF~~Mx~4bO;f0h`Nr3_8~l1d zIg&UFit;QnCQh}Zz9zq=BXJfFNd4-6Dq}_CL6eG!QwM~;SStvOuZ$IH`c+JvJs|j# zr4}g7&`RM!hfIF@Y_TJ8s#WMqpV*29kj~_ru0Q}UOmR)CZM2|*wB z{vk^gv&JHhy!u=gO(NmhG?pD(;K>;r(u~H!lGH$@tb+iaCrj z%~6@^Z-Y51fk{~;(tva%{?Z09PQ}pTkE)arC_*_9K<BY9u|;|k zJt{pczl5Hae}!HV4#;n#-^!=a2lC(07xEQ!NzRiICp(c{&Ou?hM6O1vEODyEUc{*& zZ)HIgLy~NhBpFQqYrMqCGB}K)NLfdaB?e(GUyt>fqN=J=vqYp0>^Z0cV-R2rk1F|z ziZgY8`2$6FH)V*P2Dh_+`&>hc{;%_fIl7U%kqN-Oi$p-rL~i&Yjacj&wP5^me8I2J zg?!G)-wwxb2jaMI_M!c!RhKll0nLK8Qi(O47s^~QA7W<5>ILFO}Pz* zhTM6Exw(yfTMQd|J!XI0z1NWToa=?0L#|hIj=4_e{2}d=9C5fE`y9}@yxgKF8_m0k z9hEmptc%PN=SEiOH|pQke{c9cVlF8%Vphq|DsmJAY|g2@7WE>%y!pm*;}+viW5USo zH10QkYh;W@FXNzpmY?1E3!B#~po(m{z{}`*O;e_!P#_DeHC4%0QCJE4!+pd1!(6yu zlqz-~RqWBODA^UoQAfy`nV)r(KgkmxUkuVyUClbF<@SPn~#+zTQ#qnJ3QI z&s7#W=6kyQ@7X?ae&T*FX6ZubxOvHtjdjYv&_oNu(gmBT0#i@*M(bu-^dat{%a^_m%B zPqPs^0oi8e1~Y5pv!&bSY&tb>`I$SXZ0TcuZpEq>pI_E=AU>D-Rm<41n-h;d6TkAn z=+d4m%(Ewsz4O64$3LPTS>higm)n6HsQECG!9xg!i?r3&A&_Wxfv=`F$e2NPMT(eY z7$T#8Gbx!U6B{fcJx&N>g;dcfEEbxDtpba{uG}TWgf8I}NYG9)4>@uF1GAE@9gM7- zF2bL?RHiM6^_0=knF|Y*x2f=rL_&__dKmTn%_U~)@7t)MP89d(vfCEq-FIKx!GqRF zPR8z?%HWw#lj)nWups{6=ANHb<+-o<{TH}@PGmy|4kJ5^!I0(vyRn$5U^)#f16oYY za=3~dqFHaYF(9Z-;O^UWvOXkfp1y>;BrJ`w)8RX)r*FS#CyVX7>@j=7&f1CXe_b0( zyS@-jM+ng+>gXeF)z2LL(a$&XMj>S6^&V^xP1j6B@Jq0^_?evA8*Re&ySi4qT-m;V ze16S?Wn5>^KejbI`$W%FvU}s5;~!ev^D6XV2zcT1$U=~Fd8%tMSCfAEhfV;ng4pZ>TS42SsqEk%U z+3n&E<71{Su8Z#y-Z4og)gE;-Rw>PY;8u!pscs#9NGIl7CbM-yovzmS2!2$4RQC$$ z(7&ZSZhTic&3qvJ-tZ^oylk=XJR^vbgn3@#SO%!cq!Y}(vLPff#PEgz8$x=5oL4S&f7$R8LyfAVhLk>PSgRaE zR0x_(8%53hN`??oo2*PMOIkbaMrV3dN3o_$kLoiWQ3inKTLV!=TQEzD;>2Koyh>Mzk+#MPp%lrl8 zZU)Ll7u80c4jg!0$TnJ6VdJHAS9+cqgin3AMU8x*z24(u{qAdD(-5 zgR@6xZ_V44C-e{W?^T{RLN_8%(Q86rLa({P^uY99jd{&^r?W2vz6pMtZFboCv<`Bh zJ;!Smv^6NjkNRpWP|c`+3pkRl>aJCXa9*!TuE_N2WqVp-s89|$ou?dFaj1?)N3(-< zEHmK{%JgL&HJvn_F(pi_&s1)zHZdkwByU-u=$a2$MHQsBmfv&kGL>9ZJ2sr7v4*fE zs9}kNE*NM!E*nG`Nv`NSK#C<75N`B^XYJP&3|qE#lhcS-#Qt=D@%G<8c;t@fX8vi{ zum7^+xwUKd?7L&to?7?VP{E8TC9wx^*(Z-;y!p}QtMmSSa@C7W?(e&fzWe$cuT$BR z_=-efz8N(BFk*njY*9if&u@!9!eGMeWcD+JS%EOH4kj4d$;<^rE&v_u1(s*q?r_re z5xPdB&AmqNK<7z+QJjW>2KR1_*SfgB{Xn09qCj73;C;F1PPJ;J3FlZ$<|0A^!KpJA zg)9)*v+bx?Zm!-J@>_vV&A^&I|H~eWImEe2J)tmD%DtSMT^fElH+L%90z@-6vUrY0 zgwT<5^bfr49=dX@Es-Wy{JN9Elky5)VjWB@C4)&x|3c7z4&Xm40?!Ln9n#G@T9n;~ zkZ?FVS~bm2IH}*L0pUpld~=5DER!2rVU2kt0NhQ!FX?j z@q@ecjT(i2gd@j+dSPtvhXQ6}$Y2fl{YJyf7I&#Plx-`ug!t^zoR=|M%D;?ia5GHL z+`Pv@NP`hj_9Gt7{SQ2tui+uhNC+5K0v`lbNhzxY3^BTslpPLhMUD(&vD?Yg|3HJJ zG}boV$_Y23fy{V@)Z8C$EuJ`YkfM9C^4=eYEm|LcDmnPk>)~O=Pb&5~PsJa-zedAB zCj=YO381Br>Lg`ICS_C6B6K&}kJw#Mzl+`ds5YGqbfu_Y-@@X;w9XSJPSAUEK)<{X z`o)MhsRm02d0QkFTwrlfr2S5nAb|&i9|XDA)SDsEi{wc83Ye|(O?U(uA>JfaEA@B+ znIKMok!q9$csiLb&XexM%fvgS2k?6Fe(8IBiFjOM821t*QdIn<_z@PU)O|%sDrarnr(?~-hcMQEsTRE-%@mk%HS9_D49jd~G24N$s;kj=31{@o zR&YjXUNH$L1#&l-r9}9d>BEmS=33$cfIraqp!=2XZsaU4bA!KA29~MPP3Ht8Al3K~ zC3n-K(T$+zjmM5{Py2GZOZvi{AKjTTBbznu7 zcRAfo!h@MB8)Sx0v)k-eJHvZ_7)JnGj1V|QZvfk6bAY27VtHU zBp3=%^+uH#czS`S@b@oHxvOqj)0jIRKCwQ207oDGWxtB5M;46P7k`)QOiLeqTm0m) z=i~9c(+c+W?^p5lb6@@|H-p*vTlAR1d1#)FoIjq2C*R(OQ~3-?!sG;U}-Cc1}pJO9SO6& zgdAP2F>LA2mWri1`ovLx;smxqtk+F3PO!|zGg)vWb#sj~Ez8+E#MQbx3@a@wtsB@D zp+)`$+abPUd4qjh{D}Rd__6Ur%UA3L@q+P7OCFyzi#4NIQLH9v6)TFtY_VFe8^%^y zhk1oqCE05AUpI>l{u^y#V%2P7F+UBbIUH`kUey!eiK(yn<*jmmm&|}|+;MO!*+~dU zxeit4+f}7TIjJxTG*V?ha@o>e3pDQ0B$?Y))zjcS@9J*oZh(Zg2>zdj@=sPTH9J|e zlr_uv*o{%rXg-$O1ivPSG$E#=Mzag7V^`GZ^h7OGnt7tuWW<6{JLHLqnI0<4x>~(a zZKvMnjatEyW&jL-M!TcTYOy;8i4vq37E+oa-UsA;rX{M=rw0aMlpZM4$uvc1N^f;Q znbiShG({j4`NNuZFU9CAz8 zB~G4l!@?PVa3p@8RowzV ziJu}dCc{6u%RDoYSt%0>@3(3Ucd^Zq+@olR*urTAf#jyeeNe))EXVRC(nyvI@xA3* zc_p)4KFxf=3(xU5$cKfH807~@<%ViQ9b3oO3U$(dT6Q(JLwbY%1N$L=j{jQt7yrGO zW|3u%VORpTqa=YLz)7MQ5_r%!Hf(m|uAr;y@)*~+v4?z-ISq&NTVfG61;H`Hk>Qgq28 z$p?aYQ#*d)>-ao;^waq6ySdJ*M{q2@qGtww@!b)>Me`3qzp*_)0|w+olhxvx=J_^K zsZ`o-QEsuZI(-J+g>g8kzC)rVEV}(}41aE?A*JqIKdL)GWy589!iK6lu#7dOM@5RJ zvgo!g2?WfLzOJoEuWeNew$*(Te>=Vj-+APzhSB}*i{H<68Z9#qEqpcJ)AJI8H{V@< ze{Y&W>+AHySKKGu`^bnq=x(*qZNfIiX7e~a9+p*Do6e#0uzMYcjBgkjhr{V1{&dw` zZLM~w?pm%^nygGTPqj{QOm$9jPxd_E*g+Im2E$k~bW&Q_FJQsloQ~5?VLB|AcY1wt zySxf0q=8b%wLMnqST-~)u_`D~z)~fD%my>FNJ;X*P!R!N5>3aOaQ}Dkh!@-AhmM|% zckX!$r+@S(>{Ze~0BZvT4Uj+}ZToBF4`9-N{Bs{P zoCUaB0e(_C>QEb}lX>Z&%L@$C!8u=+-i+=`--;gNUSxi0ILx#g-ZY#-=hFX~ZZ=xd z&FSe(E}vu0_4<7y4U=q>(cOjovn1sW*M zF+H25;Tqidtil2|bt(YJ%0k_Ew*9q1@z>9H$3J>}KOXklpK;#6qlK^iZ0{HK3%}g( z%;$vk`}WFj@a=y%k0%~D`%dp&+n$bp`|zvruUn32_&x&!c7^K#nct*;`We+=E}GBY zO}3C7BKs1HCB$=tkvOa;__&-rQxpgm^+nnwxb>WBFeQ&<8fh_y{hZ1X&ZX}CB{#%jcz9Cbv$MW!nzLPQYDbSV8|fR*g>4C zq?g92ax>kD#RpY`P6sXkGp-ttMlG0wt@KnHaUL=lQ&!96GP-4duPr1%^kwq+8Mt9| z2Lpra)h8l?pyjB+LK7RQbq|i%vC5bO?0PS?>qWh!XFC&@z|+5^ZFklsSK+lo4-yzD zA^=N8s7u?>yly50%s(0kW-_qnoxkBgO~ue#vG?3T)~)QT82Q|PG( z8&D?vvBK37;s`zS*tdIj+Q?RtG0>+B@YA{UvXW~F6# zIX0u0!O}F2EE`Alml;)TYSF4r{xg|8Z)6 zjCE+!z=QS(*S}(z`qtV-FO09L9~fUScJA!E{_*o?zTd!gn)dCD?T!w>AJ;bDvEj;- zZ^r+*1AnC4{@~=HO~Wf@2OZNQCC|)U^qU!T-(6>X;GuQ5R2LS`&lz}d#qyI)%f8m^ zlieVr_koOdqD(wmHCc2>?!4+*&KGH)>}20iDLzz=h~G8_&R$kePlr_*FIiWyvy&L0hB&B%k|vS_|0V3P8MyS`X`{dj<8EQ_isYEnwgmeQox zmO6#iMLPg9jN?&4DVjQeas`z#QSoc)o-nS;=}Y%)|ln-CAi;xn+NEG zp5;to$?B1FHc%P$G{}3pN2x=%sA)jX^c)gmSc)Q9%FrQEJE`YYEJeZu=4QX_HxSuv zV5JnI^aF4C5m6fXUOE@KzV<_nVTD@5)G7<4;AuAN>QhYQ>IckyT<5-jc==0l!@i{L z7l@B#zG1`2jW@QLyx2q?=4ZXp9NQ$*ei>{N)kIAGoW4a04Fs5CyTNJ6)@AFn4gK}~ z4aLSCW?haY$2!VhXQ{K+rOma>wa!gj&95-5Hs4{pBW=B*#k|?F*?Pb2QF)Kq9!(rUS>^V$p+Xz<6CI z6pl7%VAo_xRUEgU-%f_qQ7Ulg9QipIkjpVm&pKzDoD?%z`Hzg%_{WzA^gwLHZ7& zeNZ%DQ1Zi~!lGo{yKgei>6MIw$q$a&(le5=)19<-^9@RWk)PYj?FYZzj}h8}cA*$z z^R=_*XV68&S^QAA6<+r=`(d4?UjgZ@%?N|HO23i1E+>*79Z|>S=xf@yMi_J(Vmr09 z?P}G)kql5cfE1o%r4G{6=1(p$zry=5$!9PF>4R9q60H>v?YOpsMe%a3;Y&qZktx6S zsbMk25V6L8)7h4|hcoQk_rpK6&mO@~It+R>g#M^jcx;|D(wL2J6|LC9WM!d%#X&*{ zW(V_*3?mcB;3bU1*`X{yjGy?k8wo>}HfQ7P^sryXvMYRDt#wyXHcYXBPCtbPGRQc5>7qshW8@l(70 z7~k36hHL(`6XR{+{ejyKEn5HD%D{k)m^^&f#lfWfCERm%Y13hR>mNVFP3^Nge%^O+ zbJf^?`>HqXJQn|V^RyCdPRg{UsGBWi4}p9gp!(4568TT&XIjIJ} z!KF{GXk~n7P*h77?ZA+cEIDUH!VEJE3~|UA@dAQm1tbT_AaDqhqYfgHlZXnEB@Gz~ zN*FSdljIB{na6$gUe#CLKD)Y3tzNzNK7IO6cdc%mE|E9hZ||*r|E_n`hDcBo*c*ZiIg49+YAk-KsZn4BzPJP-N zwj2evF3*`{(*~rd3L97Gkr@IWD0Fap=tPG`n!DwNEsrKlMFt(6GU|;SVxp3WGgaxa zE@6sJr^(XXPaTaFe$*fQC6YU+UezjEWS6V^IjfC+M&h}gNQ0TKEQ&;zx4wSM$6FfB zjFH%O<&l6obCj68(%4nMY%;90Vv0eye+F7KSodB≪x>e41&7%AnP6VhDBw=EUWr z?rivPQTVyATRwBcoF%X=e7etmE3B`H-NBzxS+kMA1{C>-*u@A&>C`xvo__6d>~`KB zug>T4$(0OidTjP2AhFSk`Z$`cGRW}v;cH=Src0mg+KEZxtH94c2PVmwzBySswlx7B zN>i;;E?nGXDaXvIX1XGI_F9ToGz*HJjvLAs#rm+ke&=N;*pkuQjrnENx3Ywp+LI0? zv$ZjBPG_0i5hWt9-@wvEHemnjM0J+uFF8z9&EXBN zcF;0-F&m%E8jBOLx4(XoySVvi|lTa)wdb3~aX3#s!tI6(X~s8Rz+DW z6~$!VX-C;G>0Dp5dQbD$j3@l#L+Mu*vC4svKj2f6n#V9jAd$>+j88@*5n5M6F>SXph;oRC+zpGapmKTEwNu>nA}b3S?;|@ z>)kEK#`@tk#?sbh{m(VheU5=JlU4LulY!4XD=lpsC&jVR9;?yU@ZZ>@geO7DeAE2X5e|xZK=-+CM$8 z2PnQk_wEc>Ax$IO7VKZcDP9Lpm#8aGZNen-oZ@8ebwii?oh+<}?J&>Z>3NomQ%rxd z&r$d(>_8&Rk;eK}kg4tdsND+x0N^$eJ8rAA)>M0OHAcZc*Z8b|uF1EwzCIKa^;l;8 z{YRIB&J91A+sioAEZcwwUF>HkT-o7kK`{F5N3Z>)2Rm_@Dc0D#&_>ADb=;gsgtYI ziio1BE`cdKx?+r`^tHP8*sUh9KO%ye7FyrtaF=`<)`~k!@$J$0 zB|SZ$*DRqmg=!9{^k+7>52`_D$@H2d8?oaz1udaBwGtqpr4BSRbJX+x3cyP_C0W2t zSoWkDrSKX`yToVmEd+%&HB$_#T(m|8lUcrOJSpfXW^KavlxH~}VmkJ$rV_9rNtsFN zYWt>eHdt|gh#c0y{LE283de=Npiz&%(%NpxDR<>}-wXGNfV4(!I9~j!%DRG#R0Yde z0c^Vo^|$~S+Odba>W=cUX*z4k#0AHY!`yfSLI`H;rOf!L=iLGAOUB|aY*)BDD7$^c z21j&JZ!x2Z9I?-YwKh18riw-$MS*M2dmO4_xQ@5WMdf#{eN5}KB;E({`wojSUSg;| z3|mhRXH&|mV@Tq``6Bo5jU-O>ICE*(hh0?ZIwfGnx^A=%ANQoE8RQeeWnan5u#(wX z_pOVI`BM`2R$rOB!I(ZGU1fb@UB`=r@W8EyOLvggUC>u;DJXX`*q-b7#J}!H>r&K% z-pm)~R?bbW6k0S<5OweCklT>mWWv#N@oeRQS1K#_p51dtvCT|V^rJj+=9Ah-%lH#+vtX81nu9F& z*$DQ|2g|a4qOMW3ne92tWe4*6zNaKO2tJvEWKi$R`|7E$->j^y5PML6(O+>}dC}Eo zBSuw}cF%ej_T=NQT~hx;|@$ZC7Pie3z??ewXUi>F$_afX53! zE}`JG==8+T2_t*J*mH*-n?k>$!gw6aWOiiLTnqlTqqg@%c3~jF^T0}m2?C=X?9f6r z&xP-S9nQ2$_e7VuL*6It*JAC!2aZ9zO0m6qknPXCr8azz6~DfADa;B~54OTn?M_RB z^Sx#4g+pNPZ2KIaNz+)**~&eUv%5WiX0)hTF1BS2`w3xwqE@N@^Vhciw$Zkcnd~2O z-|V+%5tjtb;Wv#~O9r7)BI^i(CvooTewNO7n}MvX%4IV>N2fwni65=V^P|)`x42rk zTfhO?vCklecmi*>+cJ7gW^^m-3@=e)lt~KoKOqa}WRN{kbf`A%m(JxOnPE}j3_jP@ z40_0TA$2OQX64)x&R@3l`&8smxJ<2-;gta9s8-t&(T)k%h1r$2?y3S$ZMR!zSNw|z ziWiKwt-jFVr}1Nd^igBzSb1oPdW|~!QQg-2q#X2o3%HkVe4z|jV5j)y8SZS{Dq23@ zbzxa&ok}J=D}hZh!jJ5nL>3@48;Wbp8vHe~KW!s2tyogWO64OOFA`e?>G+8astfgx zzex>@Pc1-~#LL-Qgw5sfh{6oFRa{xJ!7Sx;E+{SPEv)00)GKgy0)q^3#AFr6>^KD9 z8MM@>@M$z$TN-a>me|9JNFSe9H#c(#MNgxkC_^w&z`<{vn_J(y44@TaugGhTXw(VQ z`x&9?tou9jF-c1L)Ps4fF+bN&IuWDHD{Wn!OnysjUTiK3FZRz*v+SYv<4K{lYM$IZ z8m`Lu5)Pc~=mVRJ2W#}@tnIqT^mP`h+*qq!!_oHOmt6&iovWRMs97Y(6W`ovZ2vk- z{2{Ynhd>c2ZC7_P0FI&Rd&;oK2uBEazaifZesl0nAYXbLkD?{H8 z;qZP_iNL=)U;VR@Mc=#g2F*%tiw^?_TdFopEc+l?Pv9A2PakQkhFkD!O?lueLB4+` zO0+-jZcl*juc#$#>KwH>=s|WO!@APgV^uG!b^7Lu#gA@)>h~nVc2{az;y?dsnxnpd zEoLw-J!naF+^u_u;#_(^rJ^P|*xj#ufPHIx*w7^4b1DKQMH#|;Njre-xPs2CmqjNS zLqELFe=aGr8=}ib$AZw`&59!Lky78}p!LL~XKzhxGuEP1Q`x`?QwDnpu9B0NMc0RQ zI8Z#*E`B-%#2fcdPQ@-}qjJCaTHK+?V8aMUhACMWMUUf=jabkuM}5QF5x=hYg0JJ; zgkrv=_Op^SltS}!s4}6Y&4@1T&jouBwS}e*>qO}RB+%F1A>838F=xE>#6&RD^9nva zTsg!(E&lbSYYIfRwYk9PE=rtW@63o(Hg`w8gk=wis(tm_y9SD3v)K5+omZfl==Q8U zEEPU*)LpoMUC2!JPk@N;rZOQyH2_9;V?Tg);9FN zKR1^z-N^DqLHVg(wAyR(9!E0Mz{(~Z?v)~S3=#BK2yiZP0r@K-t*BL%)U`IyvW@YxyWB{a}~1X{n4OLswDLw*&x=Bp_DGV@;bme z{o`2oB<1A+)5F1aO66S5QoIYb&Xlf)z2&;(c-ItexPxhP?^WdIQa7KKzz;_*TXTRF zU|2SY6wFG%Ym9uNsTFsLmg1;cA$8MKK^@pDy0pp*Me^a=MJ`aV{KC1rDFda|L&PIM z=UQ~{E9D*5HQsM6sC}&RogmA^w%klN2LY-V2KqmJ^VLS-og!DCF3bGzJR1`-uGBz1 z{3u$Yl4uN+q7)!Wah~~t<^cPQET#TuQKy|=FwBK;r>goCAC9-FZIZzUHvnF&Y6*}e zQ*7uK|5)VX4Ol=vP`HLj5NlAjv^Kv?Rh;GPk^iPPn5!%L6?(|8OAFXI`DeCp<;U<7 zTlh&A9=kto%g|||mpL@v4nZ-VXh#o2+!#_a9TOoU!H-+K>9de zLECr>H9plgRIRfhH5?H~alvK#*+<1bsN)d2ntoHgT-y)4RlmthzPfmri+y3rh>x02 zaekEUJYFX7c*f3N3>X;D>Ppp9fF;-}DNImGTdREl5t-cKH{uLzmhEu5zW0wkq!IOjKbCc$AvxZ8M= zB>Tc4D7jEQPN2xMAOdbqj_S$bH0a5(G6ur1%DPIMa3q=ei?SLX6L(`R5FKK}d|0(`bVDVay+f=1cb0;MXNwR*RkVc_Lu15x; zwIT&QNPOKl&L~GJvtsDQVmz;AGBjaw$r4}!C~wN-FtHK9S3fA!c~KT}Om56G@;Ull zkoOF8Tw)RGLmYwg&;G5urEY7C6@H;TWj2)JA2`v2cO>_iUVi3Bk%W;Sj^2O#v-$Pr z*`$1@@*~{T|7TounO1e!KSDdx%eU*lkc#e6^)fR{PkvXqu#E1-)ep~)0PUGTdRL36 zj34etrFF=#jHWsySmJZtnV}3wZzz83B*&)U6O9CWcmIXECygul@+zh4Kzo0%S-wv) zYD*`=#2`<_MM=O?LB`%al=z$h6i*OR0&YD~(A;M$Xc}M?L`m*L=XqC_N;sR^=$2fXkPxhozo4VW_)$fK30lGs#%3phjJC1 z0@lW_V_q-?S!@l|g7Abjy`Q&?S6mu=`0S8~A*VODB}xuS|Kj&1S}gYG(zl{JW?8R# zICy(p2{-JZajWFclEq zp_}e^Bc2m2;S_89pyw`AU*=lj6;(9DZf30%5Y~72H|shV`m!Dd?a8iPOie3r__tkr z+T#WY<4l9tQN3|dfqO6Z{xoIHb1L?}awm)|9w`%--DX{w_BC^AAQxnF`aP>spwwJ5 z_p15KqV(yr%B68WpywmseBJ)tc>%vU_dOT68scpK9;4l+F{4yxL+w4}$`+}4G!MES z`_sZ==Y89qolqJrseKml2a_VBBx8y|9;$~1H|&8ry-b)+b#$!#-QW2_v0S2+TvV^@ z3#J9B9K9y}RHT=Chm*&KN%A-~rg-q;@Gi&G-hAYFvmCYb9Wb8oGVsrk_wf%b2mi3D zd)}5O`Rt?e8=A;20%bwzmz{OS8iitfqci`!04+yt=E7AG-d5!eQtn}NrGND3Hz4hk zm}>=!1kZ7KW230d6WU(`9zU!+y*l@AsNqHJ%*}1?*F0Hl(-z2P#ED{nc}WN#^@jJG zikye+n~-b5X22MF3AvU|*mR_Tg&)WZb`5zv+uv_!_)adP;S_awx`!nfSlu&`hJw!= zq*v%+uXotAzLK_GVMXFpvAVDI)Sc*G^OUm2IW&TG9@BPE*w{L_umq15);M^ax;cBw z;JGMrmL@bYvQA8~)JlvXD%wnF%IMQ={A&0n6EG$;fT)j?4&T)zkNnum^_I@=#9ZeB z`?Me0<&Irh4Ky|`rtj$eoDZI{zC)+A+I4Nx+9r22#B-q`Iqrkg{9 zk}NAnzOt`R#KEFUI^Nns1s=FK4);&R35t_B!MsHpvywo!4hyNdMHE#FlbDihEQ(*rWF$X zra)cKGZU*CUl~|B?Un+4Dhkpsr8HgsU>tvfuh)-&LCCSwd z>n_+O-D>904WbCr`XW4&Izxv%-iUi}{%3!$csaM=vDRCY_zUVa7O!_;*}A3B8o1+G z=ouK|E#n2h7+atC6aR;iw22r)d_{E0u*$Gv98mrZyjBJUl!CxP-3{d4!Y``VAFk^x z==9LKM|jfp0Y9%&aYOh#w$d7Chim2QfyM~FQ{hXAR^JG zCr(3U?%DsW_F8ytM3|ZGrkvBAxBHW<`h$!lguP^z*`9YG8T#6uXP>olVQ!Wxs3&KW z%t`;a{BU|ihuNk_IHiwt1Fa1uR&NF^%P#SdB(|HWpK#Sn;pG2nj^g^Ei`!2d&( zL`wXP-44ayJQy4*@t>sOP^9EvePM73$^Y$p8y^CJ-Hy=TL15wt_}^a#hd?ACe>uFx zkpJuOUw`m_pa1aSVt>bgLlAk delta 22020 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 Mtcpy|%||c(4_ifdfB*mh delta 64410 zcma&O1z1#J*ETwKVuC@6f(QsGrJ{r)F?4q*ARQu|8xsKuhm!7Y>Bgj+Vd(CZ7?3_| zsjkw_qx}5hUjsoAn3H;rBg>Gj~~TgFyj;pjIU|m(;mpD zJ*a&iu~SeW4E$a)c!oJ3`&dYb)Jgevj*eH(&z~2(%Y(ldmfgj#mKm*w4sdG5omb~1 zC$ZHPPtRE&5SojPPQG(zk>>FAgLPF^A+Hw=GSbNmn!7Oi5TIZ~BMc zvxt<$!!Q2p?a#h_`?gFZHvU=SGVYN1d|l{k&UJWs@;}el@avaQV<=bspCuOcf`$qm zcqyFpF9{lk`kc5J}A;uWJ%c*~Nxh59Kl<@UMed3mD zoUr%U^mNEuGT5{GXa9Zlb>D2OHMKf&41*D+`S%F{wE|N{|Jr~V;`YW$npU~vQ3ddt zA(ze#@m{8^)ub48Qf&+Ph*8#c>hSgc%vSm4%6PTf)FRkEvlyyNY>YR29 z8uNX{91hh+moHxqysh};b%J=Ry@$^7@^U~}Y1p^GgWVRyWiF;Mb(#EGJbMZ0wch>; z8}+;QbVw~H!lj}f#aS6NGgKl5++p}+*&8;qoswkCN}SnZ7`H=C!Keq{f48%>ZqgQy z?J2d(ivRhNqezMbZ=oUm%_g<}A-^x%^~LTIBX`eZr1G^@7leZkjuq7hXBraLczd#K zbI(xSzm8=Okw6{#C<%GIn;YgW^I^>WE9j;?OaJoa%FgymvsxZv_&HrEt&g{lDARk% zc<0YsCXJtjsb!2HJsn;4iSySOw>z$~Xk}|qbehUNdGe;OzaO6@O=C0PqX=6n9DEG( z@-*t4Rrf`FC|keW)wSN;b_kKU$%SNll&nrAKdCt4%}Rn9a=YFX-p(ekZ$eo6*}WIY ztlVjmRh}3tpT>U}Jl~GM4Zt(L5`V$Qyp&SN25;y%f zNfuIAd3i3pI*4I!oYU|8BP-aEYIaIBL&=-!p%MkR>7A0>7`Ml0KneJ$6u(MHL?lA4 zV`q4y!_@fu+PdCgFvV|b_2l+wdGX6JiDIInE!8wg`rBh?$Vle1ww&-OM1XY+oSk8hL-YW>*X+nIuTAg^Z}4`%AN!A45Xxhd+7$jE?z_-7r|H3jKDEv8iv)Cj5e|Ql zM!WMN?bYM5`JdJ^ydST>3bUI4VuU`Pd-z=;m(_Mq>@cZZ%h#l;q&JW6ypt_$JIYM z^UB6Xu~5qwi<@YU%Bramean0hGj=$)^$6sRKE~y<<^Tzpj0UUKKWTCI+Ue_9&zXjJuKLPpFoS^dn^@Tgju zea`0Uw5~N4<7WE%6B@yGpFe-@$$tKpZaT)6#hc}*K;%WIhf27PiDCD#g@v1JY;13t z-^ljlAu_l;>uI@P)zuUlB$#WZ`>0#0o~!SD`;r%v0uz$d_Tr#7>oKD%J9D$CnhBrd ze~NVNxV%w_(Hr4Ojrz3l1Wc?}_AI4hd(}kobb5;CYcAqy_9TwqB43TmqRE-)>KvUK zn3jff9ak*2&Zb^$QLo%9xF)1-l;QPvqk42~@nZbO$|a-L?F7ek7wN#W$(hkQiX9br z-IS*1Y<36%7s#_V*OT=8kVvZIhU3ZG8jo$#xH_5H$`Sw|lXyCVc5&?7M?-R4W~F$! z{$9mrwu!!e&z!79JXcPfaVl{ns(s+In6(jY8|dF6b_^LPlsoet`5UAh$Zpe7ZpGgi zLM+wvskD-yas6`_+=sqWxh>0i`R{dSs5ALeKXYdBx;txw|El z7y<6lRvXmLVccc_DHaNZAu&I)T>b6D_s_*~h-R|PW30z>!XMf=PCHu_&(Rw=ZVcm6a= ztChd<4oVMYu&`LP=8p=Qy|#P9WSbl8N=fWFsGV!S<6)zxB9DESN^cbba$Sm7n5RUwCYOYR9Cl4Pd4HbsK(dv zDChTG?XfPN*n$qrmjHhEUgd#%9z9DDP3%=-+-`fG(OsrNMdS1opuE%1^#>UR1p}kW zPT;_mu9=!)UB~f_dwMx49f*vD*+98)5*u~fNZ^V>xxOM*A0oQvG&$-Nd4q$nv^iBs zM&+U@7)73Jw#7Zw5E#dU*c&HRMEnUvGhkyEjSa_yexh$>FHUjAns!rfIqSxC-x*lA zjq}`RPTxWsuwfS1@KmO{JTA|ym-{XA0gT>0(RrqvBo!g2p%K2kHKx3D;nyeDW2!O$ z6E1(cx0qD2-8?rhV+Zmb*BX9$Ck{}oBvMazNdXN-0!rOW#f}BGrCF5I?db^F%&P@7 z35S?KB7`wQZn;>!;8me*}6bWSAXWp`uMtMIaM0N zhqG-WQI%OI$?9wSe`p4#DRjJRu~4jh&P&B_`{eoFqq>N|T``N(roaf+^z!i5A)=M%_HRA*B4Mnk%dsz)2&^h7z>Zx6(Y4^Q440%zBzT+P2-aY0Z6VU zFlA9(+3tytL%@dHsDF%$D^Is}`p#x4nxOZqYD4JpYs2oGoB5M@hQBnxNHs4hsHv&f zo%xeGry2~YT3cJM@=X#E?v7e9B;nrCf{!Ja65hQ#9MY)oFjl&5Ga#;?8RxW^NzT7P zp_s0qC8zVO%)C*!e50;3RX+c7#qRRfZ7O)otMw1Nbxr-O%E~TQ&u$VsbWdPvc!5qs z{WUk!nW2u0cV0i?GgLOiAsNx}dxlGX<{ z1-|8*q^j2YNzg>8hFlfDpXI!VyxYcBiU0v&_y>P=DOc_92>%Sp<*}vKBLV}whQU64 ztiZW+cYub$Tb#^RNXw%GUz3J=HCZmxQTY+rjS}!;Tk}De!BMK1%4LTM@qsI*&58a1U zB`&iCXQ=O|D{I+{>+ao6sq*N$gPg&Qrj+NSn@q|T zrlC>I4K@Lc6!AkmTx701PtYqvp10ZRG($HcecFoCK}Tn*Csndug397-!6$ksmz~qm zGR;qf?AA~m?)3qli_|ffyn9estiHamW0;$kmL^=e7Oq9JQ=o+G=BRnkrb{ikCb9Z5 zfrfF)RXbhqm~XPB=nvDfgSs6q(LTSOmwmKcmi9Q2f$NP?g|~ifv9hvmPE@lgB%1os zJ$Q=XlNBXtoHsMITz4g1_K1h!-#gO9%ghT|IfF>(!X(GT1>MRmg?V+G*4Pd4e8b%~ zg~@sTvvl^JHly%+C?j8V-%w%&!<)T646+Thz zy+6gq9jQ0%&f0DK9a9#~72Y(evkN`9nL*BFbPcOkK9V)ZI(X-;J4s6{nH7F1M??Xf z6l}eHax2Bf-8Qt+w1CHM-p@HbZk13KvqOp$tSaZ5460RBydRs;S4wGn?WptjT83KX z(--xD#gmg9R#b{<%B_$OSM7b9n9j>+=;x{)Idw?}z4by;&ZcRfJ7Z5aai3s-2kCOC zs(N3=V7gih2VK+NpSnDgW9~bXvv3%cgi)83U-o7LMUpp!KgqbC*&aBIDMmU<8~&Ht z6!#5aBW^!JA3l_jCUN~e(M-i(C_OMR&@J;hXX-4xw0bx0f5=<9!cfIwD3t&gI}+3x z;_6>sS%uO}U~|7^SMYr$7A&b&saAcFjRnKPs+V9vW!dX6k})aLTQQnJnz+0PEd)wd>Jj_iCtkTKNyyMbVWnHP!O~Bm9j#yvtB`2s8Est;(u~ch={9$L8lV9!sy@ zD!gJF1z>T8rezlmXaMx&Q(7B-Us?SCdirv6^_*!mm z9Zq>tOFkX>uy!ru;XbV|6^k1)ah%1kTN7vUPp{WA=vMeuO8Z@6Q%Pmx-%%qwWRj}$ zOp=#(u6RuM|4>jk4SZVD7Vc@5ASqZy^nDO%3k}oaX1+c9a6m^i!dXHq6@2p-Q=vPzoq?_%W zVVMvPSau3c)XmG^Nl9$in**5X|K%91_$#&NvK2?_D;mj1mg&L&!FVC-dFWlcuQQ!# z5!(aE;4X4Y(eJLwCM62n_*BJW3#Vi2G;&F{{iIYbyc_w3-2b;blj*{f#xr4wGh9eo;(cZPO}6qi4*9nkI~G|>Ux|F;%Ro|% z+JCb!YW0>llzt$gOi5GDG8*!$SU20!n&YJoDVI`52_qZ-p763n1*ln#VI11^m$F*& zy8TJp(q%t1=|XwxTyv{2f+}c)SW}axwle9Mv0+u-^ezhD9`^x@Q_D9`(R_%k8IEpB z8gIy~jE!b#RK(tJhiblHyPQRl0=zargzqeu*}#4I`GML<*n`?SUW+Wt+T=44)SXiX z+JrHW_SMx~=^kPq)cd|y(J*@sVo!!jf!e|Xh{>F~NXV|$IC=RVLQ`j)Z~Q0wKu?KX z#CJBTPP9N-f6aqq_9uWFP+w*=ZErKdzs#A^Q3cy z1*eVb`c5q;)RVVa6zS-W;+p-tcg53Oupz16I(LCq=XE(o^`je_s!;q!zcDOU*I?b~ z$C_g80i+k=xM-66!@BO`Qcayrk8$7S!IWn)Z#KsZYdv%;rq90TG>L}|_NH75H}5YC zj9F=jDipOcaIYmaEAvb5$+}A|%0WTW^VJ|j%l?5SM=l#5br>aefpPm2T%P651nK04 z(~pUZI{)JEha@CnFZ>FROLz^SM~dv zFY1m?_jRv?ZmzK5;Q%TB)reDD{49Gi}79#nqs zIgWC%Iz=YqC&*M`>R@&xAX!ZIL+Wphrh5taig*e9Gn2hu-7?ei=7q88@swgO{nT;6 z#z6kc!aC(!Ni;WoK=Daq4X;d=XOJGuop;Jvx7QozF&iJ5i49pvOjZmF^3c`F$LTkO zq_e+x3yOF8MAM1VJ#4`JQ*yZfR{jPc|5N#^u&ld&mNPl+;1TCMoBB1N-OWWmL|_vAHLmqO*)tn2voRdcV2hN&#`{Uysa8x!uX9W)@l_fu%P znYx`QE>$5TAZAooM+Zl9f|@weo-(k$yxb;#192C_Dcx320S_}%DV2pnQksvK4!^|f z$e#)3Tj(|Sa2vtJCw+Jn_d;B0>Nm-jd2G&z?@um_yZ4pwner;R1D+orrMgrX)Z+16 zKPFAPiYog0?iC~{ zPxSg%-UiTV`Zo3LprJ5h(~~Ngv<3n5#FGG3MSE3@$NKlw@f1Rfg!mNOC>qj^Yx9Xv zKIfZMQ=>B1fQ!|qo`R|5Q)Z-&rYNPqDWr5_Q!cJxsOqa}@Kt#4jlA||%+ zce4k1gEV!{Q}5fcx_Pu*Z#=S)P}CJGmS2a9Hw&}-j~%%{uXg((cJinv*?M!!Qj+us zX4an*nmLNscC3i*MatfSIe#vfXu&$7 zR9)Y>HD{hsA0W2EF|bii$Ji(m#_@e%^fP`o4}XYcCUL{DQ4LRzW$=k=_)}c)-z)XR zTrPx`c4JRH-zWuSJwS__cncM2z)$1#OkU%T_}4S{(vz^Gg89>n88ZW=X(OYf`Oo_Y znbb2s)dey8C)oOTjZQ9Uq3&m;!Ok@Oa1R7@TmE~PYj*pT^rZ?GX>DHg=NGJIFbk56 zc9Slt{`;4%!fs{4Otbhxi2W|IJsrhA{nu$4uKrtkSD1`BFgW0fLWX*-tVP9g=8S?_ zd{*T)pCB=benu_e!i5WZ++pLNn{TvF7Ka4~D|>2-h!{9~|C-%1HN*w(mX_``bYANo z*i)rqQ(w$2b+GSqqU0zQPAadykw!2|ds6Yf&AI;}GICSBRn|jymyn9&qa`9yyKSK# zx<_qs88if_nVDcK(0PehnUfUnv{08IS{g-x@hjb@?OlOh z;vo+C@h1?+8t2~5=N4ysajkzkfK^J3&LMg2I z^LebxFmyQ}>m*)vPFTO{w5qLdBC2DuXMnswLGG33zJWrto;i&=N`z(&mW8b8-^ZH$ z)z4-d{9h;%_M^fxwg<(6WX74u@_{8ovy3jQxjWC{V&Li1GR4a;Y@Q9$YF4@m&hCn7 z+*@uUKH%szcl^4X>lfKDu(C#eS^Z}QRmvM*ka`bp1FaD&ju~XhzC?LKyy-j zpgG^O)qIossv_v0yjvrpjjDG9oqWs{%QtQ4c1oKPGXPEGL7}8WwQi)o;{khYGID;r zc`*ZYo%z0shk{W#c6%=pKa_U&?1fxK9VuJ~5NXaPw9&(gQK#*AO{S(-TYeg$?Ga(1 z-)L~~p&i}8DKcjI%!}RI-x5pCxv=9Ak)}D>eHn#wNp@jhI^h;Zr4wXxV&Pf=B#07Q z7BQ1OL?er=Ua1zE`T4U)849n0D8(O?-V&}_3Hg~1a)0=CcY0pfC8zD%@ztG9d~u^X z(%9UMMXsJ0sAh)aT+6kP zFGn~g5^wHaVZs{suRTT=EG&tT*BIx|OrDUJqoU!qi)o7BMVh&#Dft1{rQWFswZd`;c#lm%y#I zBK*=TUnV1|S>QV5JDUi7E$dD&p4@bPufc~9@n0^NOF&e!p_9;-$$7Bk0mo#5Q*(0! zul8rVoj73$iBE|Vp|n`zbnOc$3+>tvzB()eyX zfd&I~&GyXCb&NzseM7abt+hGj_6cwK+w`9UZQF4nQSVpv{*@4nHwImT_fGJwz)Bn$qM-J09>G7;IALUVnlxo>nzN$}}Fy4JxL=1ORIy?~-ev+i$ zqmkEJU`l|xXR51plu4;io7a(HA^y?K`}4SX-@Wzsp#C|bGL1LIJx<-3+q@5TR$Y3T z<+(+_*%&izTfh6TMuWtZm#)vQW%PQMT}V-FpEO+l`SayhyryrlqXPq zP^8)E|64QqKTAd*wisEOAMv9j2WpkJad3~Ax5v()56d7O-bTSz88f|e;pSkk-s0YVk!fyWKDCCh8Aya6&Zk!n{b5@b8%BTgk z+|SC_@z7o}>n#xkV%C#u5>;Y1lhmH{T`^86|MU^#1hHdvgywrWY{F&naUwp`utXmF zp=>ROVHWS5?ZGPUn>WXAta>AY$$F}p|RTNtDP*-9X&>v zYeiHupsr|cu8LlgjF(a)TDxxk*^wp= zZ|x0)nbEk4nlG5ET`8bbRF@3pM%scT)rl|bn;5m&K-?PK;p7%HVJ;~tF^0T1O>DI= z8K+6Z7d6e0F51m3Cqn|x1FeI{`p-wmdlIhA;73qP7;Uuu6e(JC<}82q?cm8wdHLSL zg#bFBx*yVyXRGG*}6 zZfwC>emW3o!P04_#q8uu4RrM?&bZ8_VP3h2^REG{f3d2gfoIgTUjrSaZA6-yAu0D_-@6%X+(t3-`hgPX6_N zY3&qaA`{GJ$;Ui7VzaBU=}UH&vPb&4IHFj(mN7o7E(#?~AQM$G+K);liYpXogq&V) zE_zI?${7%SWRssv5Ta z97Ex}^y{)(g>!L_ooPno4s~-|v8`!uLIDfU%&hM<7Ovt3jRK~g%R|)0zk7Ub@nOqCB zWbZ_KwN5Xky&jC+0RFGLnsJl*!+$68O8)8 zBjHj;DEMz+iS^P~-a7FZ)wkTJ^3+qw9q(v?U*@-&9(l1rwr2H3rlz$8>1MWWv(~I| z7P6Va*R?EtIu_pliLo+O#S$Sd^!Bq(mXbS29$TXhSY2F{gc5AOuhFIX_9%%0c(IUH zacV@b++vw&+GXvl^j^PWqYxz2*nLJ0jTzi(-HL@FG||Z}UW}_@{)|e$HzMt&a2<96 z(xruA>Tq`kQ0UQC^Vy5lFAQDOJeJ(7)bqEcsUA9N>^YbhK^q9x51nuSUl+1w@M*c{ z&tt|8m z2yZs@OY^C z^DUYWxDe-)*u&;IXbiez153dEfFbg^`i*n1q(G0?<+u*L5VM=ok?E?r`i#+fmgVcM z6zru-cTQlgZD}IjWr+lBd*!=#?{PV<8s?jJw@u+Nm~P+3kD#BgO?PI5!SowCvi99o zT@M6ew@24kle9@MTFEP>D#*gf9yGc!euDpIg_hRVIzOJjL5KDF9)#l6QDqnkn(s{4 z%7S9D#+2KnBUu*4sP<=tX!mD@5Opr2wx=*d6aYJBQE??J*`#&<dBT(%V*+Pi&MS|&@bgWe|ZW`BlnADeu)Imvv1CM@Qr4CYMd?29^G@+6F0%K&J-S(05=@4CaMWL_c45 zTr6J)o!tzsiaCT1wz*Ntr^wNl@2vGMuda5yc5x@RI%$Jk#tfrb2~T3=c{?MLMvp91)Z?#wW!PhDYP!h%~$vnd8{#4+7)y# z%wrp-IT9tdg)DO{!7goEiTmH52molDo zf zQ><`(-%;Ef2lZc0gT8WH{UOYKKlMU$qv`K~YLo>Mh35T1VPRpL#Mva6Ukmqk$6zj? z170r=m)b3McID`joj*^DU1n2KK+fiy#3Zs%)c5w*4?*8UxWt)J4$06+3q;aotw9q_ zQOh?b=Q0j}Y1S7nVIt?)ll}h0ThJh(*5ZQ) zzOsDICWNVJyd7kZ)0G0Pn z8vsYXVe54mI_3Q31Y`kyP8cS9Wo5^a{6r5wuU-fh_>U=eKXkC~I~#*7bR+wVsN|MLE+I)n}?dy+Bh!@L)!S_EBh_*=X&9-V$2{(hvt$WoAyr7eAT^q9}=@_2nu zk!73@4L@rdo(YhN%cN5RCal>@INdLiktNjnGt@)e{rq@dICzv4MSw#BO2XCAso8@E4~|Vv8lboSH_GfbX9+iL41XYBAH*ULy>6Iu1fy}j z91?(0T^jE z$VL47gTY;d1o2=_#Gr|iMXOB3($W%u0bTgf(^sNlY?)j$vi;grLs{OzW^{trZgJpn zBt(H03ILdFUsy$B2#3h?=g&R8y#wj+nVRY_C8-3p+Ud-ujdRxeRIkC`9QMZwF<);o zN}#x}qoYHIg)T?C+krz0x%!fC)2tA<;Mzqhs*LRHJ3G5ui?y!}y1%}_rzx`?l!izB zH!Q8!l%Zaj2h?t7b2h8Qb*F?b<<)rIOm}V)Ik&0dOPeO&==R;?j}BmJ@My4Lo>4$* z8?O&GndwOJ{PZc{=Z8ZRS-$@@OTgCsI+|naoc%$JAM<$xjm>MixPwj8?Mdiv;^JXg zIR#w9xVIn!y8Wj!@7U2!HMnA5PnJ2X;DR-iw#OGMZFfY&u$)u3%_YFTTc{sx72IHU8r%uV6nx^k#2Mm|&U%=<>{)h$JDH!|i zNRiLComYeC4M7_9(KzLGT=k2JifSDnH-bw{0s@3Sv@=hw%C#7*NYyMY_=Z1#@tfR# z^f4iL(zZ`mIaUGMZh=q+nlUXcE&2VI(GWYISkGRcveuhE61lTUlz;g$d;gOL|2|0% z(=?&HPPU$IiSXRyJl4r|NFcMpOPivLkCR6CjU2N-2|acM#UaT4oF<*O(Rh~|Nn`>N zC5MjBXPz;lowBCB%%;_0QMr={rz7HzKiXPf%mSpmszC)_e-!l!0dxHvl|1K-SCSDt ziZCsTi;s@JMx1UJkqqPJD&pxo^*Ct-%ZB`+yz|Tr;?xKQ*D=Ta@GAZcXagq@R#%$& zXXmoCQ0U*t3oZ)+qi?xzq~Fpv$9Ibjf55*I(k}W9M%u7LiTmv-<`s)LW?NZ-3^2J z{!(S!*yw1Sd4EZYe3BGY9QTTNx;&#f)F~cypA=!y}d&-eyWEfikktEI3Z!|ELGFMlyUIWp%WOyjcR{y?x+3v z^Ae4M+0G1qym25L&w!usS=#&?o15gC)L%cst*?UvUsK=Tq5EOt!K_BVKV7Y*^?-qG zQPGd+l>G&Iu`8O?bMN52C@AVTh6XbIq2x65V^T^7oPY@rs(tJ;I1h-^oom2+8@YYk zfngHHv}ZjSB4q%2z#MDBEg^RCm+c`_tDtjdMKDAczQ3n#aitC`iLP7o_ZlM}D&Mmh z2%v*0c$1;3*K>X0UgxbRcx3Ce>NE++tzivFRk$cz7%GyDfs5o0*V@eNN4 zgTWJ;|M6O=2fbOLrz|3brk)`~m@@|)k85d>iQsio!okUzS(@POO_etrUA*{bq?hQpw#uLr?~bcX%o1I zTDc<^>QvMFb4V`_Ge}2vtWLM1!HUHtB_&b2tjZE+@ZvzDk18L#an5?g9Du4Vt8zD# z>CYUb^EwD8YX>+UwBP%w5=DcAt#TW;ZJ7F>9j!zVuxZWI^0RAiq~aNn@+zw;w*yS- z1?jEh2Qbe__xI<<(|<$aC;tJ-e=?q7%eBB;vfrnchkz4V@}mz2!6AP*62c$l5(f_* zLha@jE{xjE&) zg0+=0HBzFGT~*~wjn8yPjvO&+iGFO{68-uc{@eNU*58$|COetxg+5gmF@Bf#jjUIQ z8hQKg5|8D`JGvBLkAPso;8hH8j=|LOB^;5cNnbtKjBXnoy+Y4z2-wo1fs53z9y>$X z^3&sDzTKi07U%2h3xS{56!EaWu@Q=zp{e6MpB2%wH-LC(igP%Ef{R;u1fPo<1STGC zLXRp4^6qD-I^smmIygB|Xi$a2yXM9L3pU4twdIaP9y)eLK|sK5-^6VD0lW5Ux{bc^ z3vyTRKePgdl++5%Gb&uR>YAII!N_{Yd7fK2X;lm6(EiN8T_89*e`j)Cy zUh)mWf9pPPeZBNNq~ljt_cvc1?cl5Bi@{lh68oiCx)jsyuPD0D5V9;@+SIUsLFCU+ z-j5F9FaUs)#05~=uK}#miv{dQD*GoC%Bn7*W(5P-3{@S#%C*J{p`0S$tXBzU$Brty z_q~h0_<*In3PCdrlU7*~nkQar??3R>^VHOtjPi+ei}4H7(%zzUuwK_TQk%9l=;eLP!-wh!%5TNvvI~)llMhUE_7%zJM zE%5qlWUyQWDv1t)$ZdGPSR42yEcjCIaKy_?U^hIP?M(=*SR7O& z)Q(fMXs}7`1B}C23WVg_)pOS0yZfl@9qsLtfpW|Yls^RfUSrrV-O6rccz*zP5zP>3 z|Hk{n&`VfT`r1N&8tjiDl$S8Ky0M&M)>|;s8t3lp;(}%~)b3FAl?_+DJ`{TA`J1)H zK{gNg>|glo9BP9H`)e69H|0Ex7znj^^yz?gU|INYZ;#QVp7^z~R6mXx_)g6kI}6US znVD2bYuT;mFk{)^!D)aaJvqAWD4Q2_Kf`$YwgehOy#;3YTm$Kf-~$-jZ_q|Mvsm-_ zMs3)8y{Eg|8;TXq4n5H%JvDJ=KY#z8Ssz3QC~7(*O~1V<-~^@;oR2FIrqwlLZWWZxeP5 z&&V=-%ME`iGf0kA!vl0L!Z|`7o0$NqD87t?M@}Y#uW-BUf>n5&)=WS6o;`j%8`-E2 zML+XOgu;Z7DVftM1L_4AC|CG&RrynwZnuCl&8NkgXp&C;)zroVfrJVPe0{;4Re29ac z_hT6u8L|E|^mu*{snmv3tCFs~+}xcLR)MUVYB1c`@MkH+VbueDuJBUtI1%3&pn4sV zyiWVf8V?IV^W2#Chwxz@cuyM~yn@m#(A4Zk;p)DjP-z4d80(RrCw4X_qhPt|XpExc zkf_1HZvy#|-P`$=_Yi}5W^`YoNkoi(w4(F!rC(KU_W%vDUFb`Jk{XKxy;!lt+6a9% zz!*PhX?;^XSi^#nY~A0dSyZwGPMkQg{O6AhI1vgmpilr|@uN2tepjXszYFCw!ue1> zxXx&~QuOI)B;=B#Sx+7ovN+1|k6EGX29gCn z1&3TezIpTJ6YI1&VB1$#;L8;E%sT+(>97L>1C9vnpF)n#uQ3tf165kPyIU6CH2i!H zJ26oI&eSqQDL^fII`eck+Z(B|7f=J`8MRxlPRU~Tjz$7!&o;6GMat!!ySoWgA+`Qt zXrv}ehViuhyb0i@TBuih0ZmjuiNM7bfk9JFdz}$rwS+?oJ8(F;rvNbvqpNLhmInV( z1Q_}&LNZp`8@uP1qFKXV0QP)9>esYO8gga3DV|D7XVb zpwPJLhgcowdA&-1BvL+6qIGGs8igOX^KkeEo-GFQdo8G@U>h_;$&T+c-)hu&ut{3r zoPj@i!fSfr3R}5q8DO1=M2V1Cunf9AaG2^d>Z@c21r~xKY@r^W9jo=Pt(QO2Z;>@R^{2S;k~Xx7?=7`HL6*W7Kj} z0VaU%0`7`@@M)fQ`t`Xxj;)Qyon<|hqYma6H^zgRK((UPqphyvyFsy5oF4 z0j0~JR=`~~0j%-hz@%_RO&I=li0n2+&@w~us~#3rUbA`jwKiHb^bH~%gyC?#yn;di zR5Mw={JSbp?Qw^ZT-AeLr>YlbLP`iQ>3IlcxE8Ww4AEAcP+Kc z=gV8|)P(J*y=@U7%#P5C@YgPV_~*9jS3S=vD>wsL;+{aJ+i?o5si z_JTUyArLKXHkM!P9};+GT^9x-LLn^Tdp=h8g$?+tp`rEet}{vp3h~I zu96!hLdh?8Xw`f3@#UM}p{);S-8k&Iz>>5`CZg|KA-YKEW1rRI#k<*P%02t`EW#%P zTi8)(p^Zvz8|Cp;9CUO~!6xz`4xqc_4w zmG|%8PX}wewu0-UgOfvV&l-1SKSxgqw~o_Z2F0c_&yi>h?C)y`nV-1LLZ2$QBEe~~ zJP8h5Bk_)V3sqLjW3`?iKZ>5Ww*`@YVPWB@!dXoAy+lb&QBqOSeP9KRQ|y@g#g%)z zTYKH5i&M8!3tx{8N-mN-xkh*HTFi%mL!u(n*Ck~`kH0y{tswLG=No<(vLmD)%!{<3kXL^~>dyD6HyIEjZu?I*M;XAf zt>=5lY9ia^66EeejvcCeumUqn8w;h2f7aH1fBW_t!11g7l1^OI*&(oMoDkx5si>qx z$HGFo+>!Wqxtj*EI@0m7y3e=4w-r+ZD@FFv25i`Ad}+vo}N(fjz0k9hBG*A^{LRfmEj-m z5>b2+Kp&nPAO`MXVH9xC9o1YOjPN46k05xHK+5Rq>CrJU@rsI~S*+M%&B@7eEKE{! zU7Fb%Xz;4IJKOvF19h4O@3P4$mzkJmP~3@xLT{3cT-FbLd%u%cMlZ2xeX6YF=jP`A z`}gl1%aNaycheM@phEbasabm8c1|UeD8uk*5@`te0r23Pl*F*UIA}2Z{rzbgzHm68 zjtdG3vT!<1`|j2-Deb$OAH`Oa0fWe%<^!m(QUuRG33vkaK^*{EAV?I9s0McQEOq)^ zSB_w9T^+lPVL}fmUV*Snx7?Z#6kI7EqKc`ER0L08TsY)ECbw4$eGiZqkV+rag>ab! z#oqE{*ZFz;h|EoiFVQQ`aF(5YX2bRGu)D@=C-A!Ro?ARTYO*N`Vxa83nv*9O?CtJ& z#@-Tp`t%*_vHz&XcwL}8T0&vOKzD+x;ljhy4<9}}^+_^8jxpDuxyh26pPxUX8WyT8 zOG-~D`1t7)qJWlrkM_1#LRIj0In8<$3_`&F#q8~iCc2Ql9c3_lZ9cDhfoTdM-$_G@ z(B!wSZ+_ZCbTRmElYv$Fb>`dOES^6IQgS~FA82rJ#19g7B@^;v?n!Bx> zfI}*9^`Wh41yNR9T3Q*EiprLLQ-lu6zC}RPLQ~Bn=f5(Guil+|5^zhNzLoJ^O*;^P zfT}8iaZvda&j=lt^`yIblL;814ERlgHIO?80JK10Yqx4^YZK4Rr`*x2IVZV%9h7Dd zI6{sp&=R2M=RnWuz~=*jn3~|2XP!=q(7427>a3c1lje}# zLTRk1s0`4q08|_UXi~m?`*r}ds!SrPzkh$S0RR>VDU}{NpCO=O$I7LU28tzsKrz{K zshFOJe`D<85g8`s%=C27L_NH`Q6f#jA8$Voe6i=@b#y^?^~spkLSN z6f5)7#e3eW23A&9Pvfsk&;lcHSZFuxiaB#P}8BRfslIXKAF)zuS(y~!Oq zg)ZBCw}RdS?homck8RQyBA$HggLVxQG+*lA%O3*r@(@mW_Z#nS9r^n8YXD%!Eud&^ z+u!cn%qY;av$u7`@N!+ac#({mnHkQM3Ks*8`|QlnbwxjZODc5NJa&ah&~BFf9gp=V z6>xyI+ox9*pFW-b1!l8^llcW^y@~Ph@l(B=YG~)~h)iduW|;2aXl-0T0O_x$CaIj< zTtprOgghUoKSQra78WwG*82MTOfWj+33C=r@jFm$kqK-7Q_OE#(Q{(*jDlz2!-!YVRX~N&M-`S5?a>vA3wU9OKX@vOiL@G@wi>WYkxbbMV&E($dE7 zSg*`?lYX><0f7Hv-{p_fiutCnizJ=9t_=G6(P;Sc6BRYJ(EH@HPqB8y8ASk)Dj*D^ z&d!gtw6xF}1n1q2Nqc+yFFa*QzX8ca#l>%2yN1!$9z-a&K)5+s1u^=`b>Z_3k%=yD zz$uJW`2FVrR$s!x+Dq*$pjw)0OE~4@ytl&^o*vIo*o?rz`}vr4zwJc zzrRsy>=DSR9X*p%Q^_GB=Z3(1KY#r?fBCX|LgF;g$#5fk^be0|R% ze!PT4TJTA6$Qi)V6X2_V)V3NL8X(D>JWKtkCMR1=Mdb>RWIgzrjZah$gWTLOz-W8fa$ zsZ*zbE!XrFTR8whV??wo9)VI#f9H;$Y@Be8c|T9c57fQ~RBbeme@+9<69ZovBuVLU zE|$*A+xtZ=j^4DtgiJ^_yRgszgu}AGBVH;hH6kRs@@(fO$Hx<_Djl8V9xgl6Om zFxr7Jr*KEhfRkiQQ+<2(I4wk3kCSX*0j+g&#@?>K=p zLzH|4=?^WvAf!*6KCK5|TLhx3JLJharuiqwWuMe)(>ysI!OqUkiEO9v|2#?ia}@rg zQfN*EA~-!g{gFU1v?+20q@N~!nfI2-$|+}0ooX-E?x&~(@l0S%?1!IAd8FQyXD{r+ zg9~qF?st@@e6r*-fc$^ywn`Ofi(Lg~6tJKP&@94vY&=1GLi+$WB|`%Yt`-y&2<)Ed z0?FDCKZ=E~jcVN4BiV$QTcphMaaKT zq2ZUJQOpj7@zzB&V%hItnv;Zy#y(qN45qOtPFW8@*UJY%1kn1oqaT%rA$I|)K?L&QUjqc{=vrbMg0OyC zK%0{Kk-dPZ=yA}-AMkJgIfVJ~f#gsN-4hVP8UVoT|Nfzc5*zdy>IV~H@@G$#J8yh# za@eZaT6n16@a3_D#Ay&YQ0f`wx_QFH#H6v$4SCNy2zeK1RY#U~6ts|W0Rc}P9eF@{ ze-4g=>B!W4IA{w61+72@R+fT-0vg>zq@~GFtL$yZpi@z4I7-bBI9l zX|h-K^iV>fp(K!xYrbCfCE@Z%?>@8g6OTQ z-A5bwT>^cij^NH|sHvfBVgVM~3R$i|@kKQV>$=sz+z_A~)imDQQ*_PC3Xv2SDY9wo zP4`xuU?w^XP0*sv`b2rSDGAUwI!GY)>!m1*RYwb|6`&{dPzq`@XVTNtn};gHU}jmi zKoJaFY2#3Zz2!V?INq*crJH4=4~rFs?%cT(3Xo)puNqQzvNHs5C_#NU%tV$EC^1LH z(|BZ+#f%lQ%~eK_2!G&A^t;6~_}yV{kjSI|QMKd7){k4?I==Iw8o!@Zr!HsiFGQKr zea>?{Fv(g26TJ+E4$QCpV8f5zzP?c4q4YVG`;oe#`&xA#w9r>b5u&9xBMxwhTWC4_ z%C&3Dd-oE)e;lx;MBGf4HMm%BieZ{BwBG|lC-(mR1E7D0Cns!Bs+@G%k(9(Gm#KiB zV{MR~59flVQ-6Hrp7^hsK%orE)B>ZDWLi%%JRr9)v_-R-W_AeW1jq@h69O@|iZ&!15682pWZc`X^nGykJJPb)UFKz>o!@(FF9Nb-> zh(g68=+af*hXyQhJ1okVEI}_*@Pw$JJ$trPN>$JHn3XjMu4Dmqw&`lEWyso)c7+I} z;0=#pn;x#_cirVSdjJ3-8Alvb>oJ>N=VN~UXsEtN@rIBhMn*;!l6Kkeet$hbJLv=c zamj$m4Nvrmr4<&X8(vo~JQncN8i=Ce5)yqkm5(q8H$i;a9P%r!#A+Hqlk6;eG9@@?|-v$aOCNEEb0g+3){RfMDIwIdvA9w@q zBpnXxkV&)ZCqPb@(=9PcTOoUcE;70Y*1;$I{A3am5*iLmRM&7w zX%OrH^xYep2L}~{H*aELeSqm5uX2u}Ku!Z8{Put{_wRFDLgOBIj%$28Zz>_m`jYeC zH#IfABle^|e+@tzKvpYgF!APO!vhVJpB%}uzS=GDBZ-95)&bxef#zeLq1zg89nr}Yrwhz)M)AGDD*#S zsrrsyH<()lt=S~75GYHkd*RbPe(Y8}>5%a!r}+18E!bqk!^3x3=qTON z((c3n8K4#+Hw$8y2!WZxJA ztVeCI=u_27^M2P62;Hp#<6|lKH(un(5TGG5y54|n*5Uflu)=+oUfUfX9&WbM=e$B+ z-Qu{`36y;~Nivv0Ov-aK0sx+i#yCI%2T!aafS!Uja0;fi@oI#Ei;D}~D0MIs048Z` zz1;EK14BaU!Buw4!H>6;1xjjw``!^~-JpDp0Sn+0RNDahA;4qM(pxD&MV3is6lVHSiBrmmrny1IGhv!Wojy|J)hU07Iv zj1vu+S6fd{=}jOuB^KS2Cm!%e+KWvV*RNj>V^Eg;6*C1|04*iu7jUWMBfz*Mal6nG zAs_+bbWaq-x5a@BVt})aHS+D8m{Xi`SsP^|v8%Ae4=j)yvyE z9s+SGIRY+4AOwxT{6OYt>gozW6ICdlzc(;YR?~uKshOONfdvVIzIOKtWH=gZpBu=Q zFT|^42$>a|wb<{Jm4ScW0ui6bY#IZIaV=;|)8sK+Cgan9Y0)SjGR-x5`izH}UtoWw zRsI|J2uMlW0r53AH~(pBYU-cC+$bb0z{Vm392r38 zELeNtF+{5v2!%ov9XsBT!LwjePQ#9Ym?Dho9)K12Jt*j(kGRHDrIyr>#QghrPth9< zbQg%Istb$w@gd+{xV5l=EI3eXEG&qklgv}NmBBPTRz^-$E0k{UvFqd6*w~C$*ayQh zVgr#37)B6kB}cQm6XujdP_x1=5Ni8@VPT5>aoChE1HY3;15pGm3lkSN1rSDWf}GdG zuRqcbx%R}YeXOQRn`0%0`)^jRh3u37hedM>z?q&vI7}g`4BS?^62=Ff|vLZ zr2Xdhc811gcf5GB$N#39AI*>?fYbG^ybFEp9#6w=G>k_~9RAS%4WT4G=*J?$;D+b^ z=$jP0SnZXsuC5XMjzyDA8IEeF;Qi&Z+x(SUi!b!!9RxRviInw*vvJEVLOFIwgznJS z5;_ro058n$$`G!l>1cu zNL(HD*N1<*f;Wax27a6}0oZ(`8W9BN9K*IHIAHS&bQJiX%rcO+Es&3iOAg#BEBQh1 z1d2?esi_GG#SfIvzyt!JkjD^_bpVA##xgQ6Z2v5We72nrRoe$Bn!vBe@YlZ!3KGGa zBT9=%V^aFQK+kgY>gsAP6e)JxYEBEehUEtj*hs(L4dd5+eXJl z?$;0(;h2jgA3s0n8aNnet35Nf8{SnLR3{mTx=%ruwQJZLhs*_?eF4WM!bTURg-5|c zuPQHDTHxs?$iYT1Fn&-^0d)(dz-9**-wj;cuD!dEQNfeHxwRDsW^Z)$%fuvmBo@q# z;C?rEi=qe3V|8uK+{UJ{wY4?zHyLU60e8ELURqhy|O#c{`B_t_RswI`Px1*W*pYHBFIwU)td&~FkWKr3Z8al za}}=-$+BrQpmTyNCC=sS7+vIXh+3uv*eaKTNSQ+?PJq&@oF^=lYiwLxO%S?3H|hm{LL{d#T0Bpy z`)3-hvHh|%dXP`F6(NRyKq0%VqN6 zAp0Lel!EL(GzvOGZef01wy;-9-&u*{57l?m(=_~hZYIhR5sAr9f}uX9jl{wt z25-SHDEKLXwFoD7wJvGz>Mf1#B96Ly}K}X=0KW$95<3NncZl6$iKHqvK%Rzj|Z$IXK@3 z3Zs9a0`J0SDj!IdFPum*0L-B)5uof688V=kG@m7G#CK4*2Q$QT8G=n z@v*SKJ6VpNOifmZe}Pq zq3Ptyp0?&!=ocO-GUvE~yPhDMW(N!&Ep7}BHn0t#`5@q~y4F@CJ++G#oy?bj#h^u= z4!o{wlB(O=-7>=?f~x$t+-TNV*;t1w8NDP8!vNe!fk!=p-2fjgZ(Hk&&{0vjgB%+H z;^gJGSq_XEyYnv(^^{XJsdlyb{P~ zOeQ*acWP4lO~=)OehPWvceJNyg5cMMnH$NgGSmrvm*bN6d`zc|seS?RQI&17GB_R_ZUQ;C%aQXp= zU9QMTSceccN zg%=7LNpO^_q@?5rZ{|PXEBOoSaRH>LlCn^pRk2u!AURB#bRKSAnZD{v)B&Uq7}!JV z>t8|!{ga8n{2`aV(C}K+OE*t+?F<1LV7l8W)u3;> zbe1j!_zv)r?UXZGr6MQfyT@~`&A=#eu}MSuauNpgvWIUX>^50BIkdL+N?e@9`rXxS zfQ=T*{j{0JMs|j^AJy7ImQ5?%@&5S?x#u41G8z0Gx_%28FBzxhJ=kX2fAZYv5A@3tzTU$w_3u_P|OlalHFQfF`bdA=|fMGTTg%5 zlozqX-sNdfLFa+(J@J2qcq9Kpb`V8{_EgTTHg@vTgaHxDHnfg<+}5UkZ>ZM=-wUX};J)H~(;}oE!4_bwOSjF+_PIu8A@A#*1 z_73b3)~_5!aV*UpB*fiAK*4ZK0|Iye$9%`v!d-tgC<3 zRiVn`YLDnjeqb(hV3SV9q1J?AMy5(3)@O3(>sp}f|Mhaii(j;p-%PajL|uA^z(-}i zT?l;w$LC{e>RG@+M8>1UZ9J&X(@UDTj>*!|@(d`slk<$0Lx*9uos%5N`(j#I$9gPP zqjQ`}~;<`%-dlV6yhaJIvLiM`V7bso^mI}{WFVppXj3qKhy zo58C#Td$u#9OUFg#}bG-KX`wtLANM80)i{gyQ*C0khWdf4^!D|!_T<8Qw^{?iZpdk z0|Z@8La!fe7*Y)-JY0WjQ=jp2{05sy>NUWW59SuE1?*O=J7alt`tvLeDFUnGE10C| zVWqHRH=nnkK#;nd)c$`sbb(B3n-5wA7NKKe@&`B4rS4KOiaTQ9xuG4kS@u8*{TSi+ z(oaETvVUgowG08;Tk+hJ*rBb$6k_;S$gr1;;hd|i;AIK48#*{F%%fOZ;nVRDbJ31J z`NgH7_@E!`)(*d;^B*^9c>#YqpD6YT%NozOTb8zLAa zop0XWbXN9gP7IYTARv91J@b+RS=f}`{ZLhFX>R_dvZrF_puh_A;)P$^I2hL0p?WD- zr;X^2Sa3PW{&=@r&@si38svZO5qCP_B!b==(51Ky834p6VK0j%AASsh&|l3 z-tRa6JNv;tyAHA3r}q}J)5p}AXxnl+m;`A!@@ef?OADK1>5CT_FWpFTt}plX3M9+Q z56v5()@8ojw_c8;!2;2M&6@ifnvTc6A`@`B`*|U2EP@jcQbvLc6NoFm?d`~$zH7nQ zdu*9neHb9TNRs-2tQc!C&!6^J)fGk)6t@q{W)_&YZc#zeS3-jRJ_k09ymEE>AqDJ! zOw}XY8dMi+qNeS#BCXi-%1;$+pJ%M+;hdU#mjqxAt=NI=iL}*W`vM zsdz?yvF-$^o_yZltFl;nm~ibNB~J5dWdE-2cyrCm$s5-p#krY3y53hJDgtlZlzWN) zMwOWq>gc9{(w|k8MR-!<95y1qwP#N4dc}0jT4#JyI2K#^+l9!H3w(st|xYqh-; zwo0}@f9$|OtfvH=3cUWn&CUTD)my^Xsr~~r*#BL2jVUVDq@>ZnhoEr&^!)rh8Hd&r z90~utO?jaW%L+;@IRF;MOB}v|%Gj5MsB+19Cd|VM#5f#CcP$KbC{eF=h@hpY7MA_+ z5QDZw{7RN;OZHmSk#h@OK5BnfmwvqUsQ^?c<>)sW;SZ$_JU^5iY@e`DzOXb>Tjj?=Rwo+1 zRF;>x%;RrJ{)}hKb}lcHONyk>m<;6@>qoJFbhs-bV`sW+#+j`iSI93Q;4gfA-(vBP z#=~_twh!%xLQSV78R4$?ICF`=-MHa<|Abt7^60H$bs0{h8kI)ueyQC#14SiUush#|M zAH(e53qu}na~&V$-1W6rU+6dvyf|_29xTApilb!EcpBiB5zclITi9q<(+9Rqy&_6^!0q@DpsKCyfl#QDTB2AA0ufrR%IThn z%;s)-2#Wa`n0dsLWMvX;_%Xh^S|3=kB!SKWYVuPyt`vu?pl~-rA+LDFm7a=lI9tkY z{+K6CJ6$Fvrl<8M*NNEApK9-Ntr&@gwMQy8xH~S7eh>Otf88|T0S8BezK&WYX?vn= z)AxwLnv6QJ-)mESYBv!-owoBk72Wx`zYo?~A=F=$YX8aY;PuIc<6VIfzh1W3@3UzG zmEsl5j|cLOeB#>r41EGepX=SOTno6{Q576sLO|5D)uwg2x$+OhkE64pSaI=|5zBQK zCq9%^Z1-}mnPBT*Uu16C5mzgm{mu) z%!CAGf;8k>O>nk`SVQ+ue{1?eVt4ie^?{;mS+nysIhNb4WH_iN25_k--S!Yc85_kAsHZ{PI)L-#&59$mj7znQXpue&2QipAuIu# zj0wi{n%uxUsG7yF64D=G5VY0LdB_UZ@wGa(=30Y>k@hv5Yhi&18bF#ItV1zBLFCM_ zu3VNdA=JP`w=;fz#XegYq@m0$&|;1xhTgWM&;{Doj+E#o^|WICJVPEq5WTnEc(>}% z=9+@@S#DllW3hvz?%6)EUt|$F+jC~As{bkjH~jC99YnAYyeeHUE2JN7^^J>wJ@d{T zG7%shk=nQXMEK|IS5NM2WiI-;Cs8pmW$&&(`|cC>Tdl+yS5>u~l!a`#+!cJfF@`zO zi*H_);Qq4Iw?Rw>vsAAAq1JSg2sf!TvoWt}ZM~oKTA+f<9BX}^&(7>Z;P&kqcW-6J zZ7FWo6B}w?x}eE34c+BpHY;+}+}uy~l#_$KYzdcL0}6V2!-b1Vk86HWT84&VQTx$$ zTRjya;@`fV{{HYtQnjFVaL_-QQH8~>UjKpP@XqW_W2HibRc9iC_Y8JUH0_E8n362_ zPy32*VLO=45xV})Zu=Qqb$L3J^3mbnv3qv*N+7jH!u5aKDd2@VrQ?&`tJ$XVDbUa} zG;E5c=fvwyCIf~bpQ9WFLp`ltiiIBP3Kz*Qe`(V#C{PMKC-+qG1v5~h{}r5R5Y-|E zfB$1-+D_KaZlC$jcFbfWmIUTjNZMPLlOsGL#qB4aN=@=r@qBgQ26$p=i9OEWdYNj4 zCps`NPWfud*W1M9PF0Sk=|=dwIf$bqcsh zpQHMzrc3ujteH~^J79}>2Y4X9)KAX2>PNcYwJL31nXc@q8!mLtA2eI4Se>f83?mv@ z+)FOyc-~Hn0bQvRlz1>KFWxl*MAEo_NJmTCqQ>nQ33}j8wLxqz>WH)crCzn5teh+s zA={tIa+aG*Q_-8bV+AX_d{bw9zyy7U2_YA|aC!(Yf$=H2^(L6$!>w+SM zosO8P0`q*L5JxMD412-X8vN%17ihwKu|6Hy1sKVELH|8-E1sjg530zJB(jx zK5AC5`S;XdXUt5(q>KFZHFr+U#iRJ-jscK#?;S2b#lRuh6ktYAz|o?mnZCY$TpJw| z+P)m$%!krB=3fC&PuWpvk5R#C#W+LpSZ1k1P={s9h`Fd-hlI^)TH%%1`lu7B(1 z?$MR5J*~#Wi4mP^g)6$aGu31kbu>ETE#p+{YRPx3`58)nTH?m)%9wEW|(DSMlJrH`3K@j93YbjFpW0_1kJ?0)pxZ zzS9h;qpi{Wlyf<^%N*X)l7Z8~yx!rE>8s~ah)v||!P+R!A4{^bIT{IfEH=x!7F5o8?(BFpW-2Bqk`bNf`}035-ah{^bNbYouIs>#rAy=2 zl0l=YwR-G6i(+l{gSB*A=ds+>4Zm77;yu!|8@Lv+D7Kjwd>Gj#qg~$P$*=7;^*g)2 zBL3uburpIMuFw}xR#F9`%@&xKjYdkslpF+2yA2oqFAsky14-~|DZd2Pwi^DZ?} z|4Va&u@WL6B5v*;`o;kjcRGh}=kz9e5L?rQ#tH9~y%ZCPq-pIHseIoN+!@;u^GIfb zBsW(F0-z^kF=b`tNaToEGAZ|Wf4W?F&&HeS9EB)ur}HPb?(5-BOqE+^44R+Vnx;Ud zp=!YJQ_-H20Y*mgdR@&ZdH`BA;v?8(*`^SdHD$_Yp0V0Jpf@SW<|qCyTeVSzJN zdhS{zF^n7yjGP8B^Tul^8{s)_nE0czN0-Eg7F&wS9p-~NXSxC;ru=g`N1b`0?&{(9r!$ zjwgQ?M|wnpXL!C3_N^%P>jh%MiI1txPn#)63pPS#$M^2DFU-s4@b*#owgoA zDdWd^Uk)#-sXts`P5X^Uk>QK^0S^&t)4Q_(N=EwTdUh}E=$?PqMqa;W^&!6UPj^x9 zK5t#6?PoLhBRL`us=( z@%}2uuPDoQqk;|t{@rUdH>Ah}vHs*>hvSnHHGOM~OZtH2AM`+gDJ<@?gT+F>Ip}Mc zz;EGyw~6ixF4s z9IW7U`Y59`_6vDOcO>5cox0cEJXzQMhA^wl{+*uyADxcv>LXOw(dT`Wb+F)_&; zf~eWk6|O7Ok^Ykg%W-X>XwF+~Ud5nuP#S#mXmjhCsm0E*0=dAO`O#1bCxI>g1%WWP z*WyxiZ`(`LWHNAaD$k!_#3H9SOG~3Ss=c0R)i)tcGpdzDMMXzls%ZfgqfI|<8V`MK z&AH|CcM_oPy`4@^42CmHfmms$fZT38cwuhK?YqLqd((q{h9NsN(y`~R1scS}jsaSi z9nJT5Z}j$(TL4%GBjiu$`=Wbrz@sm*YExeo|_0T+!7p>#$MBoS(y*P~<+RkmMQ>nCaRp)}qQVy|< zB!js+b@#Jop)he+JyI>xlJbetFjP=j`)I0bXK^Tte~dFr53~iIR+7U}GnE1dshyok z^)Z&>++PEhWL7a#EiG3+Ux)o?v1(~$5f6L(wr%a=WcEFnou8$*TMlo;K3(g(u{u^5 z-Fa#BTX1%zhEPlEhqlH-wQ`3D`0OkRsinr`nLaDYP$rsIobI5od|%_{`k0^OilPBo)X?4qkWWx^P)bumt1b9-tIr<21)2K=ta70)kEZTIh)r)U=M^Gt$%a!`tS z)sZPQQBV0_m1pO9Y~D?SvG~m5$hfYpc)JB6kqD~2d)e75WuX^%PUj55$JQw(p%z#A zGa8>Icv$RNAi14G#%kqX&nKR}t&Y6TxS|N3rn+ODod}H|o;mTKtCl1rhx;TOySv=y zG-(auh%)Hq!+^pXorA)M#pdF7FveRBPNc2_~B&Ju`|%ocWC12d_vtzZ`$7dbFV9xb+^W_!)kGykXVTeXlt5U(*2wO@-B~PPyJrL2Tg{{gQoR=2cq=H zGvSNQMUcsxJ}Y_tc4?{+>PS$QpAWME&mR<})cBCFfXRDVg|*`gdl{upTcp*D8V#Sx z$x3axMDsWPOsfAx(&)!S~t!HVsqA=7{HPVJ8{U{o2|;Bc#LS!3t3a@AWo*jo9v4Jk8l% zfAc};tF9_q)`J5e26-Xc1PMQ4uuraf&D7wOd>iYSRZ!;i(hr<=r5r2L76YrEeP(Ba|aVAfuz88ge- zgO3<{yPd|kEAOiSt`*Dv#!u?d1-%lMHio4xv(GC^%xv?swy$2dXB)n@z<;r#m}hvR z>!O+G4GIW*lMDZmHqo{7@Xz9}kvAV_DwNf@Z6kf#gYvPC`x0gPh)(le9Alb%LETSi zLf)pb_TOhWXT$kLP^tX73i(UCwP|4?ibW#4w7IMR;Z)OQ1JL)p5KvL}#hY_-8p`U9a^Rjoe`L;UV{6mRh<( z=RjM{8wcB)5%=y@KtdQh=kEaBsc2QO2Alr`5F(SdSVejS+d|uhp2c)O;RDCT9-!$` z>I3Uv@;UU$m~maAgO%3J4o^GO1g*wPY(i-%DkXFg(QB7ANjldPP^D%kF?=&#tKp(heqpf3VM4)p)*4s)ix?lf}e#x6B9A0x4{T=ZakCP`OFE}b_?`;_DT+1*w3)#(Pj9VJ_FKf z(#CIXGfuo~s<2aD0_e%IP%@rO2rVfgb%`~U5eIEG;KkMrnQ@3_06u8?D*~uQK77~@ zmmK80s)`t>ho_`Dze##9M77joW~<#%Nar1ZhV#5sa3YVKTuM!?r}L^d&Hqc>`q=gV z6t^%s=H@?r$wwYDu2Bk4-mN2^>im_SLjkzvP%)<^bLu=E_L$pvMYj0rB9ib%**00~ z;x@IOUTax+o3(qN$d`PuuqEilUP32?3VNlQ(NVOh#Qk!Cd6N6T!v;^?9na)a#E>uLQ=_U1}&@`Im46zC7xm z5PU~ZinQM}`RzfcHL+)3$U^<^E`s{LM-x-1R9GDCeI08`u8i$J+)r zQv0I27GOrh=P^6MU&sxeywZewdJA;u$J^}njWZUa_^DN9bs1?;G8%@lw)1q)ZT{|? zFY|1~kQUtl)o5{ILkDAjqX)VBJb2+!WE_?KNVz2mhOn4_%kQrIcrGNoZo_h}mNBa( zq3yo{;NHdRg#Ekz`SZrDC}YuU->C=*?+|y7_q3C}l#}aY=*#0BFyPTCa&WO3Wm7fM zEO6!+%Pf$kSCf^iaTYgMGGisY6Dp$oF?x!k{_&;O2X*0J4pz#_vJX2q`3`Q*vTa7_ z9qGLImgKCE#Cwd;C@Khw=@lGIwws2SvCAdoRK9?I7o)eUMRuw$6qyCieq@(1$TkH9 zybhAm3m>P*s))VF4OD9BYm*=u%$D8m5nLNOpk9^xTg6{iqDOJ{VsAXwCm|=g_4JLH z28Uu$!DdP+mF@|nGC{)6pMCeAe*EF& z*kk|HZXI+kK&u=ySfBm?sL+aGd_6wiV|VW=iiz74BrCM(a_B1 z3{$Y-JEYdDK%EBD2h1pp=t`2;`Ka3*rOG;{3}m2;5$F2zvX`Z$Jfpd~p^uj|i^pZs z{Z=lV9ZT~okxNSKRv&vbN@h)~6xSlJh8=Ep5ms=WDOj71y|L7FX2O-0Ru@>_h?o@Y zh|bD=PvLp-%#zf057k7YcTyUH^5_w^HXp-V8~rW$Bv{!w;hMa#tUT5J;+1Jx?~7a> zIsT1!&0P`>6WL|X{H2^=(Jq?dpWH5Q(rBq4i`T`M#Uh2(QwK_?;a=WOqe^ez{(2^EAWmBqB)qZ+h&-Hw1*Xy!-i7uUi?l>KO~O<|dOZ$>Ic zzHp?i^$v&NamaD_<3kh7CF4g;-5 zm1>0fZ`K@UCDJxJHu9e>${RcvbFJET3dPEInl=$O>$S;*_La3~)`5@D%->i0lZ(qr zbsAi~rv7$e_F%s#)jFyOR%64=`Ikujzje}d&*&utyH2E*3^)u$ltrK)RI0s?;&HXu zlMG>$mnGtQ`BUAFHi4K66ByN7yV{@us$#Qy$KK`~s+H7LCLcaO_F01Ve71r?uk$ls z6yhsKO+Oy{=wPqtr=$3ETJRDbbYJmINoim3-neZ!KfJ#^zw?vnQIJTPZhewU;kFVX0RenD#qt&@-pd{UHX_oY)P=sX%-M%^wdDJS{aquS8eocN~kN3l_W%pi&hR5g0eD z8Qwh9wNasWcTk~00=~OO9~oDu(|2tiHJ@htvA|(>oqlPNn2T`$(4;~g&!5GW`8rMmD6zfuFI1P3mi*&7aUe-oZGUC>{e%o z3c5~C3I3U>k|`>u7D#kUh@is3V2SrDYd6YHp=GX$^DD>wG!=Ufb@oEjngp4;Fp%yQ zKg)BFNOLw9Q{z#*A;v~_zR~U~u6}mJ(mLY8q+@PtJM=Y_q;TiGw~KP6NVtZLJxAh8 z1@t%Z!v8KT*i0JRsd}G(H-q!XLxc5#Ynn=)e@AGq9v!uIbOg3o?LVxK@CYbVsS{Jr zQh!!t&=4e|QfLvunXQ^oQK443+djKIoRo3=QZ!bns&!Y3Ue7~nK{-rPv7xI+d5!<1 z{z4vhlFQlkR!tcV$!&d|q z_6y}AVwdI<8W)dR0`zC3E%%1mm_IH3jn`QT*R(GbzUpwAW!{J9URe4nYA{DeODoP9 z>C|ve=ub3YE7T`Ut}M5uV|yfQIIH-Y_u`C)OlAbV?sFn}!$Onj8eNy2y3$r>ZLCOh zmRTz3#xm2i4v|thU_0NAE99<_MWa&~og-&V(bPMpWYTL?zwkzenq^OWTT5=@45w|^2yt|^sT-7mpnUFCw8-D zGx6R$O`^r74IiAslG9di=F5UpL;jC)qC{)_1`=!q$;5dRqsNLq)i_D1=3vQO_FVkv zP&Qbf)$N_+GjF^lgYxpB5{EdL3PGWoN%$2s*(;e(i`!~dg#qn z5W}&PAX|*yHKs31;0LQn=GoSXi~H3Rn)ES5Bx(Qt4ld@~p>=*L1((wIIwzaBm?4R* z7iO^+6GM6v`Ps>^D1TodVERrQ6Ldh`I-zK?FZDm$wrdy=5vkSj(Pq~hd}D7P!T#}( z5T|ga*%0NV$)%*`oRt_a=RO<8tk8Y+R;Oc_x;in38GN_txUI{eEqQNwGQaq2GZ+y- z&WtpFZkO0>jAv_{#nZ7n7Ns{*i8VE}R#bgjey2ET`7UDXS7s62NzANQt3Yv~F=@_{ zw%zYommgUL{UP{6dHJk+9VoWP;+H*dLcc}vvv?m3E{f^{-89aH&ZE()3bIt3B?E)y zKB_m&Z|wUQX*fC^`!p5aob}bC)aifr-Y#BwOFd!U^%4THzM!9g;(xsa zXUp#&)a-w!xyv%y9SbW)07DbC2kkxC!CC@2H9|i2nh|g zR6(AQW%nx*hnnxc3R*8+yLPfcP}tBNsFWr#wC=Duc|lX{i<|#8pNg8=5G3!{Qc^Y! znx_r_{E}MkUBl-;8mD(y4!rm@6CkJ$|3XS8CMMK#D)!F=md-{jDbnac&- zx>ZC??aTuUg%@QamB*qyKj)o}OG?B{_f`bon$w3Z?GeWet}yc%R~t`HYi+Ye6fEY* z8K}E^dU~!$?WV@K7zHUZort)egz!a}4-+Qn&@=Sc#xBrlO_8hjmTMyJWBc2zOL=WM z{V-&7;$^1{Dd7jp!?H~QREEojrYn&o=f>6Ri?FFRxXv$K@+N)ys*R}W)!%}>^!Kb0 z1No+jVa&8D0^=D|Q(ex^2x7fDuCS%bDIRnTv;09*!A~M+_FtUJVa`Cm!Lz%~%ELz< zGNh<8Y5DW4BncVi@*mn|sdyJ45O{1{$E(g?fP7o2k&Im4v(+2co3!y>O!lM}Oi2r- z=(%oMsqyzY^aC$%>uYN}uemY(1|WEE1&`(mBF&d^e10z4lxE&Ib%5N5Bpq6^Kh_A} zNw(E(@BLgO92y&Lt3gjhI@3Hz8e}!)8n9G#ox)NUiA?8M>PfLWQxM);^4wV5zi~i9 zb{!{IB@4cgpaeZT2H-$M%6@~d6o&QSt>W>x5kclY^wM}yC%V14EC0Ux>U}<=#HyGJ zAIR8c{Rb8M>(rmdvRx}G-fZJycJ~Md$fs|tm{zIhFE9@Np{ygs!=sUAX67h=D$h0H z5{|g26&3Z1dmE2gciUozt$6ng(UgeGp}}rdb+yC(w|>Ko&Th+`Dupys)L2tFTXoOG z9SYeUqO7@jonH4i#=Sqn(u)())ixs$PtX_CaM=+qTp3mdG+yQ6vOGd9YwEn~K6$!n z3EyEB3nD0XN~<#tAXP8e?E!T20tw}7==sl97CpBzC?dv!&YDu@#3{w zQf#w+;_diJkvv+Ma9(Ru(ZegliQu9V2=5kavjd7Jvg*ULYCrYJ83Tl$IS9Q z{O)IHDOup%c{>!narbzm2d^leedA&Hc@X>z=xd%7qiRPd*P^+um2-) zUbE@X(46jpX7zC-@0oVOY(Me|y@ah3JoH!hxR#EFrubK!xw2KW%$05qcHdT7&I+iu zD#`haJVMW+ihJ<@eV62*N1-|NyO!T**5~J(zsg~9KVJAc?qWj<#n5tSTS!z?iVyQr z8nj1OD94FzH5$^*r`0{TJwA_OE_7ZoeJw2=oD+~UYQ37dVp4`=)nAv?(=|KT>`tMm zN&sY&`k?x`@tAFYmhGh6vTNEwa&@{=jH7Jvgmo+$fM!K;g|u!CifQV@Ir&x1jZyL( z+m!0~y$tQpn6P9Izpotco1adc{kd56lpftE*N&QuJx?FZR*CEHCm6`t$iay&)88#2 zv2Iuls*dH`LZ+P~_mo?ZJ1%=GE5*+92qy2@Jc|7+G%cy|iP$k?k&AVvfXjxYu}ZZl zD34es<1c$$J(sgjp3s(v_u(I8wOcZZPdTv&2b@juJJ@Z@>sl+iy752bICF3LG+9wP z>3p!N{bwXowYXCt0eXPODE!C`p>?{6iSeA{NNb+~5$_#dF=6F~?{&!d&ZPQy9~_c# zT38kCm0CJhoZDX5>FAvEYn#@bZWQXFIGqn0Y|$Kt*ZlOySy2gLTY>ZLQ$~$Skr%}5 zG(BM*x_$Lq#fvGW`4Vc<5)uQEC4?$>iiO02a8`=S*Xhkqd~qk43OVY-Bv-7b$hc_v z`6ZE|2`kkkim8f?s-BDWa$(>A`-bA4xXW$#DD>g39RG{9w*bp3d-wiPM;R4$6hTp9 zR1}a>>9*+Z2Bo{ZHe;d0gCHS|AYIa3A|Nf@-T43yo&S3q^PBTO?>X-|=Q{7chU;?R ziM`ieYu)$#U2A_o)*F%=F^gq9#WsV9mG$hl^=`u*0fe5anPM>c(^z87#9g-PMD%GS zb$473o4VKPttEq$IFXr=Bd#lTyF0K2XMLsHx--srQ^u8JM@`dio7(w7@I$Kl&uzeC z2}fqWtY|}m*E#^!4qMZXr%ZeM-255vqV=>11l9Gjd(p5DqpTxVvU)RzM-yAVove~$ zl*Vzl+^8R0%RD~sxVp05dGNyZ3X_ZbGX{fbWgM|F`WNFNE%k5FA0WoEm^Y`&n9}d9 z;O{fr=N&!ze687Axi4q>lO4Gj(z<$v%VzUS%=5q@xBZtF_yvXga@>4WJn}meh8v+c zirvv&C*CdJpJn7zT;hE{Os|ywA{L>r%bT_+x`5H^)3;vg&ldINwg^78inFbvw>MZx zrtfpS!+%$KfVrIX!jgMJZgAxFQ+ zTFXs7*esZuo?hO5-#J_&f?GM6Q7~#ADjIYm#!r3I7xk%SwDwh>FMts1F9O^Gtlm(2 z^Vo_b2r8@KFYV=54PPohOr+y2lc)GS)TU+8bKmyO{yc;}Dtmig{ok778GLnZw==sR zLoaQx!}i4I{jS5A~&=D8s!anP8GW;cr0 zCag1hGCZJAL?_a8G7h0Iwp97X5-f{RBReV;`P;N06nRm7W;#h-GvD9o%q;L-svJCF*=R6BKY&H!V`s@r$*X}?65Qxnwmh;PEX1xO{=UDxI%UkPwD~M zjClunh_wI3p*+`+{Na0M0cde_=u@l(RSJ~9qH5<(Z%B`ol@cv&`VYA-@;7UxRrLw^ zgGAJzF+r!G_FDt>xJBruo?9$P23K4c&p_X4!)(!q9`5Hmt<2Wibn5zX2D!-j+i-Ff zf7x7rbL9zR3DN1_31q%%R)5RdNq#*kiVzFSgoq9g4h(?q$1W_huo`nt>B?bl#^jfk z!!D>q1C*MvVwknCh`=QAt6d`gOMA7mIR6RLl~#KD0eWGTGM%SM{j@l>d_%>xPi zId|w}QeNH#R;eJ9b zjdSDWc&^^~PiDpcjABP{|A4<7tM8fPLrKPYC!f8+IRDz$nD>|qUGF>Dc8`C`jS^ZfahK@6h zD62s@C?pbr$Q8%yC zYPu_6Av|*aVpDXnlwuJbnQfVx+C_;^k>L{CA2e@D3iXt zalx~QUnoDQVDDcz6MIp=zFCXln3O${?_bsB-JQQxk6W9WNyG*H5v#Q~cd6;KsJQrD zcHzmr5F4d(tmIqD(v^co8UE&+j*D?fLYzqQ0D-t5i$RYNZ&?|rj~yFn?@$0AtFD!*mVuQ_$$Q?%a{`4{#bQs1(NQkTXo;FHa=)y~HAX|32dWv7G7X zef5AK8C;Da1qDUOL0_=n-9E?A{akx3Pp<<8WXjVU9>bQ&N7kCJZXDIuFiUU86%-ez zXda5*6wWALGYQ#r)EQGhb$X+gtbi_GRQ(3l2K31yPprA^V*&i@N&yvtE+CZzIon?yE`XwjJ(7 z$8K$EDc=7;=-!@V(E89nz-H#0_H(?%%(I8{mLjHEx*KBYXUZ`D8cCUzuYdbfHB%~s5b_1XSKIv?4yjn%L zK19M_xL~C6*{zm`5P*gsAg!})Sv-^hQJ}t|p|kRs0uOHMdFXzpJ{m5&=?|r$66t`> zbd!uX(b@bk*lw{W9@aTTplm-k>=u{7x2xoTk;Ovj4XSc;w|4W0!Nei}a&c;SGhhycBM@JEF-6>#NxfG81m7OI|u+*|}i zd1)wVQPl%-u2Td>BdM@ECEND`MqxNIczO&PX!jORasoxz$G`@+))C&Dr|GKFFVm|& z{Xl%ctax&FV+I};2#-t>%@;*u;!Kj@LCQ|8&c=ar;iL|3{G8&@qBVe22Y;@I4Rn zGU&2fQYPtPT2bZ+<>D6YjpRtsA6^lZKlIOm*|O5`_H=wdwynY3EzypQt3l^p zLwAcL&+g;7G{m>;TNs5~`cmnRkpEZ^V_KdV+klma-eHN@BSDS06KAg0!8k`xfBBQ& zj?)3-72Y|cUDy7+`DiRLT{lr|hP1`Icr>oSjapwIOzIwr_%N;En$pOt;k-$hYufz) z8fqK=x0#tJLDG7q5VzW4K4DFl^83f|&&l3@g1m+1e5jh&l{SuT_Z960 z#+bzuC8fq)8y6=-aO>0GhqUi*aI3TtOITAp3$2$P!r47B+}ZH(10k3?-n(Z5j;e9- z@sWyL06>!lVqTk!1oc7w8To*-?kQYO6Q4E~hpxf6QMXg>46m3a0J@4i@E?Fvm^;^9 zEl_*J-H5Q*#E%js25t~G8;630n5chO%|UVr_jFXKy5iveu!sEHsEiCfHyxXcavu;_ z8xWBcZ4d3yyeTaHh=G~CwXhmiOCe5uLzY6Q-z7P1IH#uAnGYvx3;KQq+$%VJuyJA@ zQQI7)metaV0ekF=6o^{_9qcJaxhwu3`Ek>)2k+qt$utg_>t^?MfTb9VNzb<#c6}DB z2_fOO`ZzC}Ih~?PL%fRZvoPD_XwTm&EH0i0|35ZPi9%r8egJwhMB?iUM8DOHDL|Y2;o$QX{%K{9|Iqj1_j(j+@n?nBJgES z%6s%8`P54Y;`W{Lign%HiXP|4k#99D>4knA?#Vb07HL)1R6oF;nH7wlh#e1?1%E^o zp*tDg+%*ROTkA2WLyF38uTp$pjke}G`fQG87ssrAvtpsdfM4+xkOy4}z6ZN&- zlGu_1|5jsvEPJ`Wnofr;^>F4Tw_*5+m?s*Ba*Ni`KG2xvYzOUeneZwT#+8 zQP(>|o!d9NVB*R*FDuMX7=(gv&&ZvGRT()i+Kdtz@EqW@y{B$nw)bU*9giT(rqYFs z^GP5OcFjuH6=KKbl)%i68T@OB5MpAZE^mAIlfDflUZNFMd;0=1qJiwMv$z?bPfC1Z zy*X$PC`xf-?O?-}W>W#dqjhK~@jp!S#(gk@Qy&soaf5fs_a{jV8pozJgl{ z@NyEellNIf2eM`PTIl<*(-^KLi$clQ*n;u1+q=8-%azA=mKsP9bj;kASCWdB9Cm&b zLXyx?WNYk0&eJy|MAY@+5x&pmyAPp-qe}V2soI;dcf|1$y;#$I+UJNwC#I(2fg+S& zL;l#F0ljosPwbzoxoRFj*>td{P4?;58gPZr3a8@ zHG%Nrk3b@?K!5o1oC#jXZR_%FwUfp6U$ru4K6sG(LkMa0Been#X0X;$KpT{zeE)XB zr(^EN&>Wy_d(Bn24NSSf?ThL;p*%Ts?M@>g(14w1Fs!+(4;jnau6=zPd*g^jR-?D9 z({!gktbvGwuT=-n=76@%N0Iq1BrR-z#4AJJEqYOv^-B2fc+JCY$8ouP!BLZNd1zNU zb@Jq0PX8)kprZ6uV0YDreiDo-+kJKF^eJ!5aXglxl?lG}CF2$vs%e`y!?r0hR#sVd z*rBwEiHY0hyHPMvBq~5c=CoXi-w6gVx_aSl{TbY`v4Z~acBDes(<@ximsA0a?$$eG zB>^~gm2AEQ9=()!4nXgx5B}0C!4=un9O|L{BPURl*x+D+k&>v%b78PJzw*uP3@`w6 zTCJl3_EXMc{+XSYYv1rtMY2UK3UyGOn(;jY&F5a1vbuqt}M^ zcGe<+Q4QLTeYbIVaQp4f0>^}5<=YAm~YM+Mg)9!uwh`2MC&zh zM5HnDx~OJ05R3SOr);dMSz+~voO}W}b%DuW1Xz{Cetd+VYunD5YdS1k=C{+{w;ooS{!0|5obVS>5wM0rMk_>m* zXgA15M3m>lr#!L=(5V7=kFYB~cu+>%6cwAHSCZ16kPS3yW0YeJCV#A)DuLGa_KGc$ zJb~}t#ey~H?B*9&k%3~-(Mz^(^Q+hS&frUN!RVVQ?T%9X97(piwubiiSa96FOqNp5 zVm6=zc)Tufl(E@ULb6!Xm6Ix>PZ&8pO>Aw&tGL6FAUW@$9A&h`7DdCyCMEp9EkAfdJI1?aOluQU-9B^?_kuWVe_+g#Z%>oGL{QaId>k)ow{jGP0QWhW19_ z8F??H((SYFin=o4Ty?`ali<0H88DMkfa~_bOff?zlmhfCiXsJ31lxa@mtW- z)s?hTtT>)j5UhxO+chtVh6lyau+XjHoVHYccwp4aSI&7l!i+VnX{wPN!7GSQO{D`{ zI~aJ1s(vVmG?dr12rN%2g-@B1#5V}~%J!d)pm8|L(nHbpnEw4{nw`lYoiHp`pmBux`D*6raRDz)%)|%k2cu$2C~9pylFH_3ucu zlwF_Y6N)HSk2&FEG`y-pB5=D*a~sFF31L>Z)o^kuLII*EA&QBq-Hiv9$Z`pcQ{tOKX_ue9 zTFu)6%(>PRndl94@GdOS#l^)_&yF0~UvZ8(OZF*wOYwJb=;!B0nL?J|iQk6CuLcIx z_V*B6ODLOpBv^&&m22?*DV5frBm4E`2cvJ)O1x$3@-6+UOJ;PI_);9;8DHOAFC$Px zLnhAou?osnORR*hzHTp;5Ux2>yY@DaD$c1=g1tSsayGRAtWXbO#I3E#G<;%(CKC~V z$Pl3Eige;Og6iZ-tMTwGI%eB@5%^HK`ZwAloLe4e7lspVc%17q-`v>fiq?5hBKK=E z!Gq^EKvKMY=gzmc-VkX=7!xF;x&RQC1D^1UfLsGVNJtE#W0dcVj*fbIdjlu)xm-Cf zRc}xn?3zKLuwan74mYM(Klzw@_Hlrs`HM2Nj#NHJIw8nItqWFG!YVlie(2-|6Bew2 z;w5`S00w)k3@{@r93T!Q^)!Xrf2Oq~jH`AOFO=zjzT0n898Laz~C*M$nRe9O0i$SQ}u%5Y?np$N%xG zkyV)4fBe(J5pL3te-?1VUP!5UIOVv6ga)llYPg4x5`I7QVO1Hy(-jdDlLKZ5*nat7 zetdw1#6RBu_jjt1JOBIwc0Yi*D=EE4!Lpd69Y()i9Zn5G2h65SoF}B_Gi`oegnI+J z@9f8sAS5B7AR^)k??mHb!Ulq_Z2veUp603$UA;=1(mA(Q_J6*Xf1C>p%O3}WuWa{` z02*#?HAqMAg*6EX{u_53z9k^=pe5n9Q5PIb^{( z21Q4=TDq%vJ^XR@?u5EJI@Amd5|x#e@FhkpLFvbSoPvepPhA)fZv^hwd86U7QpnfZ zcOkL)@rnu>z^gavS?8`*qsw034+dZO;P2m$hZijP0dSh=0j=dLfu{=|`BE_H00m=tr4?zs?+1dtf^z4K?-G8S49K z0zY3}@#Y}{nF0VoVzu#d$wj;W?lgLTcO}n>2_3NZrv|JaAc?JvH2-m_3Mukn7ziUn z0{g3x{_Umw<6NYFDCf{ZtL(O9_d`BcRS{jda2)~F$L8XXE4N1TKrv!3eqyicnae+} zjOhC^;rD-kZ?7M}4X*vwic}&ZB5eRAM{~oIgtwqJP0an(ci5!`1YQ9G2<+kEJE9e| zXE}eo9WNNgV}CyoJPEjA|1U?_e?Gt<{&WAMSN}g4VgLC6gZ}q#W{ZEE%m2~{L!0?O z9$?6Sb%gzzW0LDo_~0LZNX_j)xtRg94rMxGenCM+)krX1gXFT{;pQR~V4*C=dWS_N zwK9IZSD6h)mN(_QPBWJL338HYZ&tNiT2}99!Ryz*BaqReJpg)e-29I&aROpa)Kbwr z1LPpWGO*lH`Nln2n>7q}VWhtq z2Uq>CQC)E#3={l60-(SsZ2Lrq0I0>^ze`FSIVfb)ft=5F(6(FHU%;1~9?r13x;k*D z3@!)UE=3_QUIEq!NDv({{HJ*~dbGisp2l(*S&CCmb;zhkeYcC~IXIMW2zbA()7u#sYjC zxE04woVWtP8;-e>XU~rMkaEa`v6%-^lCfLdhdA5`4{_vJjDE(M*@Hj1|3`%HVCxZh z=9RFT52XU<=qotNQiJNQs0ea!wmOvkQ!E4AQ1jyi!telF=81s6&!-4OxER^ag{ zkA3@75bPMGfU+P5;rL@rxp79})D?BRUR@$HV8h6>4LQVuVJ5nq;KfjsD5Ly@0H(K} z;u!+!8HM?e87MIqc%A8x1PlASZH?l??FT|eM&qs-pw+=E0k)I91aMFsuuqb}=oQUY zfcT&|O8}nSiV6xrpx^77=Yvx4{Vy$1nxl4**2M!0!T@wRG zqj)#$VEt!J7G$kLYuW)!oenB_pb7mYO&4i2^@fyc4Jc9#dfA2mE4sPF2jqfv09ZgO zHrJluyHtXc5Kit`U59!OVjgV4(DNoo_AEhelmhSaR&e~a0j3pKM;BN+!3>SHIB^*b zdg$SP+5iOuB5@r6K9+%>fcNi}X6-;APJ=TDF6c^-q;Pa}tTW10C?JClI_SDN2#H>6 zn0Z+e6vX^!k_O5vaOGf$9AXA=hv+QR95h?UszOLJAu|+~k&}~4>Ic0?Y1D46I|H36 zJaz?}U?J=K2~@m+ko+0>{JFW%l>&XcFT>Hv={EFNxaylr#X#J<*DnsK z8Qg<6#^5Th2%Q^(CRwv%HIbqL5Ma=>7nmLzj*~>fw=IRF<-rjD9VNYbQBH>`gk_2O zODv~<&Ec2c!k0h+ZqN`!U*Fi+0gxA)2Ksy`0OxLvQo~h z@ImTgq$p6^VQ@g-)0_ot#cj*5UXw1M0MZU9=$1h33M93+C@-;e8TovAV;-F$cn|li z6E=KSVUsQG&71rW9y|czfuHQI*3aD4{q*wM8k#4g)FzOJ)ooh&onQ(SC0khp9K$Y)}Qx)gO|6LXTC52Zlo>j-d#%p+C&c z6N`&!;7Wdmf>#ZI#&WRBmv+C0HU`G13eevHRhSHx#8FxLMl$NWadK znaC@fON=Lw-&NkT?FX<#hj@ln(hHRZahSPWRtQ=^+35A5=~M zc~1A=N$HUPQPqUr>i;;W`>&>SXeIHZ{`v1#P3ZUk+e+hqS~UIme)oodR4V_4s_B2P zX!`N}|Mpt`aW4Ns)%3qtG$H@js_EC+SVY3+4p>jDFQG>@J=lZ@NuRXYRghbt>XxX( z?>KIjw|YSILqdptNbbCF7gFuXlD-0%aan9;zTFo2t%HDrg9E0Nz&af(yKBfP59Iuh znln5B-5cG#>Fnf`Y(88jA8;a(1LjglZIX5K?)?m7?zj$sCP93Z-`Op;od+$q{I=L{ z9X!8kgXVX>zP?2OQ=Y~8Vle4NckYw%|BUw0r{waKlwlfP2PxdB3eWZr_)Ms>k+0ui zi8e4a)aXYJXmOIqY3;4AujEUtrVFnP@PJ`zUQAWGAMpTPB1U28goLpmc*e$}UC>?h zPKX$+_b9%^0>$gr?B2P1N2Ol;oYU?TS>_nb!fG|dat@j^Qu*+(4*Dj%#OqLTfFhN+ z4E~chZcNV3&VG4(e-OUm`=6sdfi9T{tw2=9*U@}pnBKP8{zKyvZoM@TCk#rCh;CiM~sp%+O=9GiEcO5)-pc=8D<`o*DARX~AB zV3A*i`+XuV#q-&aq8Nd8y8hw_fzJOyjgiX)IqfzxbIw3NKEY)vA4B(|zkcO&gaD-s zEK(?;qDn|RCjWPuM;Kt+^R`J(*1@3wxIF|S$2&BB-LCLx%W0fq4x9 zgNF>|DBuKLAG&j1${C*C``(`T0LG-{;ZX;-dgyxema4GS1=*p_V+)e!_%P#)1;Gt+Kr(zR-qGmOKXA}U z%6^-^ojKbiL{}N}DmGQD;U2AEoNipwYe?i+%0qC89U^9}AtjsZm~Q!6N>ztoux)&gUojAJ%zqvU(! zgUEi61+pg`6Raz5>Rt#^{XrD`WfsAVYGhr!DH4=try}!}jHe(fd6Un*Zm6;^N$8p$ zYZ!;?n0cP5N^RwSv9O5PmztVa;M=~uhie%Qym~6-8_o6ahfIb4yCyXsk&zvr$8e6< ztpuMcK2j>wIpS=;Gd?#5YkTbo@T=fEW&z)M9-QmzbUqIa@$x-N`t=bP2%{c<&2{ki zU#HD_eUaf(bL1^n)>lrp^JAk%zO5UPgCEpiqWT3Kh50Y}ss133c(Ta+__0;f6XWOl zmV}sF&vRunUs>jLHQ)bnonNjr>iv%Kmi3c;?CJdx(P4UU-crr0lr4DVjtbY2A0OrS zOpmoH?kl_2_`B2y&(}hc#a29#I!px|gQ{_SJ~J23d!dX$0s=~I?t#9f*XS`30`&uv z*#LX9+NtlKo%1bm%h^mP;Z($RtybU{Dgzi5+CIzf_V>1N9P7(9E;&RFb^#*f@e+sR z*>PUKzP(hx?hrG+6u~!__^S!{BYREs+CB;cS@n2J*5tDDny_&K<#m}qAEsBodmCV~ z$b9Mre94a%pP&bXD{PU`Hpf;lF`lcfY;EnMdGq?>Yn|+nk*^ModdB+xhjMTQ=;3#r z9s%ws&Hr)rs%YTc zx#cHeXS*Z>A6tR!d=QC=k{x}jxIJ0R0UH3G!=k7%SA_qG%lHMV=M#g6wKb{+ETIG1h>u`xeQa@fI6t7|}DQXPFJgzeGs*d6SG0{g3mgDH~q1h|7vy`Iy!IA48Uv=F_xs&m(3}?vvx+} zxFD0!f?$`#&6dfjJ}5zZ%WQ8nspM1|=m)28A7#{dQvc0@ozdaJ`MK?W&Vb@{e)Ide zSovaPAnnZjsPd}|H$_*fQZu%8mn)C9=wg@1plO+Oq=reWgNg;S6&5pdb0+~u0ni+{ zvD^uN-&o;%)&YaPC&sk;{?_@hf?Q<4TZ6S7x%?5MG-4&2U&X2socW-zo zH&Wf=^i&}dm4>f8Kl;=VQ+ScKX2y~N@O)qTt2`zQT&w%73sv7Y=_$~ue~G4YZu_Wr zb7|8m_Ngsnq*D$PZSX-*-_jtPM_Y^=%b&WZ3Zeh4rv!<24`<%+xXf9=CvGes|0}F9`1R=vwX%RRQ&yxbOxZ{CazQX1e=fcesH|QS@sDrYczz!@LVg{E)|ckkjB1YA zx}-&jq(|^NKBFqKf8uedr==Zp@?=>fJoWA{G1|4z9P6Vi5y>ur1(Zjg?hWXjFFamm zY0X|QI>c6zRfU3P^xZu`GfE4}a#to=uyelY3J@yyv~aL0`THJbC6BrQg!nqhu@CF*(-+bGIi@y%Bz+ zyrf_E+0kWZR4JWbz>5Cf1x2<^=GuHSH5k6~E6Gyl%LU2tpDcRD5uXl(2ks_ne3Vpj z%sj%JkvBHA&TDg3&(r#>wlenjwg%aHOU7~v2hKF=F;!_?{t7o6d>&OEW@ucyd+o2+ zuPyA%`agfG+}~abOHy7kpEWOUi`5oYVA%IbQG5stK`QDy?r_E*jf>++xt!Q@P0h-} z_#ZTd@ezWkU_2?d8u|BJNy*cbjGQ^9>BOVHl5{8Z?jn=tCHwJRN6{^=KfUEuH`_is zib69xj!!-y7QC+4U6V^oBb~*}$DmT#diq1^z$%q^Xg(#wk^n}_n_~Zjbaw`>Bl=QL z(II;+qrj)lFZ{gwJI-RaoXhriPQz{=y6G%4qNST6cc;5`NqMh+?ps7wo**fCcw!NI zPt&_Z2jISC>q}I1bp?%@`|4xgcr9?9|Jpy!r#BoK@tbD-j?EAYZP4l$*_Ka-QGeQz zaWphk34A2?@_s$Cthp;AC^d9=pWY>TuqE*AlZ{1*^7D=LaWt{=`%W9pcZRkk%PNM3 z{-UAqowkyC5`^0`XRv?XiO?Z>&?O2AgGc6jaXt3e-CFU;VhTM9pn7N>fs|#Q#XcdtK4`_o-VC_ z`@mIL=rw-U;b4jE-sh<~He%OX4aXvEpOM3Eb2JncvQ<+N zA2dBOyW4Z+efATQv_9JkovZ|-^(!uH=68nzm&UE6{sa@udRqcPK`J-3E&L|cJOC>H zWwgmxVUHu0{h+1GfPXy8sCUd{-QM`O!MELlGU+bIz3uD@3Jcr!TWh!Brp8`+XlY}U z<&ggT`3<;@5?-vxqNR=16mL|r*ufkZy!eGi>BA=#+oYP)`I^YtiqnrgaG$@iSy)@w zb~gF^)l&Xh6$HUTg!L=e2siZc;Cf*5!T`0y?er1N*6*me;<%v4`1=_m;+K2Bu|+ z-qn#u8ExVwQwz6Pjq10v6J)*W3b)OOkCW@xYhg!(fb-_3njnkbfv-UKm5CFPPDPaN zqL~>S{x?>H`Sm}_)G`bv$>chPwvdvN&bIUEiVGcv%`;Bbz^zS`%CsuF?7QiD#oaV4 z`%yXW3IQ+Z@5o>D2aoOIHBcPb386Q&e% zFS5NvD-M3>|K92+wm(TJfW*KISu1+?zRfkvGiPBR&F z>zl(`3yXPXDw5-R=i+I?ofM0Fi`K7NEmvr492R$KgAz4fgbGVz&SDzY z0-ZZwimr^|sg=78)`v{cqq#47_+3x>y_3T-_Y=n%cVOn?NjplVTF@wPKW~xMbJVr> zX(lh-xnn!!E0+|SrD_Y|rr3T9SIK4Gn&oDzClTg9T8P1H%f2lpMo3P&XIt02e4wb9 zPq#TeqvPm=TPgSb!)n{Vn&oaWua&jjGzCq2cWi9T@^B7$uvWDJt{kK6*&l7mM2=;9 z+wkBomz~xgv3|WEQ31Uc&HAK~nU2FLMpgWZDs|_N+k0{rHgmY9^rrdlZ^g#W@SwzF z8JP={r6q>l9|#|L_#%3vo<1SOe&&~K_f>+lhm69)uzJ$1typP+Ql_92XXp<9iv85~ z`e%vZS3KZDz`9uIMjvOPZ5X`d&ARZW`AMjmK)by;5xy6ZeW`ZjVQ1lPtj*E^buETm zuVIJ9WVpx=ta<$3{?S5(Eq|0&v~D<*C7!q2M5CA=a2?UptIoX2X2q(qW~a&)fAIvY zeNhi)G7M6T$KH*FaR5f+e12z)W#iNAXIH3U=1|70bQM#4dfIf#ZO82dkJEacG(*=~ zWwE&*aD=_O7Pn?_<}$%I3&1{*Q*@u5Gw$N9si`T4!;aNlId;Nq*NiwvL_S$M`Ta@7 z+8BA{O2R(R;+k0z7?Gt;B+1aOLBd#}fP2 zblsMFY-zD39E=dAqL$u!=#wB<*o-UVqP_N9xBg5C8g$IJ74Tdi;{-{x*i zy5@9ul#Eu6S=9RZzb-XLRMxhKD3om`s5-Yo0N6PbhWF&o<*WgHjZfK3tuf-xcx7P+ zx<%un#7&Z9m7%hbcP$K-z6&xv41OAB=F5AwA{0s_qtD|N=b2BY&=3|9lL*6DMui^)f9WHxa+WWjmQGP6yDPkwC zdk8QqL50KO)9*HiN9O|nfcU4hj3ce_b~tnOYM9!3xphDBg$rX$>V(H%c(=q&^te%P|V2a zH^Vs{p9wS}DpIKqmJq7PWWMni9kZb1!rb6Nq6U;$BMsi?2*pM-tnzYJV&=P6lWoF5AaJ8vUEQ#)5Qa*Ih!*=1|e9_@*O0 zd5(|fIHFu}Je|fJUz(d=q=a3q3}%cYpNE9_w4%wx)AC3-`@bp;zLY)|=CoPWKFG3# zs9<2!AMVD+~sQz)h3wPwSreg%# zu;Xyw>}#R(GQZiYQzZOqXPF6aa-L8{{LSgD)<<0u{Er`>67sv?p>gw zqJsV5X%UzoJ^Hm!JgPu=Nks8BMe;Z&Dx_UWQ$_O2a3_CcG{<$I6hJGyUS90CkSF2c z>Z`oh&KJ@e%oY9ofY+}tI$de~DehSXJ303J4tMf%pW%RQ(Z&paH+gAl#4Di>L{2O( z<`u^hm6~IP=oSAyU-*{Ix1FMwRyR~LvY%F+Fi!AKkVAeZ6z6GTU{Xkncy=+`dEaq( zzMJt^5&T+5S?BR@P4hd;D+R4x5|bDOM$Ly&OJq2nW{{1lVSY|}cWWSXu}B^PDM<{x z=B+QzR}2k$!(0i8%ttdKNh8JE;t2|8wk;Z|pB+7B)ses=9<@V*PNd9;om~3v?Zj!< z{4AS&M@IzrF3#Ryox&>#0CYF-&nex_{w(-QN%5n*a=3Bz%Wy%ZXvW~+yxs}TQ(9l6 z%hXO487#`Jepxr}cGwP~n(d4ugL%g99Tnz084G}*fWk%nNp9J^!% z>5fQyJEH1&qzB2MvYls}Xi|*4aratf-|U2lkkIe2p@s^8Uc=gd%b@w05;EWa+PrM*1txM^44-8d0G6c>r+V?TLxSzyg{=9env(~P*;Mu!27D#AU zvKahHWqYXQyD+d}2fMg7?yf?5+F%4kn~lFk z8Mv792}+Qw)L_@h(s^lovDjtPEvleTRLE% zH0YH;z8HQ>;=%7A$&^4Sd@}H*1*)i`YCAi-TfFc0Zk~Al7fxR?+q|WjZape+*Dqq) zreSVU)Bzd5A{`kjL9aFyN1zjoL%>V z3C_Ej3!BGhwIHs>e3)X#!&2# zEB+rO=lyobg{Hi_Bz|h>lo_^*eSx?KipzAW43axxNcPunb=dP>wU-W7k6}wVn45$? zdmWh$;jzBbpJy_XFCS)Ub&!9*Xf0pU(RgNrdGYztV_S|JVhZ%gZUhA}LxMol+SQH5 z$Zh$e>qDBm8+YJl!v2++c0{Vknoc>(g4|evPJBNOgPEL=L#ERb%Dn(vLTm1 z^`N-u`zK1-nMEzFjH~@$0?1c3(~1Zt-G&^~m!5$k`8MG zOx~X07CMDwSD$u!+JZ5KBnO8n1f|lOcaZB3+kK}w{htyU1bVBTsB!4l8tEAs#=f=h z*$+(7Lc&4Gpvc*7V&n*Dv(f41H%e~HH7b-GhOSKNi$yMLgwlX9d^4_nlS%1d_h?yI z`3or0;yI?*>Ih5N#yUIvydhgeQk9Bc`^+n>EVh(GjA=O9M8$>G&?plmE4G=2et8dD zi*?wDQ?#}!+`Ra&>MOx@cY}MO`<)EAuGL^LtPeE2%7skIV4#G*Xk1?+Vxlx(CSH_p z$o$W+4bst>mr}%O1CM<|b2&f8V^E*ul2jr%${EH-W}iTT z;RT4~V>GdwpwM9@L+kU79pa5B=aE98&0%qGai;3usZ=toiYoiHT0xA;ufv>AmPPXT zx>6lQ^_V6EAs08rfz))`#AvIG&;HoARQ`Fo+)dLC=6-&OPcD2`tKoL0!}YKn0P(TP z_{Pv=i}odUi`5I-d^_H>*D6ia^5-yx{}uU7rhj_PF_=a%@|8FRCCy#G&W7NR4cZ7y z3V1e)+e&%Z=|%8k{ffpE6V`V7kteL!{Va)HJrV!W%#05kS7tNM7e&X{vMU44o$GO;JIUWCXjW%09wr>pi`Kc zE!iMzmIr5lABX-5`M;H`E8Zaa?3s$y>RI~~&1@~2yu3V{<|h}e6S} zS();XA1-!|#h_8LJguU*%@w5eFVCW;KXSA@F@DngOgG1<`v_$Hpd2j^mQ*`{S^}y8 zon}emeOw*VTpZkum2T==$LlH8HpZlHYs5h5s00s>K2DN}qgF_LpDX)4MazUI1vidL zxaWtNCT_Sh+bhe#I!ae41?3{$X1|iWh?ipgC=xxcBdIeSnF_qGd>)zUmK)$ z^5lsT0TYv;v5BtSuL0}g5#bRy7EIi(-{ADctAtHUWc{$3Hac6$n81~wkELqo;S#MM zP&0elb6PUna`3iRjCJ`*aetb=cc>pE|GRcuWw@@qk7M9}W~@11Ma4kaL|(MfvfTz187e zzjxCYEk?Odo?HpoT+L&59eQ#{DqEtoI;RfcIe>K7>4F+O8F7XM9#ydOZvX$LPs+wozlabMM671JVqE=~|tG9`0mqMS0scuemg@)x96a|0k zIZ$}Ive6WJ>Uz{diL>KA`CvU8H#4*9v~K)h)!^m*AqRAOy1whsnZK{zlQN8ZZn{f| zKKo^>;#mwP$rK*1x)XAb@f9ja8A2KPQ;SdNF!Po z0yGcyACYn(4u6_oXuJ{p{3wV0)J43gmaS{*mbb2Rdw+dx@#p*vLF*I{tL|PVP$OWE zDmv{TfUa@awmX3o7O3*AJ}I0#Vb>)YaJQmTPw>vOVcQqsA*-OW8~G{H*{qBuoZ*G# zX*%70*&W7j??pH_ykhB{qk`_%oa3Cxga?+MW8gm{%X(|BEmkpocO!Ykr_z9uZtXVY zEvhEC?j~ER!(G>g84WUyfK_fY9|!tGm#o`-L$Ru{^n^pOr&-LB9= zVqcZMhTNM1LlQV1ZfdSk94DPf0yJgO{lni*%T|={@8CKF4!B{t7Wa4-nR%fU2 zE+1}KNJQ<`Y84k0TrJ^FWCR=vM3cXo-FU2t*r;0SdaGYTwEtqlifag0p6$y}*>Ij? zSS2a6SR#PxFK4xs4;d`Nz#0qCyMO!BRy(C1_i}*;+i&a4th=)V>wU`1Sp0(NjJ+_N(Hh3i-Dm%+dfp_8@$*amIPg>3*ueketWe-WI zu3d|C$LTvBMzASwHTc4+(c0B5HH>u`ix1}yajdD0W0F=*EaEKpSm!i{^A4q@B^$!N z-eOtfO0yMNQ)q{V+sxatQE3o8_E*?NZ z1H-ju?Cf}N;JfLy&$KjLj>t+Keq%W6OAsPa)sNYn;f99u3g&Jp?Vup&-X~8Hj0A!v zw=gprFi&t!d*zwtd*pE39xumeQz{f^wOV4{ove=yX$zbk*ScRHFOI)?D12C3WCeYRo4es#cK18OX0PARY2P>JJ4@ap`tTt) zY(&tyk2Qm#Bk7}JYcQNCYSh1=hDhMP-;x7f|JCdSL?Ob7qn_r5UClF^(_JZwzEqJ8 z&z#m*SrJjsuE#Dv#2LS^1>8*V7Hn{jg9Sf0F-mSa=0Qv!5Ye7CdPLpR`(oS}xpFgG z-z8+K07UIuZg+XshaqWP8G2FYO}5UNa;Vfe$48uCXl`uWFkzUzoNl9uv1U*xVMjoI zvZR^0uHMI7qmwl~64DS7seR`%I`5AUlQ(zy%+Gw|HkFQ%%@@2wMU@SESis-%L98CV zRrqk4pBKs9kOfGjb(uAx_9ss5`N|||Grct|_?~n7QM|;#ok0%B^2GME{=e3)Jshfi z4bxPTu-z(|vQMO3LfDCsORkf$i?+%oavKqmTn1UBEsS9sWZOu48`N=|+%MU-`z2#c zF54}IL1NTHlKc4<<(%i7^T&D4IrGPQ9?zP!=I6J5m+yPO_kEYayIli=tu0|ygcTmb(EyoE=5_ z$=ET6h!$sK9p1M^rKL?nnsf7{xk!U8W`u)MznuzeO_dm0^OHK+cYzNfxjn*ukehp$ zmjO#A0Dn!+ga?3)endX$ul1IZA``NyH(oXjjIUJ(I${t#-T7?@cslJO=-TP<)`0 zw^Z>IvN!^jy$DK%pGybN3e8sZr0Sn~cRW1W6rJV|e7ES@^M)PUd(VGqD=^lWz6$xl@<1k z#qi>8^iqixUI2jBGf0=2G`trUi(sYSkI{oCuGzKpyM4Y?sb#&4PLbJ`Xb5azvk?U3 z9Kf75W(8H$Q%QtQc}y;UZ2ryOFQ}#MGGE%aA7({ddgkdrXrWsv6jm2s*I6g6)#Vf8 zQwLpt>B`Z2+bw%@eM<6?;|m}UshrHSZh4dC&wM%{5vYc^x`(j=(rLWk(&_wa?bkg6 ztj5b`QYf;nvEDlMe6f9bQ7~OGX|2IQ3ONuo?B|F0oe1RF>>7!gG5 z$zs}?36oNSGxJDBJO7mnXapQ$7=aRp<>Ga@*4n%gg=$R-)WCwC(JCu;X~SB`xnFtY z2Y@aRbiV+)k(^DzPk-9@?+gaK<%)8H3K}azLU)8`;+ao_DLlh7FtLu8oCoZFphA9t zgM<48UQg!|AeBi#5M(INg5!<&Rx%9g^PU`V{A`k2;5@=7eA0o!hax zlh6E_;Y-okB_nWEtDB5l|In45+IjF+SI&<7J>qUZw#kWUvaUime!sfVd_ss;T&yA0 z4mBV(p0#67x@L%gm$isT4@2NQx!%S@?8(n1^EMnLxvO1}P}fH`NSV}Tkl`GamzUpe z{rr!i)Q75qtpWN9OPpH?RNrYoBrFxGuw!`Kq5i^! zfn3BgU0tav?i3J^zQAN&lv|p_0a6mp2W-is zHd~0U{x3{tR`+GZromQ`=9{HexuR-;J0cLf?fS{&MbE~4dItAL*=+h~jFvk)em3E2 zna`93TB=k~qjJ(bW_(k$0$N)|Bf?qLBGrtE-r;Hw4-eb=aDiTUg%&O&Fym{SF(?FO zE`1=H11pEHL=YDYlx{|8p)4_Eoj3bx<%%x{h^o$HoK47~82&Q=y|=HII$fK&+KT?C z)F)`R38Gml+)>Za8fY>_*RsNe&qMn&^YDZDHdBL?0?bLG1< zP#e5?t*#rSG#;>BKsw%il_QcwD!eVVaUWv#m1lNQ2XT9YpNQe_TgB)KCFxOVq?j1{ znwY}g!n-^_6<9R^13D+AyV`q)h`N5g3-Dk#!lR;!&^sV-lf|Znn&4Ds$eZRjWB7(>G+~X(-{dGx_G2Wd}?YLVF759|HrG+ICxn zTneXQT!`ymIQNqc?kVgL02JGJg1q|KtMwscX;)@l6Th-+e^y`X`4hsrCnAHQ_@xm1 z54UF-TH!EJ#SSI~Haq%-#<%Q2gy@=~Uw$L-Nr1Xsp^c?P8a1 zfPZ!fb~b_sAde*F?*%hrNJvPIRk>hjjrRMYDtJ+%bU1BvD^EtB|7=utwk*h|8-|8{ ze;k<-{58gbdl(I?wts4O+w6VxIOD!m7K5|Dq7RwX3~FYhbLWbD4s1*aI0VCLLBTxW z7k7ev=w>f11Qai>SrT7PqxhCX+JgsN2I;Xp$Ub>m(5gRqdXv{9Adrm;0t)DzopE3u zWUF#O4O!F3zMMbaNEYicG5*q$nd{IDqi8=OkqO>!=cI(dPHzNc_7lKE{)cZ}-1t_4 zv{@J^fQH|_JA3Dj3J7rRK`L!gx`~q@demJ|$ik}M2?t&ItArQ5eZa6SEROpow)VKD z<{`BDl%j?KM?f5C!y5+%23nIMHhb6T8Zhu3z>dnQ%3mbdiT=yQZ1dx?goMUZFkmGa zV89dxqG({N^Q=@p%t}vBPf!Lz3i^qihM(NNeY*#%84&<~5l&iZX%;JR-_Me(2|R(9Km#kMWZO%!&Q=I1LI*!Qe&Gf7Wm+{5hE zN&@8x7OYLtr$iqT}+~^SFJxwxwkhpG`~MU)@ZN zOBL@*?6`*!K_Wc~V=of3zzir}?y4Xx9ye11N;|r|H~R{X($kH77C6k--?4+HUj+fs z6}wMwtq-t0bZqHK{?k)v1n^&6_J6yI_`fy(-?T$Qk=c*mS^>gYwI%DA)_uD!j;+=% Qqv9dFzKLGm8QXh*0~5T2t^fc4 diff --git a/applications/dynaclr/docs/DAGs/witness_score_classifiers.md b/applications/dynaclr/docs/DAGs/witness_score_classifiers.md index 69e344a0b..bdbf4805c 100644 --- a/applications/dynaclr/docs/DAGs/witness_score_classifiers.md +++ b/applications/dynaclr/docs/DAGs/witness_score_classifiers.md @@ -95,9 +95,15 @@ dynaclr run-linear-classifiers -c linear_classifiers_witness_infectomics.yml │ |w| ≤ t → unknown (dropped, like annotation `!= "unknown"`) │ 6. TRAIN logistic regression on the labeled subset │ (same train_linear_classifier, same group-aware split) + │ 7. EVALUATE on the val split vs GROUND-TRUTH annotations + │ (witness.eval_against, e.g. infection_state), mapped via + │ eval_class_map {control: uninfected, perturbed: infected}. + │ NOT the witness label — that would be circular (see Gating rule). + │ Falls back to the witness label, flagged, if no annotation column. ▼ output_dir/ - metrics_summary.csv (one row per marker: accuracy, F1, AUROC, …) + metrics_summary.csv (one row per marker + eval_source column; + accuracy, F1, AUROC vs ground truth) witness_state_summary.pdf (bar chart + ROC + F1-over-time per marker) pipelines/{task}_{marker}.joblib (+ manifest.json) → append-predictions [publish_dir/vN/ + latest] (if publish_dir set — central LC registry) @@ -134,6 +140,33 @@ labels every cell by sign. The dead-zone is the honest analog of the annotation path's `label != "unknown"` filter: cells too close to the decision boundary are ambiguous and excluded from training rather than forced into a class. +## Evaluation: avoid the circularity trap + +The witness label is a **deterministic function of the embedding** +(`sign(w(z))`, and `w` is smooth in `z`). If you train logistic regression on +`z` and then score it against that same witness label, it trivially recovers the +witness function and reports **~1.000 accuracy/AUROC** — a meaningless artifact, +not biology. On real 2D-MIP infectomics embeddings this reads `1.000` vs the +witness label but only `0.71` vs true `infection_state`. + +So witness mode **evaluates the trained classifier against ground-truth +annotations** on the val split (`witness.eval_against`, default +`infection_state`), mapping witness classes to the annotation vocabulary via +`eval_class_map`. `metrics_summary.csv` records `eval_source` — either the +annotation column name (honest) or `witness_label` (fallback when no annotation +column is present, flagged as circular). Representative real numbers, scored vs +`infection_state`: + +| marker | val accuracy | val AUROC | reading | +| ------------- | ------------ | --------- | ------------------------------------- | +| viral_sensor | 0.865 | 0.815 | strong — sensor reports infection | +| SEC61B | 0.764 | 0.838 | good — ER remodeling is a real signal | +| Phase3D | 0.580 | 0.536 | weak — label-free barely separates | +| G3BP1 | 0.491 | 0.554 | ~chance — witness axis ≠ infection here | + +This spread is the useful output: the weak-label proxy works where the marker +carries infection signal and not where it doesn't. + ## Config structure A ready-to-edit recipe lives at @@ -155,6 +188,11 @@ linear_classifiers: dead_zone: 0.1 # drop lowest-|score| 10% as unknown; 0.0 = label all bandwidth: null # null = median heuristic on pooled (control, perturbed) max_reference_cells: 5000 # subsample each reference group to bound kernel cost + eval_against: infection_state # ground-truth obs col to SCORE against (avoids + # circular ~1.0); null → score vs the witness label + eval_class_map: # witness class → annotation class for scoring + control: uninfected + perturbed: infected use_scaling: true split_train_data: 0.8 split_groups_by: [experiment, fov_name, track_id] # track-level, leakage-free split @@ -183,11 +221,16 @@ linear_classifiers: preserved. If cross-experiment batch offset dominates the witness axis, apply a LOT correction upstream (see [lot_correction.md](lot_correction.md)) before this step. -- **Labels are weak.** Val AUROC/F1 here measure separability of the *witness- - gated* classes, not agreement with ground-truth annotations. To compare the - two label sources head to head, run both recipes on the same embeddings and - compare `metrics_summary.csv`. +- **Labels are weak; metrics are honest.** The witness labels are a weak proxy, + but reported val metrics are scored against ground-truth `eval_against` + (see *Evaluation* above), so `metrics_summary.csv` measures agreement with + biology — not the circular witness-label reproduction. Always check the + `eval_source` column: `witness_label` there means no annotation was available + and the number is self-referential (~1.0), not a real score. - **The witness score is unsupervised in labels but supervised in wells** — the quality of the pseudo-labels is only as good as the control/perturbed well assignment. Mislabeling a well flips the sign for every cell in it. +- **Class balance.** Gating is often lopsided (e.g. G3BP1 real run: ~53k control + / 2.8k perturbed). `class_weight: balanced` (the default) compensates, but a + near-empty perturbed class makes the val metrics high-variance. ``` From faf741d57c189e13edf6fb9161adf686a4df3fec Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 14 Jul 2026 20:15:02 -0700 Subject: [PATCH 24/89] feat(dynaclr): add witness gating diagnostic page to LC summary PDF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Witness mode produced only the standard metrics/ROC/F1 plots — nothing showing HOW the pseudo-labels were chosen. build_witness_labels now attaches the gating diagnostic to the returned AnnData (obs["witness_score"], obs["witness_ref"] = control_well/perturbed_well/other, uns["witness_gating"] with threshold, bandwidth, counts, and the pre-gating score/ref arrays). _save_task_plots leads the {task}_summary.pdf with a per-marker gating page: the witness-score histogram split by well-of-origin, the dropped dead-zone band, the sign cut, and labeled/dropped counts. Verified on real SEC61B embeddings — control/perturbed wells separate weakly, most labeled cells are "other" near zero, which explains the modest AUROC at a glance. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../linear_classifiers/orchestrated.py | 60 +++++++++++++++++++ .../linear_classifiers/witness_labels.py | 45 +++++++++++++- .../linear_classifiers/witness_labels_test.py | 7 +++ 3 files changed, 109 insertions(+), 3 deletions(-) diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py index ce4963886..783057884 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py @@ -407,6 +407,7 @@ def run_linear_classifiers( { "marker_filter": marker_filter, "val_hours": val_hours, + "gating": combined.uns.get("witness_gating"), **val_outputs, } ) @@ -557,6 +558,10 @@ def _save_task_plots( pdf_path = output_dir / f"{task}_summary.pdf" with PdfPages(pdf_path) as pdf: + # Witness mode: lead with the gating diagnostic (how labels were chosen). + for vo in task_val_outputs: + if vo.get("gating") is not None: + _plot_witness_gating(pdf, task, vo["marker_filter"], vo["gating"]) _plot_metrics_bar(pdf, task, task_df) for vo in task_val_outputs: if vo["y_val"] is None or vo["y_val_proba"] is None: @@ -570,6 +575,61 @@ def _save_task_plots( click.echo(f"Plots written to {pdf_path}") +def _plot_witness_gating(pdf: PdfPages, task: str, marker_filter: str | None, gating: dict[str, Any]) -> None: + """Witness-score histogram showing how pseudo-labels were gated. + + Shows the pre-gating score distribution split by well-of-origin + (control-well vs perturbed-well vs other), the dead-zone band that is + dropped, the sign cut at 0, and the resulting labeled/dropped counts — + the "how were the labels chosen" diagnostic for one (task, marker). + + Parameters + ---------- + pdf : PdfPages + Open multipage PDF to append the figure to. + task : str + Task name (the witness label column). + marker_filter : str or None + Marker for this classifier. + gating : dict + The ``uns["witness_gating"]`` payload from ``build_witness_labels``: + ``scores_all``, ``ref_all``, ``threshold``, ``dead_zone``, counts, and + class names. + """ + scores = np.asarray(gating["scores_all"], dtype=float) + ref = np.asarray(gating["ref_all"], dtype=object) + t = float(gating["threshold"]) + ctrl_label = gating["control_label"] + pert_label = gating["perturbed_label"] + + fig, ax = plt.subplots(figsize=(8, 4.5)) + lo, hi = np.percentile(scores, [0.5, 99.5]) if len(scores) else (-1, 1) + bins = np.linspace(lo, hi, 60) + palette = {"control_well": "#009E73", "perturbed_well": "#D55E00", "other": "#999999"} + for grp, color in palette.items(): + vals = scores[ref == grp] + if len(vals): + ax.hist(vals, bins=bins, color=color, alpha=0.6, label=f"{grp} (n={len(vals)})") + + if t > 0: + ax.axvspan(-t, t, color="gray", alpha=0.25, label=f"dead-zone |w|≤{t:.3g} → dropped") + ax.axvline(0.0, color="k", linewidth=0.8, linestyle="--") + + marker_txt = marker_filter if marker_filter else "all markers" + ax.set_title( + f"Witness gating — {task} ({marker_txt})\n" + f"labeled {gating['n_labeled']}/{gating['n_total']} " + f"(dropped {gating['n_dropped']}); w>0 → {ctrl_label}, w<0 → {pert_label}", + fontsize=10, + ) + ax.set_xlabel("witness score w(z) = mean k(z, control) − mean k(z, perturbed)") + ax.set_ylabel("cell count") + ax.legend(fontsize=8) + fig.tight_layout() + pdf.savefig(fig, bbox_inches="tight") + plt.close(fig) + + def _plot_metrics_bar(pdf: PdfPages, task: str, task_df: pd.DataFrame) -> None: """Bar chart of AUROC, accuracy, and weighted F1 per marker for one task.""" metric_cols = ["val_auroc", "val_accuracy", "val_weighted_f1"] diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py index 7778da100..3b40f22ba 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py @@ -120,7 +120,13 @@ def build_witness_labels( bandwidth = settings.bandwidth if settings.bandwidth is not None else median_heuristic(X_ref, Y_ref) scores = witness_function(X_all, X_ref, Y_ref, bandwidth=bandwidth) - return _gate_scores(adata, scores, settings) + # Well-of-origin tag for the gating diagnostic plot (control ref / perturbed + # ref / other), before subsetting to the labeled cells. + ref = np.full(len(obs), "other", dtype=object) + ref[control_mask] = "control_well" + ref[perturbed_mask] = "perturbed_well" + + return _gate_scores(adata, scores, ref, bandwidth, settings) def _subsample(X: np.ndarray, max_n: int | None, rng: np.random.Generator) -> np.ndarray: @@ -131,26 +137,44 @@ def _subsample(X: np.ndarray, max_n: int | None, rng: np.random.Generator) -> np return X[idx] -def _gate_scores(adata: ad.AnnData, scores: np.ndarray, settings: WitnessSettings) -> ad.AnnData: +def _gate_scores( + adata: ad.AnnData, + scores: np.ndarray, + ref: np.ndarray, + bandwidth: float, + settings: WitnessSettings, +) -> ad.AnnData: """Gate witness scores into pseudo-labels and return only the labeled subset. Cells with ``|score|`` at or below the ``dead_zone`` quantile of ``|score|`` are dropped (ambiguous). Above the dead-zone, sign decides the class: positive → control, negative → perturbed. + The returned AnnData carries the gating diagnostic used by the summary + plot: ``obs["witness_score"]`` (the kept cells' scores), + ``obs["witness_ref"]`` (control_well / perturbed_well / other), and + ``uns["witness_gating"]`` (threshold, bandwidth, dropped count, and the + full pre-gating score/ref arrays for the histogram). + Parameters ---------- adata : ad.AnnData Marker-filtered embeddings (same order as ``scores``). scores : np.ndarray Witness scores, shape (adata.n_obs,). + ref : np.ndarray + Well-of-origin tag per cell (control_well / perturbed_well / other), + shape (adata.n_obs,). + bandwidth : float + Kernel bandwidth used (recorded for the diagnostic). settings : WitnessSettings Gating settings. Returns ------- ad.AnnData - Labeled subset with ``obs[settings.label_column]`` set. + Labeled subset with ``obs[settings.label_column]`` set and the gating + diagnostic attached (see above). """ if settings.dead_zone > 0.0: threshold = float(np.quantile(np.abs(scores), settings.dead_zone)) @@ -162,4 +186,19 @@ def _gate_scores(adata: ad.AnnData, scores: np.ndarray, settings: WitnessSetting out = adata[labeled_mask].copy() out.obs[settings.label_column] = pd.Categorical(labels[labeled_mask]) + out.obs["witness_score"] = scores[labeled_mask] + out.obs["witness_ref"] = pd.Categorical(ref[labeled_mask]) + out.uns["witness_gating"] = { + "threshold": threshold, + "bandwidth": float(bandwidth), + "dead_zone": settings.dead_zone, + "n_total": int(len(scores)), + "n_labeled": int(labeled_mask.sum()), + "n_dropped": int((~labeled_mask).sum()), + "control_label": settings.control_label, + "perturbed_label": settings.perturbed_label, + # Full pre-gating arrays so the plot can show the dropped dead-zone band. + "scores_all": scores.astype(np.float64), + "ref_all": ref.astype(str), + } return out diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py index 5dd5e7914..8c3978a5b 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py @@ -91,6 +91,13 @@ def test_build_witness_labels_separates_control_and_perturbed(): assert (col[is_ctrl_well] == "control").mean() > 0.95 assert (col[is_pert_well] == "perturbed").mean() > 0.95 + # Gating diagnostic is attached for the summary-PDF plot. + assert "witness_score" in labels.obs.columns + assert set(labels.obs["witness_ref"].unique()) <= {"control_well", "perturbed_well", "other"} + gating = labels.uns["witness_gating"] + assert gating["n_labeled"] == labels.n_obs + assert len(gating["scores_all"]) == gating["n_total"] == adata.n_obs + def test_build_witness_labels_dead_zone_drops_ambiguous(): """A positive dead-zone drops the lowest-|score| cells (the ambiguous C/3 cluster).""" From 0a17c20704206d997de2f2ca6ea011c185856872 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 15 Jul 2026 14:20:12 -0700 Subject: [PATCH 25/89] feat(dynaclr): filter-based witness references + annotation-scored eval Two related extensions to the witness LC label source: 1. Filter-based references. WitnessLabelSource gains control_filter / perturbed_filter (arbitrary obs filters) as an alternative to control_wells / perturbed_wells. obs_filter_mask supports scalar (==), list (isin), and range dicts ({lt,le,gt,ge}, one bound = half-line, two = window), with a well/fov_name key routing to the well-prefix match. This enables contrasts like early-vs-late timepoints or control-vs-perturbed-at- late as weak-label sources. Validator: wells XOR filter per side, no mixing. 2. Annotation-scored eval. WitnessSettings.eval_annotations lets a witness run (weak labels for training) be scored against real infection_state joined from annotation CSVs when the embeddings obs lacks the column, instead of the perturbation proxy. _join_eval_annotations does the per-experiment join. Tests: obs_filter_mask forms + filter-based late-window integration (8 green). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/dynaclr/evaluation/evaluate_config.py | 81 ++++++++++++++----- .../linear_classifiers/orchestrated.py | 51 +++++++++++- .../linear_classifiers/witness_labels.py | 69 +++++++++++++++- .../linear_classifiers/witness_labels_test.py | 57 +++++++++++++ 4 files changed, 234 insertions(+), 24 deletions(-) diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index 7e76803a3..5ade5269b 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -173,38 +173,70 @@ class TaskSpec(BaseModel): class WitnessLabelSource(BaseModel): - """Control/perturbed well spec for one experiment, used to weak-label via the MMD witness. + """Reference spec for one experiment, used to weak-label via the MMD witness. The MMD witness function scores each cell by how much it looks like the - control distribution (``control_wells``) vs the perturbed distribution - (``perturbed_wells``). Those scores are then gated into discrete - pseudo-labels that replace annotation-CSV labels for the classifier. + control reference (witness group X) vs the perturbed reference (group Y). + Those scores are then gated into discrete pseudo-labels that replace + annotation-CSV labels for the classifier. + + Each side (control / perturbed) is defined in one of two mutually exclusive + ways: + + - **Well-based** (``control_wells`` / ``perturbed_wells``): the simple case, + matched against ``obs["fov_name"]`` by path prefix (``"C/1"`` matches + ``"C/1/000000"`` but not ``"C/10/..."``). + - **Filter-based** (``control_filter`` / ``perturbed_filter``): an arbitrary + obs filter (``col -> scalar | list | range-dict``), enabling contrasts + like early-timepoint vs late-timepoint or control vs perturbed-at-late. + A range-dict uses ``{lt, le, gt, ge}`` (one bound = half-line, two = + window); a ``well``/``fov_name`` key routes to well-prefix matching. + + Provide wells XOR a filter for each side (both sides must use the same + style). Cells matched by neither side are dropped (never silently labeled). Parameters ---------- experiment : str Experiment name matching obs["experiment"] in the embeddings zarr. - control_wells : list[str] - Well ids (e.g. ``["C/1"]``) whose cells form the control reference - (witness group X). Matched against ``obs["fov_name"]`` by path prefix, - so ``"C/1"`` matches ``"C/1/000000"``. - perturbed_wells : list[str] - Well ids whose cells form the perturbed reference (witness group Y). - Cells in neither list are dropped (never treated as perturbed by - default), so dead/empty wells do not contaminate the reference. + control_wells : list[str] or None + Well ids for the control reference (group X). Well-based style. + perturbed_wells : list[str] or None + Well ids for the perturbed reference (group Y). Well-based style. + control_filter : dict or None + obs filter for the control reference (group X). Filter-based style. + E.g. ``{"well": "A/2"}`` or + ``{"perturbation": "infected", "hours_post_perturbation": {"ge": 24}}``. + perturbed_filter : dict or None + obs filter for the perturbed reference (group Y). Filter-based style. """ experiment: str - control_wells: list[str] - perturbed_wells: list[str] + control_wells: list[str] | None = None + perturbed_wells: list[str] | None = None + control_filter: dict | None = None + perturbed_filter: dict | None = None @model_validator(mode="after") - def _validate_wells(self) -> "WitnessLabelSource": - if not self.control_wells or not self.perturbed_wells: - raise ValueError(f"{self.experiment}: control_wells and perturbed_wells must both be non-empty") - overlap = set(self.control_wells) & set(self.perturbed_wells) - if overlap: - raise ValueError(f"{self.experiment}: wells appear in both control and perturbed: {sorted(overlap)}") + def _validate_refs(self) -> "WitnessLabelSource": + control_styles = (self.control_wells is not None) + (self.control_filter is not None) + perturbed_styles = (self.perturbed_wells is not None) + (self.perturbed_filter is not None) + if control_styles != 1 or perturbed_styles != 1: + raise ValueError( + f"{self.experiment}: each side needs exactly one of wells / filter " + "(control_wells XOR control_filter, perturbed_wells XOR perturbed_filter)" + ) + if (self.control_wells is not None) != (self.perturbed_wells is not None): + raise ValueError(f"{self.experiment}: mix of well-based and filter-based sides is not allowed") + if self.control_wells is not None: + if not self.control_wells or not self.perturbed_wells: + raise ValueError(f"{self.experiment}: control_wells and perturbed_wells must both be non-empty") + overlap = set(self.control_wells) & set(self.perturbed_wells) + if overlap: + raise ValueError(f"{self.experiment}: wells appear in both control and perturbed: {sorted(overlap)}") + else: + if not self.control_filter or not self.perturbed_filter: + raise ValueError(f"{self.experiment}: control_filter and perturbed_filter must both be non-empty") return self @@ -258,6 +290,14 @@ class WitnessSettings(BaseModel): whose ``eval_against`` value is missing/``unknown`` or not in the map are dropped from the evaluation. Default: ``{"control": "uninfected", "perturbed": "infected"}``. + eval_annotations : list[AnnotationSource] + Optional per-experiment annotation CSVs to join onto the cells before + scoring, supplying the ``eval_against`` column when the embeddings obs + does not already carry it. This is how a witness-labeled run (weak + labels, no annotation for *training*) is still scored against + *ground-truth* infection labels. Joined via the same + ``load_annotation_anndata`` (fov_name/id or fov_name/t/track_id) as the + annotation path. Empty = rely on an existing obs column. Default: ``[]``. """ label_column: str = "witness_state" @@ -269,6 +309,7 @@ class WitnessSettings(BaseModel): marker_filters: list[str] | None = None eval_against: str | None = "infection_state" eval_class_map: dict[str, str] | None = {"control": "uninfected", "perturbed": "infected"} + eval_annotations: list[AnnotationSource] = [] @model_validator(mode="after") def _validate(self) -> "WitnessSettings": diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py index 783057884..30b067143 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py @@ -147,6 +147,47 @@ def _build_labeled_adata( return annotated_parts[0] if len(annotated_parts) == 1 else ad.concat(annotated_parts, join="outer") +def _join_eval_annotations(combined: ad.AnnData, annotations: list, col: str) -> ad.AnnData: + """Join per-experiment annotation CSVs onto ``combined`` to supply ``obs[col]``. + + For each ``AnnotationSource`` whose experiment is present, loads the CSV and + maps ``col`` onto the matching cells (via ``load_annotation_anndata``). Cells + with no annotation keep NaN. Used to score a witness-labeled run against + ground-truth infection labels the embeddings obs does not already carry. + + Parameters + ---------- + combined : ad.AnnData + The labeled AnnData (obs must carry ``experiment``). + annotations : list of AnnotationSource + Per-experiment CSV specs. + col : str + Task/column name to pull from each CSV into ``obs[col]``. + + Returns + ------- + ad.AnnData + ``combined`` with ``obs[col]`` populated where annotations matched. + """ + values = pd.Series(np.full(combined.n_obs, np.nan, dtype=object), index=combined.obs.index) + for src in annotations: + exp_mask = (combined.obs["experiment"] == src.experiment).to_numpy(dtype=bool) + if not exp_mask.any(): + continue + ann_path = Path(src.path) + if not ann_path.exists(): + raise FileNotFoundError(f"eval annotation CSV not found: {src.path}") + sub = combined[exp_mask].copy() + try: + sub = load_annotation_anndata(sub, str(ann_path), col) + except KeyError: + click.echo(f" eval_against {col!r} not in {ann_path.name} for {src.experiment!r}, skipping join.") + continue + values.loc[sub.obs.index] = sub.obs[col].to_numpy(dtype=object) + combined.obs[col] = values + return combined + + def _evaluate_witness_against_annotations( pipeline: Any, combined: ad.AnnData, @@ -185,7 +226,15 @@ def _evaluate_witness_against_annotations( col = witness.eval_against class_map = witness.eval_class_map - if col is None or class_map is None or col not in combined.obs.columns: + if col is None or class_map is None: + return None + + # If the ground-truth column is not already in obs, join it from the + # configured annotation CSVs (per experiment) — this is how a witness run + # trained on weak labels is scored against real infection annotations. + if col not in combined.obs.columns and witness.eval_annotations: + combined = _join_eval_annotations(combined, witness.eval_annotations, col) + if col not in combined.obs.columns: return None truth_raw = combined.obs[col].to_numpy(dtype=object)[idx_val] diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py index 3b40f22ba..4f33a15cf 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py @@ -62,6 +62,64 @@ def _matches(fov: str) -> bool: return stripped.map(_matches).to_numpy(dtype=bool) +_RANGE_OPS = { + "lt": lambda s, v: s < v, + "le": lambda s, v: s <= v, + "gt": lambda s, v: s > v, + "ge": lambda s, v: s >= v, +} + + +def obs_filter_mask(obs: pd.DataFrame, filter_dict: dict) -> np.ndarray: + """Boolean mask of rows matching an obs filter (AND across keys). + + Each ``col -> spec`` entry contributes a condition; a row is kept only if it + matches every entry. Spec forms: + + - scalar → equality (``obs[col] == spec``); + - list/tuple → membership (``obs[col].isin(spec)``); + - range dict → any of ``{lt, le, gt, ge}`` combined (one bound = half-line, + two = window), e.g. ``{"ge": 24, "le": 36}`` → ``24 <= col <= 36``. + + A ``well`` or ``fov_name`` key routes to :func:`_well_prefix_mask` so wells + are just another filterable column. + + Parameters + ---------- + obs : pd.DataFrame + The AnnData ``obs`` table. + filter_dict : dict + Mapping of obs column name to a scalar / list / range-dict spec. + + Returns + ------- + np.ndarray + Boolean mask, shape (len(obs),). + """ + mask = np.ones(len(obs), dtype=bool) + for col, spec in filter_dict.items(): + if col in ("well", "fov_name"): + wells = spec if isinstance(spec, (list, tuple)) else [spec] + mask &= _well_prefix_mask(obs["fov_name"], list(wells)) + continue + if col not in obs.columns: + raise KeyError(f"obs_filter column '{col}' not found. Available: {list(obs.columns)}") + series = obs[col] + if isinstance(spec, dict): + unknown = set(spec) - set(_RANGE_OPS) + if unknown: + raise ValueError( + f"range filter for '{col}' has unknown ops {sorted(unknown)}; use {sorted(_RANGE_OPS)}" + ) + for op, val in spec.items(): + mask &= _RANGE_OPS[op](series, val).to_numpy(dtype=bool) + elif isinstance(spec, (list, tuple)): + mask &= series.isin(list(spec)).to_numpy(dtype=bool) + else: + mask &= (series == spec).to_numpy(dtype=bool) + return mask + + def build_witness_labels( adata: ad.AnnData, witness_labels: list[WitnessLabelSource], @@ -98,9 +156,14 @@ def build_witness_labels( exp_mask = (obs["experiment"] == src.experiment).to_numpy(dtype=bool) if not exp_mask.any(): continue - fov = obs["fov_name"] - control_mask |= exp_mask & _well_prefix_mask(fov, src.control_wells) - perturbed_mask |= exp_mask & _well_prefix_mask(fov, src.perturbed_wells) + if src.control_wells is not None: + ctrl = _well_prefix_mask(obs["fov_name"], src.control_wells) + pert = _well_prefix_mask(obs["fov_name"], src.perturbed_wells) + else: + ctrl = obs_filter_mask(obs, src.control_filter) + pert = obs_filter_mask(obs, src.perturbed_filter) + control_mask |= exp_mask & ctrl + perturbed_mask |= exp_mask & pert X_all = adata.X if isinstance(adata.X, np.ndarray) else adata.X.toarray() X_ctrl = X_all[control_mask] diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py index 8c3978a5b..773c579d8 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py @@ -15,6 +15,7 @@ from dynaclr.evaluation.linear_classifiers.witness_labels import ( _well_prefix_mask, build_witness_labels, + obs_filter_mask, ) @@ -171,3 +172,59 @@ def test_run_linear_classifiers_witness_falls_back_without_annotations(tmp_path) results = run_linear_classifiers(zarr_path, config, tmp_path / "out") assert results.iloc[0]["eval_source"] == "witness_label" + + +def test_obs_filter_mask_forms(): + """obs_filter_mask supports scalar, list, range window, and well-prefix routing.""" + obs = pd.DataFrame( + { + "fov_name": ["A/1/0", "A/2/0", "A/10/0", "A/2/1"], + "perturbation": ["uninfected", "DENV", "DENV", "DENV"], + "hours_post_perturbation": [3.0, 26.0, 8.0, 30.0], + } + ) + # scalar equality + assert obs_filter_mask(obs, {"perturbation": "DENV"}).tolist() == [False, True, True, True] + # list membership + assert obs_filter_mask(obs, {"perturbation": ["uninfected"]}).tolist() == [True, False, False, False] + # two-bound range window + assert obs_filter_mask(obs, {"hours_post_perturbation": {"ge": 24, "le": 36}}).tolist() == [ + False, + True, + False, + True, + ] + # well key routes to prefix match (no A/10 leak) and AND-combines with others + assert obs_filter_mask(obs, {"well": "A/2", "hours_post_perturbation": {"ge": 24}}).tolist() == [ + False, + True, + False, + True, + ] + + +def test_build_witness_labels_filter_based_late_window(): + """Filter-based refs: control well vs perturbed well at late timepoints only.""" + # control well A/1 (early t), perturbed well B/2 spanning early→late hours. + adata = _make_separable_embeddings(None) + # Give the perturbed well a real hours gradient so a late window selects a subset. + is_pert = adata.obs["fov_name"].str.startswith("B/2").to_numpy() + hours = adata.obs["hours_post_perturbation"].to_numpy().astype(float) + hours[is_pert] = np.linspace(3.0, 36.0, is_pert.sum()) + adata.obs["hours_post_perturbation"] = hours + + labels = build_witness_labels( + adata, + [ + WitnessLabelSource( + experiment="exp_A", + control_filter={"well": "A/1"}, + perturbed_filter={"well": "B/2", "hours_post_perturbation": {"ge": 24}}, + ) + ], + WitnessSettings(dead_zone=0.0), + ) + # Only late (>=24h) B/2 cells are eligible for the perturbed reference; the + # early B/2 cells are not in either reference, but still get scored+labeled. + assert labels.n_obs == adata.n_obs + assert set(labels.obs["witness_state"].unique()) == {"control", "perturbed"} From b6015a9fde1d5cb2a5e7c96f8bf342ae680931c5 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 15 Jul 2026 15:41:52 -0700 Subject: [PATCH 26/89] fix(dynaclr): witness ROC/F1 plots use annotation-scored eval, not witness label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The leakage fix recomputed the metrics CSV against ground-truth infection_state but left the ROC + F1-over-time pages drawing from the trainer's witness-label val arrays — so the summary PDF showed a circular AUROC=1.000 while the metrics bar/CSV correctly showed ~0.80. _evaluate_witness_against_annotations now also returns the annotation-scored y_val / y_val_proba / classes / val_hours (aligned to the has-ground-truth subset), and the orchestrator swaps them into the plotting outputs so the whole PDF is consistent. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../linear_classifiers/orchestrated.py | 50 ++++++++++++++----- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py index 30b067143..0690b1b1a 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py @@ -216,11 +216,15 @@ def _evaluate_witness_against_annotations( Returns ------- - dict[str, float] or None - ``val_*`` metrics (accuracy, weighted_f1, auroc, per-class f1) computed - against the mapped ground truth. None when evaluation is not possible - (no ``eval_against`` column, no class map, or no val cell has a usable - ground-truth label) — the caller then keeps the witness-label metrics. + tuple[dict[str, float], dict] or None + ``(metrics, val_outputs)`` where ``metrics`` are the ``val_*`` scores + (accuracy, weighted_f1, auroc, per-class f1) computed against the mapped + ground truth, and ``val_outputs`` holds the annotation-scored + ``y_val`` / ``y_val_proba`` / ``classes`` so the ROC + F1-over-time + plots match the metrics (not the self-referential witness label). + None when evaluation is not possible (no ``eval_against`` column, no + class map, or no val cell has a usable ground-truth label) — the caller + then keeps the witness-label metrics and plots. """ from sklearn.metrics import f1_score, roc_auc_score @@ -247,6 +251,11 @@ def _evaluate_witness_against_annotations( X_full = combined.X if isinstance(combined.X, np.ndarray) else combined.X.toarray() X_val = X_full[idx_val][keep] y_true = truth_raw[keep] + # hours-post-perturbation for the kept val subset (F1-over-time plot), aligned + # to y_true so lengths match. + val_hours = None + if "hours_post_perturbation" in combined.obs.columns: + val_hours = combined.obs["hours_post_perturbation"].to_numpy()[idx_val][keep] # Pipeline predicts witness class names; translate to annotation vocabulary. y_pred_witness = pipeline.predict(X_val) y_pred = np.array([class_map.get(p, p) for p in y_pred_witness], dtype=object) @@ -260,17 +269,26 @@ def _evaluate_witness_against_annotations( for cls, f1 in zip(classes, per_class): metrics[f"val_{cls}_f1"] = float(f1) - # AUROC needs the positive-class probability mapped to the annotation positive. + # Probability of the annotation-positive class, ordered to match `classes`, + # so the ROC/F1 plots are drawn against the ground truth (not the witness + # label). Column order of y_val_proba follows sorted(classes). pos_witness = witness.perturbed_label + pos_truth = class_map.get(pos_witness) pipe_classes = list(pipeline.classifier.classes_) + val_outputs: dict[str, Any] = {"y_val": None, "y_val_proba": None, "classes": classes, "val_hours": val_hours} if hasattr(pipeline, "predict_proba") and pos_witness in pipe_classes: pos_idx = pipe_classes.index(pos_witness) - proba = pipeline.predict_proba(X_val)[:, pos_idx] - pos_truth = class_map.get(pos_witness) + proba_pos = pipeline.predict_proba(X_val)[:, pos_idx] y_bin = (y_true == pos_truth).astype(int) if len(np.unique(y_bin)) == 2: - metrics["val_auroc"] = float(roc_auc_score(y_bin, proba)) - return metrics + metrics["val_auroc"] = float(roc_auc_score(y_bin, proba_pos)) + # Two-column proba aligned to `classes` (neg, pos) for the plotters. + neg_col = 1.0 - proba_pos + proba_2col = np.column_stack([neg_col, proba_pos]) + if classes[1] != pos_truth: # sorted order put pos first — swap columns + proba_2col = proba_2col[:, ::-1] + val_outputs = {"y_val": y_true, "y_val_proba": proba_2col, "classes": classes, "val_hours": val_hours} + return metrics, val_outputs def run_linear_classifiers( @@ -439,10 +457,16 @@ def run_linear_classifiers( # trained pipeline against ground-truth annotations on the val cells. eval_source = "annotation" if config.label_source == "annotations" else "witness_label" if config.label_source == "witness" and idx_val is not None: - anno_metrics = _evaluate_witness_against_annotations(pipeline, combined, idx_val, config.witness) - if anno_metrics is not None: - metrics = anno_metrics + anno = _evaluate_witness_against_annotations(pipeline, combined, idx_val, config.witness) + if anno is not None: + # Swap in the annotation-scored metrics AND plotting arrays so the + # ROC / F1-over-time pages match the CSV (not the circular ~1.0 + # witness-label score). val_hours comes back aligned to the kept + # (has-ground-truth) subset. + metrics, anno_val_outputs = anno eval_source = config.witness.eval_against + val_hours = anno_val_outputs.pop("val_hours", val_hours) + val_outputs = anno_val_outputs row = { "task": task, From 401a38a584259ae3782f621dcae035299d45fd2f Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 15 Jul 2026 15:59:10 -0700 Subject: [PATCH 27/89] feat(dynaclr): per-marker eval target for witness classifiers The witness axis measures how much a MARKER's embedding changes between the references, so its meaning is marker-dependent: viral_sensor -> infection, organelle markers (SEC61B/TOMM20/G3BP1) -> remodeling. Scoring every marker against infection_state was wrong for the organelle markers. WitnessSettings.marker_eval maps a marker to {eval_against, eval_class_map}, overriding the top-level target for that marker (absent markers fall back). _resolve_witness_eval applies the override per run; eval_source in the summary records the actual target used. Lets one run score viral_sensor vs infection_state and SEC61B vs organelle_state. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/dynaclr/evaluation/evaluate_config.py | 11 ++++++++ .../linear_classifiers/orchestrated.py | 28 +++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index 5ade5269b..626fb83e5 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -298,6 +298,16 @@ class WitnessSettings(BaseModel): *ground-truth* infection labels. Joined via the same ``load_annotation_anndata`` (fov_name/id or fov_name/t/track_id) as the annotation path. Empty = rely on an existing obs column. Default: ``[]``. + marker_eval : dict[str, dict] or None + Per-marker override of the eval target. The witness classifier measures + how much a *marker's* embedding changes between the references, so its + biological meaning is marker-dependent: viral_sensor → infection, + organelle markers (SEC61B/TOMM20/G3BP1) → remodeling. This maps a marker + to ``{"eval_against": , "eval_class_map": {...}}`` so, e.g., + viral_sensor is scored against ``infection_state`` while SEC61B is scored + against ``organelle_state`` in the SAME run. A marker absent from the map + falls back to the top-level ``eval_against`` / ``eval_class_map``. + Default: None (single target for all markers). """ label_column: str = "witness_state" @@ -310,6 +320,7 @@ class WitnessSettings(BaseModel): eval_against: str | None = "infection_state" eval_class_map: dict[str, str] | None = {"control": "uninfected", "perturbed": "infected"} eval_annotations: list[AnnotationSource] = [] + marker_eval: dict[str, dict] | None = None @model_validator(mode="after") def _validate(self) -> "WitnessSettings": diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py index 0690b1b1a..83f9bbc00 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py @@ -188,6 +188,26 @@ def _join_eval_annotations(combined: ad.AnnData, annotations: list, col: str) -> return combined +def _resolve_witness_eval(witness: WitnessSettings, marker: str | None) -> WitnessSettings: + """Apply a per-marker eval override from ``witness.marker_eval``, if any. + + The witness axis means infection for viral_sensor but remodeling for + organelle markers, so a marker may be scored against a different obs column. + Returns a copy of ``witness`` with ``eval_against`` / ``eval_class_map`` + replaced by ``marker_eval[marker]`` when present; otherwise returns + ``witness`` unchanged. + """ + if not witness.marker_eval or marker not in witness.marker_eval: + return witness + override = witness.marker_eval[marker] + return witness.model_copy( + update={ + "eval_against": override.get("eval_against", witness.eval_against), + "eval_class_map": override.get("eval_class_map", witness.eval_class_map), + } + ) + + def _evaluate_witness_against_annotations( pipeline: Any, combined: ad.AnnData, @@ -457,14 +477,18 @@ def run_linear_classifiers( # trained pipeline against ground-truth annotations on the val cells. eval_source = "annotation" if config.label_source == "annotations" else "witness_label" if config.label_source == "witness" and idx_val is not None: - anno = _evaluate_witness_against_annotations(pipeline, combined, idx_val, config.witness) + # Resolve the per-marker eval target: the witness axis means infection + # for viral_sensor but remodeling for organelle markers, so a marker + # may score against a different obs column (e.g. organelle_state). + witness_eff = _resolve_witness_eval(config.witness, marker_filter) + anno = _evaluate_witness_against_annotations(pipeline, combined, idx_val, witness_eff) if anno is not None: # Swap in the annotation-scored metrics AND plotting arrays so the # ROC / F1-over-time pages match the CSV (not the circular ~1.0 # witness-label score). val_hours comes back aligned to the kept # (has-ground-truth) subset. metrics, anno_val_outputs = anno - eval_source = config.witness.eval_against + eval_source = witness_eff.eval_against val_hours = anno_val_outputs.pop("val_hours", val_hours) val_outputs = anno_val_outputs From b769bc0bb6be676094deb6200f60ff7b1e869b8f Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 16 Jul 2026 17:06:10 -0700 Subject: [PATCH 28/89] feat(dynaclr): witness-GMM annotations replace the witness label source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the sign+dead-zone witness gate and its circular annotation grading with a two-stage, annotation-format pipeline (Soorya Pradeep's recipe, framework-ized). A witness->GMM label IS an annotation: its meaning is named by the modality it is computed from (viral_sensor -> infection_state; organelle -> organelle_remodeling_state), so it flows through the existing annotation training path unchanged. Stage A (new `dynaclr witness-gmm-labels`): witness score -> per-condition 2-component GMM -> writes an annotation file (named state column + real class vocabulary). Negatives = all control-well cells; positives = perturbed cells with GMM posterior >= threshold; unimodal (near-noise) markers skipped. Stage B: the existing `run-linear-classifiers` (label_source: annotations), untouched. Teacher/student is free — point Stage B at a different modality's zarr (e.g. SEC61 labels -> train on phase), joined by cell key. - New shared `viscy_utils.evaluation.witness_gmm.fit_gmm_labels` (+ tests). - Rename witness_labels{,_test}.py -> witness_gmm_labels{,_test}.py; recipe -> witness_gmm_labels_infectomics.yml. - Remove WitnessSettings, _gate_scores, _evaluate_witness_against_annotations, _resolve_witness_eval, _join_eval_annotations, the label_source: witness branch, and the superseded witness_score_classifiers DAG + visuals. - New DAG witness_gmm_classifiers.md; update evaluation.md + LC README. - Validated: fit_gmm_labels reproduces Soorya's saved GMM on 2026_03_24_A549_SEC61_ZIKV (bandwidth 238~240, remod weight 0.655~0.649, confident-pos 59.1%~58.4%). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...linear_classifiers_witness_infectomics.yml | 73 ---- .../witness_gmm_labels_infectomics.yml | 61 ++++ applications/dynaclr/docs/DAGs/evaluation.md | 8 +- .../docs/DAGs/visuals/mock_witness_plots.py | 133 ------- .../DAGs/visuals/mock_witness_score_hist.pdf | Bin 27452 -> 0 bytes .../DAGs/visuals/mock_witness_score_hist.png | Bin 58276 -> 0 bytes .../docs/DAGs/visuals/witness_dataflow.dot | 52 --- .../docs/DAGs/visuals/witness_dataflow.pdf | Bin 41944 -> 0 bytes .../docs/DAGs/visuals/witness_dataflow.png | Bin 230645 -> 0 bytes .../docs/DAGs/visuals/witness_gating.dot | 30 -- .../docs/DAGs/visuals/witness_gating.pdf | Bin 39358 -> 0 bytes .../docs/DAGs/visuals/witness_gating.png | Bin 66884 -> 0 bytes .../docs/DAGs/visuals/witness_pipeline.dot | 23 -- .../docs/DAGs/visuals/witness_pipeline.pdf | Bin 35886 -> 0 bytes .../docs/DAGs/visuals/witness_pipeline.png | Bin 47573 -> 0 bytes .../docs/DAGs/witness_gmm_classifiers.md | 114 ++++++ .../docs/DAGs/witness_score_classifiers.md | 236 ------------ .../dynaclr/docs/linear_classifiers/README.md | 7 +- applications/dynaclr/src/dynaclr/cli.py | 8 + .../src/dynaclr/evaluation/evaluate_config.py | 189 +++++----- .../linear_classifiers/orchestrated.py | 301 +--------------- .../linear_classifiers/witness_gmm_labels.py | 337 ++++++++++++++++++ .../witness_gmm_labels_test.py | 181 ++++++++++ .../linear_classifiers/witness_labels.py | 267 -------------- .../linear_classifiers/witness_labels_test.py | 230 ------------ .../src/viscy_utils/evaluation/witness_gmm.py | 129 +++++++ .../viscy-utils/tests/test_witness_gmm.py | 54 +++ 27 files changed, 993 insertions(+), 1440 deletions(-) delete mode 100644 applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml create mode 100644 applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_dataflow.dot delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_dataflow.png delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_gating.dot delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_gating.pdf delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_gating.png delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_pipeline.dot delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_pipeline.pdf delete mode 100644 applications/dynaclr/docs/DAGs/visuals/witness_pipeline.png create mode 100644 applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md delete mode 100644 applications/dynaclr/docs/DAGs/witness_score_classifiers.md create mode 100644 applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py delete mode 100644 applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py delete mode 100644 applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py create mode 100644 packages/viscy-utils/src/viscy_utils/evaluation/witness_gmm.py create mode 100644 packages/viscy-utils/tests/test_witness_gmm.py diff --git a/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml deleted file mode 100644 index 4906daa3a..000000000 --- a/applications/dynaclr/configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml +++ /dev/null @@ -1,73 +0,0 @@ -# Linear classifier settings for the infectomics benchmark — WITNESS label source. -# -# Second option to `linear_classifiers_infectomics.yml`: instead of loading -# per-cell labels from annotation CSVs, this derives weak labels from the MMD -# witness score. For each marker, the witness is fit on control-vs-perturbed -# cells pooled across all listed experiments, every cell is scored, and the -# scores are gated into control/perturbed pseudo-labels. No annotation CSVs -# are needed — only which wells are control and which are perturbed per -# experiment. -# -# The control/perturbed wells below are TEMPLATES — edit them to match each -# experiment's plate layout (mock/uninfected wells → control_wells; infected -# wells → perturbed_wells). Cells in neither list are dropped, so dead/empty -# wells never leak into the perturbed reference. -linear_classifiers: - label_source: witness - witness_labels: - - experiment: "2025_01_28_A549_G3BP1_ZIKV_DENV_G3BP1" - control_wells: ["B/4"] # mock / uninfected - perturbed_wells: ["C/2", "C/3"] # ZIKV / DENV - - experiment: "2025_01_28_A549_viral_sensor_ZIKV_DENV" - control_wells: ["B/4"] - perturbed_wells: ["C/2", "C/3"] - - experiment: "2025_01_28_A549_Phase3D_ZIKV_DENV" - control_wells: ["B/4"] - perturbed_wells: ["C/2", "C/3"] - - experiment: "2025_07_24_A549_G3BP1_ZIKV" - control_wells: ["C/1"] # mock (pseudo-control well) - perturbed_wells: ["C/2", "C/3"] # ZIKV - - experiment: "2025_07_24_A549_SEC61_ZIKV" - control_wells: ["A/1"] # mock well for the SEC61 layout - perturbed_wells: ["A/2", "A/3"] - - experiment: "2025_07_24_A549_viral_sensor_ZIKV" - control_wells: ["C/1"] - perturbed_wells: ["C/2", "C/3"] - - experiment: "2025_07_24_A549_Phase3D_ZIKV" - control_wells: ["C/1"] - perturbed_wells: ["C/2", "C/3"] - witness: - # One classifier per marker; None (omit) = every unique obs["marker"]. - marker_filters: - - G3BP1 - - SEC61B - - Phase3D - - viral_sensor - label_column: witness_state - control_label: control - perturbed_label: perturbed - # Drop the lowest-|score| 10% as unlabeled (analog of the annotation - # path's `!= "unknown"` filter). Set to 0.0 to label every cell by sign. - dead_zone: 0.1 - # None = median heuristic on the pooled (control, perturbed) reference. - bandwidth: null - # Bound kernel cost: subsample each reference group to at most N cells. - max_reference_cells: 5000 - # Score the trained classifier against this ground-truth obs column on the - # val split, NOT the witness label — the witness label is a deterministic - # function of the embedding, so scoring against it is circular (~1.0). Set - # to null to fall back to witness-label metrics (flagged eval_source). - eval_against: infection_state - eval_class_map: - control: uninfected - perturbed: infected - use_scaling: true - use_pca: false - split_train_data: 0.8 - random_seed: 42 - # Track-level split — no track lands in both train/val. Same rationale as - # the annotation recipe (temporal-contrastive SSL leaks on cell-level splits). - split_groups_by: - - experiment - - fov_name - - track_id diff --git a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml new file mode 100644 index 000000000..a19cc654c --- /dev/null +++ b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml @@ -0,0 +1,61 @@ +# Witness-GMM annotations → linear classifiers — infectomics benchmark. +# +# TWO STAGES (see applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md): +# +# Stage A `dynaclr witness-gmm-labels -c ` +# Scores cells with the MMD witness, gates each perturbed condition with a +# 2-component GMM, and writes an ANNOTATION FILE (a named biological-state +# column with the real class vocabulary). A witness→GMM label IS an +# annotation — from viral_sensor it is infection_state; from an organelle +# marker it is organelle_remodeling_state. One marker/microscope per config. +# +# Stage B `dynaclr run-linear-classifiers -c ` +# The EXISTING annotation path — point `annotations:` at the Stage-A output +# file and `tasks:` at its label_column. Same-modality trains on the labeled +# zarr; teacher/student points `embeddings_path` at a different modality. +# +# The control/perturbed filters below are TEMPLATES — edit per plate layout. +# Cells in neither reference are dropped, so dead/empty wells never leak. + +# ─────────────────────────── STAGE A ─────────────────────────── +witness_gmm_labels: + experiments: + - experiment: "2025_07_24_A549_viral_sensor_ZIKV" + embeddings_zarr: "/path/to/2025_07_24_A549_viral_sensor_ZIKV/embeddings" + control_filter: {perturbation: uninfected} + perturbed_filter: {perturbation: [ZIKV], hours_post_perturbation: {ge: 18, lt: 24}} + # From viral_sensor the witness axis means infection. + marker_filters: [viral_sensor] + label_column: infection_state + class_map: {positive: infected, negative: uninfected} + gmm_pos_threshold: 0.8 + bandwidth: null # median heuristic on pooled (control, perturbed) + max_reference_cells: 5000 + condition_column: perturbation + output_path: "/path/to/output/infection_state_witness.csv" + +# For an organelle marker, run a SECOND Stage-A config with: +# marker_filters: [SEC61B] +# label_column: organelle_remodeling_state +# class_map: {positive: remodel, negative: noremodel} + +# ─────────────────────────── STAGE B ─────────────────────────── +# Train through the unchanged annotation path on the Stage-A output. +linear_classifiers: + label_source: annotations + annotations: + - experiment: "2025_07_24_A549_viral_sensor_ZIKV" + path: "/path/to/output/infection_state_witness.csv" + tasks: + - task: infection_state + marker_filters: [viral_sensor] + use_scaling: true + use_pca: false + split_train_data: 0.8 + random_seed: 42 + # Track-level split — no track lands in both train/val (temporal-contrastive + # SSL leaks on cell-level splits). + split_groups_by: + - experiment + - fov_name + - track_id diff --git a/applications/dynaclr/docs/DAGs/evaluation.md b/applications/dynaclr/docs/DAGs/evaluation.md index f29316c37..2a7d09f43 100644 --- a/applications/dynaclr/docs/DAGs/evaluation.md +++ b/applications/dynaclr/docs/DAGs/evaluation.md @@ -1,5 +1,9 @@ # Evaluation DAG +This document assumes a preprocessed dataset and a cell index parquet already +exist. For the upstream stages (new dataset → find-Z + normalize → build parquet), +see [end_to_end.md](end_to_end.md). + This document describes the **per-run** evaluation pipeline (one model on one dataset). For the cross-model, cross-dataset matrix layout — including the central linear-classifier registry that lets Wave-2 datasets fetch LC @@ -128,8 +132,8 @@ configs/viewer.yaml # nd-embedding viewer config (also valid input │ -c linear_classifiers.yaml # reads per-experiment zarrs directory + annotation CSVs │ # joins annotations on (fov_name, t, track_id); trains one LogisticRegression │ # per (task, marker); marker_filters omitted → auto-discovers all markers - │ # label_source: witness → weak-label from the MMD witness score instead - │ # of annotation CSVs (control/perturbed wells). See witness_score_classifiers.md + │ # witness→GMM weak labels: produce an annotation file upstream with + │ # `dynaclr witness-gmm-labels`, then train it here. See witness_gmm_classifiers.md │ # writes trained pipelines to linear_classifiers/pipelines/ (in-run staging) │ # if publish_dir is set: atomically promotes the bundle to the central │ # LC registry as {publish_dir}/vN/ and updates the `latest` symlink. diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py b/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py deleted file mode 100644 index 2c01f4f67..000000000 --- a/applications/dynaclr/docs/DAGs/visuals/mock_witness_plots.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Generate MOCK example plots for the witness-score LC DAG doc. - -Synthetic (illustrative) data only — matches the plot types and Wong palette -produced by the real orchestrated.py so the doc shows what outputs look like. -""" - -from pathlib import Path - -import matplotlib - -matplotlib.use("Agg") -import matplotlib.pyplot as plt -import numpy as np - -OUT = Path("/home/eduardo.hirata/repos/viscy/applications/dynaclr/docs/DAGs/visuals") -WONG = ["#0072B2", "#E69F00", "#009E73", "#CC79A7", "#D55E00", "#56B4E9", "#F0E442"] -rng = np.random.default_rng(7) - -MOCK_TAG = "illustrative — bar/ROC use real run values; hist/F1 synthetic" - - -def _mock_note(fig): - fig.text(0.99, 0.01, MOCK_TAG, ha="right", va="bottom", fontsize=7, color="#B00020", style="italic") - - -def save(fig, name): - """Watermark ``fig`` and write it to the visuals dir as PNG + PDF.""" - _mock_note(fig) - fig.savefig(OUT / f"{name}.png", dpi=150, bbox_inches="tight") - fig.savefig(OUT / f"{name}.pdf", bbox_inches="tight") - plt.close(fig) - print("wrote", name) - - -# 1) Witness score distribution + gating bands (motivates the labels). -def witness_score_hist(): - """Mock witness-score histogram with control/perturbed humps and dead-zone band.""" - ctrl = rng.normal(1.4, 0.7, 4000) - pert = rng.normal(-1.4, 0.7, 3600) - scores = np.concatenate([ctrl, pert]) - t = np.quantile(np.abs(scores), 0.10) # dead_zone = 0.1 - - fig, ax = plt.subplots(figsize=(7, 4.2)) - bins = np.linspace(-4, 4, 60) - ax.hist(ctrl, bins=bins, color=WONG[2], alpha=0.7, label="control-well cells (X)") - ax.hist(pert, bins=bins, color=WONG[4], alpha=0.7, label="perturbed-well cells (Y)") - ax.axvspan(-t, t, color="gray", alpha=0.25, label=f"dead-zone (|w|≤t, t={t:.2f}) → dropped") - ax.axvline(0, color="k", linewidth=0.8, linestyle="--") - ax.set_xlabel("witness score w(z)") - ax.set_ylabel("cell count") - ax.set_title("Witness score distribution & gating — witness_state (marker=G3BP1)", fontsize=11) - ax.legend(fontsize=8) - fig.tight_layout() - save(fig, "mock_witness_score_hist") - - -# 2) Per-marker metrics bar chart (mirrors _plot_metrics_bar). -def metrics_bar(): - """Mock per-marker AUROC/accuracy/weighted-F1 bar chart (mirrors _plot_metrics_bar).""" - # Representative values from a real 2D-MIP-BagOfChannels infectomics run, - # scored vs ground-truth infection_state (eval_against). Strong where the - # marker carries infection signal (viral_sensor, SEC61B), near chance where - # it does not (Phase3D, G3BP1) — the useful discriminating signal. - markers = ["G3BP1", "SEC61B", "Phase3D", "viral_sensor"] - auroc = [0.554, 0.838, 0.536, 0.815] - acc = [0.491, 0.764, 0.580, 0.865] - wf1 = [0.388, 0.768, 0.466, 0.861] - metrics = {"AUROC": auroc, "Accuracy": acc, "Weighted F1": wf1} - colors = ["#0072B2", "#E69F00", "#009E73"] - - x = np.arange(len(markers)) - width = 0.8 / len(metrics) - fig, ax = plt.subplots(figsize=(max(6, len(markers) * 1.5), 5)) - for i, (name, vals) in enumerate(metrics.items()): - ax.bar(x + i * width, vals, width, label=name, color=colors[i], alpha=0.85) - ax.set_xticks(x + width * (len(metrics) - 1) / 2) - ax.set_xticklabels(markers, fontsize=9) - ax.set_ylim(0, 1.05) - ax.axhline(0.5, color="gray", linewidth=0.8, linestyle="--", label="Random (0.5)") - ax.set_ylabel("Score") - ax.set_title("witness_state — performance vs infection_state (per marker)") - ax.legend(fontsize=9) - fig.tight_layout() - save(fig, "mock_metrics_bar") - - -# 3) ROC curves (mirrors _plot_roc_curves, binary control/perturbed). -def roc_curves(): - """Mock per-marker one-vs-rest ROC curves (mirrors _plot_roc_curves).""" - fig, ax = plt.subplots(figsize=(6, 5)) - ax.set_title("ROC — witness_state vs infection_state (per marker)", fontsize=11) - aurocs = {"G3BP1": 0.554, "SEC61B": 0.838, "Phase3D": 0.536, "viral_sensor": 0.815} - for i, (marker, target_auc) in enumerate(aurocs.items()): - # Build a smooth ROC with roughly the target AUROC. - fpr = np.linspace(0, 1, 200) - k = np.interp(target_auc, [0.5, 1.0], [1.0, 12.0]) - tpr = fpr ** (1.0 / k) - ax.plot(fpr, tpr, color=WONG[i % len(WONG)], linewidth=1.8, label=f"{marker} (AUROC={target_auc:.3f})") - ax.plot([0, 1], [0, 1], "k--", linewidth=0.8) - ax.set_xlabel("False Positive Rate") - ax.set_ylabel("True Positive Rate") - ax.set_xlim([0, 1]) - ax.set_ylim([0, 1.05]) - ax.legend(fontsize=8, loc="lower right") - fig.tight_layout() - save(fig, "mock_roc_curves") - - -# 4) F1 over time (mirrors _plot_f1_over_time). -def f1_over_time(): - """Mock per-class F1 across hours post-perturbation (mirrors _plot_f1_over_time).""" - hours = np.arange(0, 49, 6) - fig, ax = plt.subplots(figsize=(8, 5)) - # control: high, flat; perturbed: rises as phenotype emerges post-infection. - control_f1 = np.clip(0.9 - 0.02 * rng.standard_normal(len(hours)), 0, 1) - perturbed_f1 = np.clip(1 / (1 + np.exp(-(hours - 18) / 5)) * 0.9 + 0.05, 0, 1) - ax.plot(hours, control_f1, marker="o", color=WONG[0], linewidth=2, label="control") - ax.plot(hours, perturbed_f1, marker="o", color=WONG[1], linewidth=2, label="perturbed") - ax.set_xlabel("Hours post perturbation") - ax.set_ylabel("F1 score") - ax.set_ylim(0, 1.05) - ax.axhline(0.5, color="gray", linewidth=0.8, linestyle="--") - ax.set_title("F1 over time — witness_state (marker=G3BP1)") - ax.legend(fontsize=9) - fig.tight_layout() - save(fig, "mock_f1_over_time") - - -if __name__ == "__main__": - witness_score_hist() - metrics_bar() - roc_curves() - f1_over_time() diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.pdf deleted file mode 100644 index fb84bce976bd1d80ccc98c26606dd04b214a0174..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27452 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�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 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png b/applications/dynaclr/docs/DAGs/visuals/mock_witness_score_hist.png deleted file mode 100644 index 5d234612ad2b2c9b594cbfbe63bbf6d0a7e8e096..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58276 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 linear classifiers — data flow (per marker)>; - fontname="Helvetica"; - fontsize=14; - - emb [label="embeddings/{experiment}.zarr\n(AnnData: X, obs=experiment/marker/fov_name)", fillcolor="#DCE9F5"]; - pool [label="POOL cells for this marker\nacross all listed experiments", fillcolor="#EAF3E1"]; - - subgraph cluster_ref { - label="build references from wells (fov_name path-prefix match)"; - style="rounded,dashed"; color="#9A9A9A"; fontname="Helvetica"; fontsize=10; - ctrl [label="X = control_wells cells\n(e.g. C/1)", fillcolor="#CFE8CF"]; - pert [label="Y = perturbed_wells cells\n(e.g. C/2, C/3)", fillcolor="#F5D6D6"]; - drop_ref [label="cells in neither well set\n→ dropped (no leakage)", fillcolor="#EDEDED", style="rounded,filled,dashed"]; - } - - fit [label="FIT witness bandwidth\n(median heuristic on pooled X,Y\nunless bandwidth set)", fillcolor="#EAF3E1"]; - score [label="SCORE every cell\nw(z) = mean k(z,X) − mean k(z,Y)", fillcolor="#EAF3E1"]; - gate [label="GATE scores → pseudo-labels\n(sign + dead-zone band)", fillcolor="#FBEFD6", shape=box]; - train [label="train_linear_classifier\n(logistic regression, group-aware split)", fillcolor="#EAF3E1"]; - evaln [label="EVALUATE on val split vs\nground-truth annotations\n(eval_against, e.g. infection_state)\nNOT the witness label — avoids circular ~1.0", fillcolor="#F3E1EE", shape=box]; - - subgraph cluster_out { - label="outputs (identical to annotation path)"; - style="rounded,dashed"; color="#9A9A9A"; fontname="Helvetica"; fontsize=10; - metrics [label="metrics_summary.csv", fillcolor="#DCE9F5"]; - pdf [label="witness_state_summary.pdf", fillcolor="#DCE9F5"]; - joblib [label="pipelines/{task}_{marker}.joblib\n(+ manifest.json)", fillcolor="#DCE9F5"]; - reg [label="publish_dir/vN/ + latest\n(central LC registry, optional)", fillcolor="#DCE9F5", style="rounded,filled,dashed"]; - } - - emb -> pool; - pool -> ctrl; - pool -> pert; - pool -> drop_ref [style=dashed, color="#9A9A9A"]; - ctrl -> fit; - pert -> fit; - fit -> score; - pool -> score [style=dashed, label="all cells"]; - score -> gate; - gate -> train [label="labeled subset"]; - train -> evaln; - evaln -> metrics [label="val metrics vs annotations"]; - train -> pdf; - train -> joblib; - joblib -> reg [style=dashed, label="if publish_dir"]; -} diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf b/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.pdf deleted file mode 100644 index 954b46c0e0f38f7239d22047257c73b0e6f68478..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41944 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 fGxR5m|749hB265SCo2pDsplUy diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.png b/applications/dynaclr/docs/DAGs/visuals/witness_dataflow.png deleted file mode 100644 index e88f1a476b22a837bf231e808ba8ad3d749e3743..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230645 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�pA zQVRFRIeN4He0a5vrPsOUkrB*5wVQSNLU7FTOo>HDxNlXh=VNHn^y*yaL0)*6pphix zB{u;KKjNO|tH+0lpIr}+G)vtdC=N-run6a-_Fd1dDRi5%e@)RI1U%?CfT|CLZi}%a|d}=tWnSEI@c~heK6{ip* z{`$3MyR*S&BoiZBt1ioHbE1^|0^ki7yHasj9(cSZzr#4W5@QF*kJ}SxXdUrFt-o9X zoImR^jFGd(H#L2w9xhZ8466_lJlbyUG{d}6S?_ajha zc1u22y(58J=2c}gB_uI+`TA{De!zXemHbyA0Df9?5>8+pY(3YihLNiqIKP@VuD{=3 z-dZh)#}aBfskhtQYi`^%<0d@l_<%k@L{4ZjF!jJwd-M90C)j2CD=kOu-*}v6@6j=n zObiG=->4qY`8hV0xIWK3;bp;(PfD6#vMbSYcIbwKGJehzH$j}vW8*1|jU7eK7Ech~ zY?xPebYrEx@B2SONA@N4O9abi$4k->PvqIn%jIa6>LZy1aPI$#RVIk^mGEYf!E34X z6UzpXgb+m7ICZvPZ%;C4h;K%LX52f*a!EldYtx}gdB;hW&dwzTOFv!&k_yL4Dc_#} z(g$B({ytMBy`=%2eBEa58T$^&iMiFbk9_=(+@dnYpBcI9qED3GAu?c{sj088hp4PrYlVoWbS90o?n;G4l*5_9(Ju+1DX+2%uE2hej ziea68gkwsle)jtn)y_hZU*ON+LL)R!QQiopXP+2VRZTTBdQVt zTi!<=(~WY!hTD^hsl}N9YNGX(x%og4u_GJQXxWD)8x5eK%*HZ(O(>@I-B1h4YiEm^3 zy$)W4ygWJD%DCHybobT?IJ7e?=`=h^QBItdY=%lJ`{QY{aqLB;h&+>vn_(ah;NUo* z?3FsM5&;=S#J8tILz5^sZ{3o$k8qKd!8rCx=fPmq3F;zj?&%Z|Ex0YKm^Gh0Hu{lW zkMA@06SV?U(%h=Ee9`4*Ea5htwh1R++*`Zrv7cb>9A-z{=3|6SWwNM%rq+<4QOx7* zDTqJqOX{}+li3Q9@ZZv&>OXV8osl}sPfjL&=0P51 zx2s+!O}&0*%reTqZ!}WJX20CUJ7~r!>I9~>m@kvwGvui2*0DJS1o__fWu zqm~CO>k@|Z**+AroQ06OafQ*XSDPip%Fw!2b%jK>KCk~K%(-*>m4@TZk{1|vd|0~5 zbwbeDzNChPkPALK?oYUSy}XtG)UErx(rGF|ex2WRaXvn5>sIT;%uF4a2_C0464Cbr z&Aofb`Lwvms);-+G!Ah&t?Pav{h{_lZA;h3?Bm@ZCGB1&Oiqkaz7^D`5bSB)G znvK?`+cCPQRqmue^ATq%p|goUHfr7+lNXEko>npX#`->zt~4b7t7YHIC2`5$@G6^8 zd>%x_#}^9*MQ!6$9&3r8z}tu^Hmtg)+U%V+4>{Ju&GdlOva#g)_b4}QH^(!6M`bp4 z8_AwuNnr~s&MnmcrIIwQ^MzH8H{t8EY`p8Yy#fHF3$<0H(c=A1-%Ku>5Nmg(u}Xt1 z$rE^xo;CZ^*;d>zaky;)ib+~aH4YPxD0r~3-bZBS6iLrCSe zqhvQXtQ#i`KYjP2co*!9W0mASQsqI{_2SaAo<{IIx{5_Hax|-2TF=dDOZ*M+BAS~n zP8m_X^v7y^h;PCdEfAM`=&!M%hdd$)is##FMrv^o4mp<}HUiL43gIoOUfrkTp8@hT z4t8Rr{)_ySmeX-RDawfLyp)56%a-#c>(e2JfB^#&Vo&be8-G$%exT38a^m5+#!?yY z)vc4rK}?kiO*>FtaM*Xuf+<-oB)|&~ymr)w28J<3T2e-C@oZ5!z$g3{5H(HKemE9U@ zTll8#U^=lH&N7ylp2T)8E)o>0yxF`?x*qQp`pcUa^XThgz??Ol92Fg}7{86H*em>X zTQtHPt_P}#R=>l(spO#EOlED)-mkqSaYg8aBH!(!&|}pu26v@uD@g;`tKzLM5QqQFb$hqs<37(z~Hb zqGz4ysN#WlP5UtBNChdR`=M)C3Ji?N_LlU_cE_J`TZ(%ho0{&y-60~P{!tl9MVC;F zJL9FTJGvzge~DhBaUH4cN%&;90a;<{-1fk#_p+Ko_xCEdT@2sYG1AeYJxo?y*t!g#!H@ToTB z3qx0)P$79pYBeyp8`gH0I{W%)T8A8LY$i=rpH}FA3gjg#4C$+8N?G^T#te%`xqM0% zT+D|bF`@ZPj!gJ+ojjRZCK=Lsjtx*6pueXgdg$x7^z<+Xa%5UYIoAlzrF@IqVz>n@ z3G|ed2!_#|*xj36-jB>|E^O9G&!|G*9C;ApDzkF8(bkS5DZo$SqoKV`-b(q>O8lA0 zK}?rQv&U@%@$Iw*@@4K#{cuO~(ZV3e(4SnpGjn){h~vQLENk#P`_Fs*;;CsN3@X_o zXP1OlMk|wC#u;U-g;LhPrY6~$mI!DXO#oh3b51+lvQnza|NGKnDWM;IQ%bSq1`Vi^ zXE>dUnU_6JgKkt?=l;-4mN5{v+#D|`<}X-_vMeF2E0K7h8Px(_#zDr7p5Up zP{m05G9?n2_7z{w9x9&L_?dFDIV@IeIn;&eMI)6^%Hw+5UbBy7?eS#M4ACwF z2X$OT?;YlgU0hIESVHWJLL(VRRs3a9m#=NQQ-9YwWE4@^Re>iV%ab< z)I5~J`Ilb2OfzgjBh=(`zBncZ{GVH-$ z+Z(pK;721(qr$GZ?8pRHyrKAw@_Dv{nFOuptX^z)e4eoK+RQJZZYx1H((>M-Dwxy=L zYf94S`O4**0hUDt-$7ZU9c9+*B=@PQ77dr)0f;kOmK3xwsO?bxEFSj`KZRuSBbHI~AHp zs&=GP(%H-;t{A_m-Ue@BmhUv1L$Q$veut-PM19Ux0d0!Ge%BJC(PX2l{>y{7O8_kM0 zo3BD^F+#dmi++F$A461>sj!lfjIu-e8y{|(9LICneotucf0BVogT5^0PBjxI!@`XDnc2J7aCQm`Uzjea`Bs`TflxN7akHKr4Kv;o0_JcRov{ zWa1Ll%tY0^l;bdcYxbM&r-u3a&x_qngpO@F*Kr6t272bUb_+{4JC0}ejUd=-IMpmQ zrD+I0_xtQ4mJd@;&t|iHfaJ zS9_!OcaeqW7GqZ%z$WGjXmgr%MIdHCdHA+T@Moo%u&iN%hvj?O_a@@V;q-&|q?oQP zYvjohiK*jZ8S3WY^pyPhNtxfg&dWg9^ec2|q5-!)tBjMGk5s(_@l^Xl>K!vKgFJ~4 zix;p#A$`hlJA?b%(S1Q=E`@6N^cq#1@HoRn0E^cQ zCmf@FcR#eXooh3>NKegt{#z;mKS4hK4{zWM@20SI4^cGXKU_yLoYZ0$39V4Nw*SgE z;hpj_4P25*;6bzE_7XbU2*;}5h5U`l?k|SBnN$z%M`r~6h!cij)7Vv`_}8;eR{o~U zeIzAs9+Faob`vBZlx{rrLu*A>9v%2)8vF!#dafZz^z}FC<|mO4sTA4jSCt$}zRiaD zPdUTLqMY9iz8a%~-%1Amy@i3v!ROx##{BqZLrUF4Q@+CG$BX)r^pG*2xu)_-D**eX zVk%lBcd*<|*ve^Hx<2f|W~*ZS{TEBD`U{QVY4SfB+%%vAZQ41c)6a3(Y9g?z5}bb8 z(p4^gUc$~_y!vJP+bI2{4qHwYoHuLw8QbJlav|r4ALYBLZHmepyYBD9MDK4(eG6vP z*(wli(I~5Cq|ewTRdfsS87CEk@ldyW4Pc$#-(e)qZTpNSz5vOw7m1m#brPqPn6&~x zU0NS@M^aC#Lk!eT{NuCgtL_LU?Z{B_z)8=#Q~OZ^(;?yS>o+e8yhmmk74!;o$+$@)}U3w^XQ?lhk5Rz4}TC5!x~6bQ-E1x#(zBX&JKr|IC)3Y%~yVl*V? z4R4fZx36KCbnZ zhc8qZ%Jo!!DL#c1LHBmTnoJUVKxc!@r$;HSHnUFW1&!njf3$e*4`7#bb}5riP6T_uh)EGNo$~yLe%jG zRC2sqw?xx(NDt<9|GATspu5_D0-2ur%@n$WofH;4i{%03<%N;e2Xa@)XEqi`vD~Z4 zVQTC~GxL!8E!$maTdN($tnv&iB1ZeFS%mlb^N)q0Nv5?C%z$ske(yd9lw|55&Z zz9~tw!X-U@#)zW1f7Lr7WFWd!)5 z*uBp5O8N=B$rmY$l|d0m1E8O6V=!rO&o}H6BicgDz)*7-nGj^LAotJVzH>7;X}r__ zI>y040WKs>Rl-ti@x%M~eBF6IP>P-o7MQVAXAj?00nceJG>l64t{8aR{>8g}Ut&t! zN(x@q3)m5Roy#S-KETAmNtX{fvq@3t>7mQRbDIz6aG1h+e~abUP!KA*7H`8;)YO9Wto%&$I7p!P)){rJz$B|Cuo=Y0o*(c6E+_@5|Io2d7X_gvj{g=a}NACMSb`Hb2xme~d-C zNe1l#fRiq#nQsbG%1T6KaxBF_>%Pki26H;1$Nzrc{XE^x9}vXgeRqG*)rBd(IolrX z+esVvb5xM~uwomT7p)iX4Q~jEJ0LQ@r}PI05&_8XrUuCA!G(hC?)QyjQJY;7R~}`j z-yfVd8D^w|NrHilVq@!DHnP{(2jl=?CVp)zY9%*IKf~?BbZdZ+oN#fhfMJ?8`13(e zJve3e6t+y}I+7lNC(7eCNAisq(D2xrv2v zx}6;IE+|PJ0*P>!a49NxZaty=tJV+Ngfd5od?q&xx7!$upStXBv|B{DzC*GX5(-{$ z8=ybCrM$QnuIgX8-OR>w_rT;(AX5nd1OhlZxlC#pNQY3Zff*ty`{-3{(YNW9DVg@6 zK9}v)9262kcY#`uyRC!uF_C7sd%oLv*v{I{g3s}BcU1`rVoF?RiROKnsoijN2Rq~8 zg#BT@xI(6uZr9=T{igS*;^?Rq99wJMaSF&$$3U87%iSkRv(om4_l9puF4svFrZ-t- z8O_($+z%{BC~9j2)XvzL9RpvLgGhMKv@+-f1OzPKn^!-=bovCOag(b;RonjOx3DXPwYpSrdgvO6~)_h-it?jN-eb%;^8EJm(*g99!6 zzO481p6kx@CjkUk z+9ZEBrgE=cxv?AuS*HpE$k%~?Y;4KEIP26Jmc$FDIZsPH1-TE+A4$}5yL-GDZ9aZv zoJ_@rD#OMG{aN^U=3@k@=B3>xmW{bje>w~6T)jri&i-D&WxI|>flMU>sEj%idG9!7 z$yfha*zQNU#BD`qz3m9};28?v?zgLRrna}T&`5NDDRP&3Hmsx8jJNN6I&{NwtOWC6 zd{D^3SZ%ij*3Mas!>Q7{~;?0 zhoDH?D1@$^ipKMJ1ZLwJiNRs=U}x z$Sj135VlQu<|yVqfA8Sz>`LuA@*vuUD*HRX69)l4wfnqcn5Hl-mlPlUUH{=``oqq> zY017i)qkzL^uHO15L*7^6b>O%>w^znu%|tr6a;DI&tJdx+{Aa-UpwW$JUlp9k*59M z^A6#uDmlKGsp8y3t|JMy$r9(Njr1j}RP*AFzEBIUHeMU4NJt`A zGqbfm*UK8OxIZ|fDonGJC_Ydan2POWd8dCV0jd8Rp+8RQqQ5IBhm*=6A3TFQMP z#(Gz>E(Qm(ne3vPC6u7FELQySJ7epePhBqqk39?xe>v=@AwmCl2>MZxOg_q7`zghv zLrCr2kNEoPN1T`DV^&_cMD6{me9Jon--w!r9GC3pkAiu$-!q5CsM}@!$XztE7Q@e@ zk7Q(PfCF?H#m&vvK2l!J^@xGt%RJfYYP;8Jpo<}s0u(M0ab{xFl*rT44n2ok?oGIc z?)@&0!b)iS`@4b%ijn67x<`jE0#s~|C$Ba~d`b84&La<@TNrV9Pq1tJtGDw|cgAgw zuR%`6(RTx`eDGa4ILGoJ2D|OPkB=f7$1Uk3(;ua5L$`-`rO`zDWX!K{k1L@poO00j zZ^;0qj?T8kuYjKvcQex=dIVnybzxvxP%eqD7A~E6JXffnTnW})s`CmDfkXz%&PorG z{q$5Xc>|s}#mp28n!k#C3{_!HheI-2Xhqcr%9eqB*2BW;dzNHKrpJiQ*T z6~EZ^jaWHTnUYfA^`lcEiF{}{{9MtUN#*9aUi&g|2K?5Ft!LsO;c4cF-JBL<)T16@ zq=up+{*~fUCvB->8<)5Dbyxnqbos0NpD97z5*(&qZ$Aa}W+^g_?n3n%E2KS<%dDVk ztF-5-?VBCdy>mblu75B)Ulh;d`5yA=PyZK5j~vLVxA*2&%Q#@&ARd^n4H=v738|VW z=iNTE>^O0ruiSEGb2qTVY zn8DhJCBe@2tiJWt8>U?KkZq7@!Ue36x<8xbZ1%q;o?~R`In=~D?;@V1#~w9^UI_p5 zJX;qr5B5NXS|PomncQg$PPhXXC9&h#0^dBNETpi|ic@UMa(&<5-~V%K9BGDPu90R; zGe<*Di5y3M@dq28e&evBj_50=(MJ9P^>PV7OZOLjTSQV^^;} zH^BMqZG*F`3uK<27OLX9?yml9ST*~qz~IULX9vP}u@JU6u$G&Xi$}A z^pItH3;p^X<~k^Fx#)3dq7pN^sP6pIR9owVM7wVs#*P!K{VsFr`ajNMGvm@KXm0qZ|2(|pivEh3jAjofu|e4A*Fmet zfkYZgjDFn&H=knaKO*=dkz30;zwaw-B>K=;cjDPt?);=nSZTD|m|bUE{b5__ysGLy z{0ta2#`rVAPegi?^$j|v9gS`?GBUDSj_{ODL&`=$Q1yZ=EBj+?tUeUcqIm0^S+M|< z8RGS*zf;d{$T=UrjsjGHS7c;mUtF+u?M~{$5{rB(>h6A4vC}PmkD0k)n?>;QdGqva zu!upKzWyG{oko2Kz;A8zP?+tv$qb6TD_dF^Wzq2m=+NY7oT9< zx)n(%;LLjI(xn*~SwsY<<}rY#;Vz1VQCIBtYp`-~)YjJ-t{hd5Bmdva_}FvACqUAx zrmYRjs&e->wPYAS(gq@ka{U6nlLH)agf%COTJJn@9M|zAa0HTNJ4-UE1lVVe=x&1;{u3T9FzF;3JKGd4uzi$E)k=T4kLLOAC zo{aBlmDnByj-%8hO#?dWAX0QLmQl_4jL2`*Zs91r!f}{AVp-S z17S}V)4v$7@*WFIBe~bf=ev0>lfN!2DJ%C4HI9ss!1oBb%n3m+phKPTJ%Xd8X0Z$< z{o}pWKb3CWncu!qNa6`p`u_CVa>lYJtr>pgF9DpVnq?nePmBMu=DLsl_$nb0k$!)c zn)6(YvHel$k%o7ml-HeEe zm>P=Yq^{F>I5LPnV>o}Qr2J!SNC-}%$DSHg$St~$Kqt>6@WP^a!`=dM<8^IqZP-Ka z4ElLGTDTcRy*PFjqN1Xg0SgNkNOgfA6$t86<@9=h0Fk45?$aZmiG_uGibF$y{rKJM z+!Ntvo}Ha7Dppf-61zie_AxFFl>}E*kGD(m^4Q*C-F*$rW(9 zq5XYdVD}yyq5=8gyU;PFOs3C8fvj6xQA`D$aW7sIB zHYzr$w3HW&!f)W-1Rv?gLiWrLt8p=4wS&ME_|vXON?k^Ao(|$>ebEweyA>GadQtO! zLq{IV5X+ zW{J)8738>9tr*Gb9gwP6bjS>XlQ82 zbA*J1fWP=H?7&DD2(&uW7{asJi>|D!Tw=e7r*B|@OH3R9JJzo*Is$;_z}ff?$fltI zW3!1W1P;r?hPvKROp5PNVvX>XgTXsHIs7Q-Ry>lYp8f0BmW=(6zL1if-nQL z!72}appgwC=C*uE7EX=nbLGabg{~Af#}(C%VIW9QBA!PajBe%j_hmLVHhw`t*#+Jo z;B-JK4klc0f@XI-c&qi*0f<)r6q?{e2ZNrjt~WH+*xU%N0<}u`E5*lE0u)bYglM4Z3D;3xex%acW#krl$1a5!#A>+B!ON zicZHazp1FG_+4bC5)+2kmkV}rc_0@$Qqc*nU5;W@yQHI|Q)*Cz`S>dG^1Qsf*4EZF z1|fp9FlY@}TJV=%0g*5j8yhY#XBVsji+%mU=JX301dWIPHiEjomj3*k4b;@Pe{Ba% ztE8f0Rt%)fwb1#nZT>9oDI7iMzM+8gLQ6}lT5Nt7)bwm{Um5`cf$l(#7GnN^f)t^f zETzKzTv9>^FMkyW!0k0js!sPbI>zDDk&<3$&4;VQUBE&AR9#)&tQ^)85|FIWW)n!r zcFD-dh@FO}TU$~R6Ph)GXJ!mP#>7zY@DKv@gKeW}ILLv`+6XT~QUScTGttDonGsP@ zw(6JRTUvRJ( zuth4}k2H$={Fwp>MmYnnZaUhVw!&aH2?*W<24Vq?Bhoo)ZEqjJe;c=mQuyqE3WD4T zNI{5hgBh=!@B>%Mv>M!iu&^*hbu26_@TG9AuC6|y;_}5Ny#@yiwt7+C)s^q{>(?LR z;sSsaT)#c`!9w4+=#f0XueazK8S6rX&-}p0f>qL6`IGmN$A%K@!!MRY7(#aXVhELM z*REN^nn7xeii_*^ix)3qE%uoyIZb=-fu(Ey7$I>E=ytdyB!S7vln@x}!VvL}ul$zo z-Mfbn^w~_+`K#+(+FKdKKRY}7$f|d#`X36)Y2VaDGN{}nCl7&)IV3tU z_s>iU0NiEH}gRtcPl9cjFTh@}M6_Rb9P>G{8@tjRGCf zkH3FEjpuWaD!7?MEeVv#^}x|QIXzuljja9i?}*yAkOzC)ktCd3N+Za^0`Mkb)I00r zs1~54d1(pyAk} zpg^xp;9ZU@0k@f6KmHlh3CpHDO@a)_CGe7h0`>s0e8a=URN}!i*=4<-p~H1DTwdsv zRO;&=Ke&Mpy5+5?pM-HkLj$3(r$9ppc}PWt0KCODpscTlZatghJ&W7C{;(T=H#`yy za%g;fAk<$WWQU;gi;9PbhD@JJkh2?J74}x}wV$X#H6N?E`}pd?;h|04E!;&d0H4py z%wz>Z~&sB-is$#C3jUh0zHe4o&ujmC6SPvO~cQHGHt2e~&HY@7Qb!t{Fc?p=RGj}}XYy+K90qTBmb0of67Na+loCHDj4 zEJU^Xor!`Us4=0nxDm9}Y_KQ_ycHt)@QY8dUbc-CVamYP17%D%fUTH05ITXF-rFJX zTJSB#3h*&V(&dvH68N1shq17*kfv?KFoN7KKoer1q`x`~SaoOuqks(tUmsYTDWDCV z(uwit9FXXdHv=)#1&@=JlQV)JFVq|~=WC!hhcJ(1x*@0*D7JxwoW%*~S543VN?dz# zMFj~`mS;DI1-&}H`T6YaTwos|KcK&}&_&3mj}HEap^Q}T5tu4HU~~rS<^A|`4K(mg zEH5)SF_!s0%Mg)}xC*wF4YVBmu!Z!(6@FoNW>9BJ&$OCb1v%AHs`5(NWdLlS@#*LljHj@9$T`5r;M zB=oHByhma@)7aOqFTqinfF+TTknomD?KTIqSqsrLtLx57EJ9XqDjq+3rDxAx!}+4( z<-H4<8}#YB^78VLyaiOSg@`QyFYXo)h`xFC>J`MDhS8y+uTGEmr$INw@(Vzl|2yFQ z)m`e%h;Kw*_AN4Ux0ak7HnP&7PU88Ua{8qfA=3yU=qosHWG*wfcNOu9EdDpzdj~oa zZe7L1eB|Q7i>xTDH&~-0B2aof&Kv)L4oiv29R@dPxY%NvlM6BdIEaT6>IjP4da0Kl zh_#8LS#9omrDKB98R~{%2Ev-VA!ouOcz0L(Z!$&wYIjl z)^lxl-}w4Ycc)4hy^jMA1kox1aU!l!iHX55(#+gkGWepZ1x7c<4J>|x-*0_->2Zy)t;D-N3*L#3--M{a{pN3Gf5-lSu5h_t+Q$n(33uPuD*(1p; zq9h6-Ss4vwE0H8CtIUvH_MXrA?*9Fc=Qy6{_rH(hKEB=G_sv|4vywtsgO>*k=X>*)bp8NJK@}F=dV0v*$32~k*0OUv5p>PDwe|4=*8}XT= z&m5hcFghR%3_4ENu?=~G{YykMPcJXxC4_Zy+HCs&#dW9MeK(P|$C|tZQaA)Y0QlYy3kr|AO`pHSqD}>93oXynI(kWrH>Q6pUhT}M z`>)g>x-ReDyNBrHu7RR+cJ>C#uU{nG z16s*9p$a_yf^+nTtgP%LrZ>n@+yWZL86dtK>p?**M1^DGvnGsSK*A{>H(kdIh5$D? zoDjBc$cSfeBZx5WI>ipN2=Rij@oV3)NVs1@W9H)%*7QIvm(MH8lXA_gsL|uQi*(eo z?!dPvCshf@fL$P#IQmC((Nf01$hesVKu#nX6k|0{(SCTj0`Z4#LV^%HTAd35{~F5*UX~k$$i~Asq3p2H+q~1!(LO8tLxh%yPMO zE!jl1U%`jsKWM5-R2GB<2Z?bL=(RnGz{}XZcyPFvq(O%rJ9ZF9z9UY?$HzZ?6Oy)D z{r?No&Cs<=EJB-^*KgiTwOlkgh!;r0;Rc}>O}uuaoF#aWB*xGGZXr>N*8!XLfRy)v zSFc|E!wwiCkN*6RJw2@WilfJmBSg@3{a>ItI3qp;>&4N`ocj+RY`G&#B*dhDNe>m> zqc?sv?(}qdX2LVqSn!-oO-)e;H(mAk9ii}RVj^6D)r$c;2Cq4ZJUuP#Lxy%H5El)= z%MpWjRQUFPx8Xu;aJ1fI3vW4l_ACKiouv=pkhkXTDXFRbKVF#=F$z)VL^vQvIj-B= z+k3dH=slkQx&1gxT({Iy6l{j6g+&0yy5tuX5ibD$q)GGvN+z`?y?jXp(0A(QpFIF! z;jt5mHiKPHfe7)|4=P4}Q^d#oUm`QsioE<^Mgle*rG=|y0!+)SnGB&xc%|Vmq)geksajV*dih#qFdrF49*^#GD}TIhmlBL!MhHXqfQw= zMSQT`zJDK_?=(b>?%XiBK?V5o5 z{GVVPul)>_ykk|8&N+%(=u)7OqInNYOQ8ABVaz+s&CO#p>D4vyhD<9% zc4KASe)C31C)|KkOqq&(%de8PQhw6cs+o*(6} z?Qq9?!6BJL>^9{8w*5Ax6nYeCL}Kjg?-vetL9)hzczpcIcKa(_HVP2Ka&P|C2WBQd zyCzyNFeWAjOu;?kRUw?H>FNdn#sY$UVGd6Fl#hGpb{k?Q2E4?%bl(`o^O(ZPPJ*RIT!Rx8eZVGNsMAS0_2E(C}lk@YRkq<@L!*3+1 z@et5t72V7P3xG3RcucYo zb$^f+_y2?5r9ss7%Oax!AflZzGf$} zv#>Zlqc{M|c;?^IesLi#gmM@6^(nd}>Pwga@T0)#5a|+{n;xIEkN*~&R`TV` zmk;Y}De2Z2Bwl55-gQl_WW3wujmzTF(@=G^|hd-pCP zlT6{Kl2OT%6zu7pD?h*(#&4~{mK4b$s04`;? zJ&#Xv>Gv)HHDqHE;+t-iLFG9h;m&|-mO<1YNk?)brkMk8?h&#;0YP>7f}%Ws`v1G@wd++g*m46=EAxxD9z3nN9zG zhxwg4)-wPO8z-l{6aTRjC*<*h2ofI;j|_ED5CoAM`-R(=3(W8p$?qZ(62g&T42y2= zeDvs%?kh7g9CJL|9w6}FMclSqtp{wngfkQnM!`}DcO)61a8TG-Uwbm}1F+r4?p?#r z5)T)UKjl!wuv0;_c@M7_j<)3_T&UD~_fh1>*)e-$=q*qQJ~t*S2y^T_!q0!qtbzh- z0EEEHYP-&ytav{Bq?qG=Jv}`lE1W&M?dsL5xd@u%-W2v(_5YslN0D~h{YV>Xyivqs zgU*7B{WUroav@G^#FMBMrP}{wjbW1#uK}w$6eW*zmd=*^mGDU{0JGJ_DK(zBmxAVu%ZP)hGZk)q=d>_rXw!-LC~-%4%)(T zi>gv<{vgRn8wL_y&_EG7ABi5M%M7u3o^3u&Bwl!jTS=vMYM#55dets2Ej*+4^l6{D4~cwV*#8VX8j@(0{1egwA1x8+0(2BvRZ=yG&NRe%4ttGxp;`LjNLPKW&~8_>Rvgw;aY=`Qh*S${dJy z6m<2@1;kM>W>8M#64fq!f3Aojcn|of>GBSek&zLvZHl;6#7h#|%Ki1;SbRj?fdzH^ z5ad~G)U7=}8xsDs6jW3d7&en{MV3bi`30=$9isqVdZ1SvmkYk zWFQ(#OG`qHaCUvoASMW!5LD_G+y}HK%ZLX-ENQv9v={+c4yqCueG>`F=VKvDYLuH- zE-1*`1^@4L#W_p40IH93V{HUM2fi-keeSMmPADP;1qF+L-qhz>H*rJAW{=bh?dfqF zs$&LNBay(Zoif?3zN>h#FBD7`){Xn=L6h}qrgM@0cqFyvsuFVyoQeqZ#X42QRp8IS z+Yer&HZlX}fW*v#><{4_l{Ka#id=$z63cGs>UxS06~HSIrM9J7h@*grUAup&*GdvPv0M8gjG(Xs;*kfj4B~fxTwu_?iGgU7fT*b5gpS?g zy`n;(YG&h5Mlo9*-JI;~v%m+q;MU06UF!SQjg7==kR0_%>o zX5G%r%(O5`(FjJ|BlJP&onNY}d;fZ<;C2deFtr(m2kMr2OCWXrkh%vknLr2Z;On;E z**sCYaxHW3CMIMt(8g$w*B|F`Z|pWn=I=7WSROI#@?6)cKCuoQSA5iS@j6Kw`UgUY z0p~8dwQww6i{%F(EZL-_Oo-Z)*&sA5Qx#=1A`ToNy<=k?kt(|)9+fT)?)RLjJ^A{r z5pIRxqoCTIh2r2_6iOvA*Q^P$DSg3(V9)U0L1gqpDFMT{e`xe7dwf&t0P(N@z~jA_ ztx10pJ`>RFe<{em17}G)JLh#Cn|+bkHVp70*wMq}Wizs_!NY)YC(IVLv3H1iACgo~ zURQ1HPGnsLkY7kEcK{~e>x!@!+JbyPbz_(oRoOpLyY^%ch)X*4z!ozOF+N`26YuBl zu}JKD@!|!4%L%$XM*s!Pl_Qo}>B{(N1nCde)s!+aGK;G-_4|)FZfj#31xjst}OsBY_ejYZKxEFhf?xm!B+C!Wlk7 z7T=_iiNWB175hiT!jc1$k&T~!CzPNE2M?-Ofj%>K8`zC)0hY2KBftphax_=}WdlqN zDu@p^Cc2r!#i!3H3oAF9dcrsZrNp0LiHeISCww*x-u#tuqGh*4k{9Y@qMRX3Vo%ASDg$5irUX$EXgKi66NB4TY1P2X*SZSHe0+Rx=O6K6Fbu1C zH|X7kT?We_PzzorLj5V`$RQn(N`r(l(7i+M8c4VA*5QxUK~ZEg``Gr^?9 zdrnvDhvSn!fv^O>_DSZ9= zK)bQr6YnTaN@58h-2~uiidCVWLZb({Z_!M$j+U1AY%yJgH;nBc+S-^<(saE0FW5y} zfirevhqTT0gwv+Y*tM5zleG9$#jefLaS18P(c2IDKL#63VuR2>dLVhw1>DOWT3NzG zw%Hd<>qCo#Wp62v4zrj?cHxPH`z#@8Z}vsms&V@#Fnq;Z>n3?<-xL%Sw5YO^R1+l2 ztE&|OU_*|Xe{NoR3XB0?5I&Q_#brUPqokw+e|{JhW$jFL(4Vl#Y$eg{*^~A&70*XD zOK0rH(^#jImp=HqFZfJX9}@nFoio~%3WlK%Q|e;PN58KF-e^pClZbDA>v~3F@u|p- zcx0mrl;QiYuH2@POmhl@f~9X+hSDSS#}C~`%#juAF!r3PBp)=+vxy?HVP6&Lhqo0o z7CDb`!&^5vg{MzR>ML33ZW=kzwqTq#am;J>%*~tc?sF=hPmnaqT(7RJZOLGTsJWFi zHJlvYYAqxwX-uL8CT`~KUwi9CLvgSIpzKb1y7g^05MV|_`Gv*T5ZfyNa?aoh5jMI? z!f@m)Wjk{q`iP#!#H;o7g|e~PRh#Vog>_H(6`k9k2w7ezsak4ESbzTho9u~I$+jxz zg3>aq;MN;v&dSRo%|^t>3xFD#b1d$7Zy$=37UJtmqGM*xiZ+L6a6$5F zBiNgfi?!qea&mGY(vN&^YO2KhuLNJmdH3#J>_yP8rFz@rup*O4Y#3LE@yP7*4i2vX zc^F`9iKVfb+by=S*iZ8c3q|FY6L4I4ZLP{vaW{>J(b1up^A_aN<8_fl3u<5&&=Hcj zE;OKEe9I041A|00o(epwgZ{Ma$BwC!XmQI~EnxyxX-k)SP#!rf&swWc8-;5+jU&NA zMYxymJoX4?5k>^h7`(D`k(|Kkl@N@!k}$NI=jKE=|Mbkv8F%->!Kdp>^*+~{XL1o8 zG1(3==_xIeELbpEXvv|N!cIwI=i)k(*`_Tw^fqy37IDfp#9(D%f(Zz;4}?=6))?*f zJF+BN5Wg>-!%#C;V6YVG##_X7ASg$Y*f80c6D$xXR*bx!Ud&=|AS<3*Ib0I(Ae?+$ z@IrcFwn^2N1puBA2`jeETvpc4qN8VH-DQ(Bu;kfPrs67lg1`cW{`qrNU0a(A`a>nQ!5zdv z5?i)OnekYDd+D{()oJD4i3vg2G%JeTW+2%HTv&Tq>vRH$MfuSYx|7#B)1146j~=B3 zDJ&*Y23yK@QeW2lZud(bh*0!ksTlDv%$i=PZ1zQU&|sJo8><%-&@;50FVMEIkdl$1 z1SSB#^TfWC4l)(&qS_<^&$?Urbb55YFuXY1Xi{j{vA-z?3;=4~^FVPYTm^`?hJ9WM zV*JQMr4*f^@H7rS9v)gy{dtRnPweFt6@!h#mj1r)A=d2FSH2unH7WnrZ7T>ln;M(# zadTd$yeCQ)wvt}CPMMW3iPhB95VwEyu#Z5R*V*;oF$wi@$QhkHm#wc8U=Hq(zfqSAC zY6~)Rq@a=ul7hei5n?N7m*i-DMwBs0LO&Ay9RSHdfWMMZ?OA_O+8xMkfZvZ48I+K$_m(k|1!YKy?!aAz)c|pPii^@p!r@ z_n-|vb{{hF?!CiAB7siKV=3D>&{7;mc+Q~pH@CMU3W6Q^_52{Vpd9j)g+Mz{67FDq z(VT~2JE7-%=#nH{yLgphkaR_s=59)P%@J|jR$_y^F^`o1`hXGy#Tx5H;0pMA#Am(< zsTiLcl$p4-vN8o5$B*~+tdc8#go@Xf#|U~6Br}QRtn<$M#~3$^FFqS9J%lVkLg4oY zJ7b*9x8Sif5IrU?0y;sgs&QaR;>&Zz2czPHfBsPY8uwEMj(3ia=2Tn+9L{vIAi0+b zNP$P&(VAlt4vlUVkDnm)CD&#(p})w&L;BX*;%zP^q3)9rcN zKgY&v8z1&}Zrs{OVhAaU?tlx3l%~nnI+#z>VUJpPxU)d$=AR#%P%L6MSA70Vc$)|| z0-V-Xte~v0CANk5R9q@)u58^Ca9nKic|%oozrL;j(Q{gw#v)=&Y^6LM0n z>8I-{vVF_Lp|K))BSJh^lRLVPi5}ef^;o;Da2pm=!YL=K4q<}n^|WIVUzucyhbcMIBDT4mr2*! z+)(d4;Jsi{MS6zWWh7siwa!YEk-vKPE_>$3+R&^i%cCXZ5Mhm&CfsJOL-{65?taI6!}0wiCsbL^n}kTrk2v7`YT0RGP3V#Hip$_w)2wrAHi zWk}u|35@j{L{e7Q(Fs6e+YL?Zx{HgCm4^zK{|BJOSN1)Ur$j~ng(a!=DVo*A{_vpi z@G3aEM%PNnORnkU81Fhh{-l zSw}6cM|}GCs`zq>{lMd6=MahcBg%1S$+PA;eD}O0x|O4`#cuHXHZz>E&8CrWzJb>U zjO0fvJ9FoT=HC7L`(bU(SqNpsTQY{dB#)q=vuAW`hmU}1t<7?@wzkfjudc7RZzk7- zbsjD_b_maqAzQ@bBP0EA+6f`XA`J)T7boR#jL=1Ay>*!B&uy#OHsQlg1gvHeCYvBv=R*k3Ywdc14s~ zdomAu;aDXS3G9yBa4FtY6^jQI*xqu&v5XuG2dJ)KvS^+Ih-Mr^n~Vw)HR9*8^*O=f zg>Dbm(ezZf6R`11w1Q&~Pfv`3REvQ|;eiWsi8VvOSD&p&l z{jmn8olR|QLO>R=pelbXVz&=PYaqz8>N3|$<=&%;`R z^z(^$Hmj?iT3TAah!;W=x)A>3`@*qVx2G#5ljS>LDsV+9=kWUr9ieLp3GG5LgWw|t z{{I(7ylbv72^s?%V-HlJ7=eG504>j-KmUc<=v^Afm7E> zXi_+*to#X-!4yohq=x#x4ei9`H`@q!7RN3n`2rz*0>?mDRf)C%r|lvNtYevi zfIi!#i;}F&mp>vU6KBm3W=&A1pT2ydK(Kz>TL0#-&9yR&r~eFt66`5q2--w`+9Pbk zKrkt!SZl$3^pMUWd6Gzk4-qt;yTY8Xf|L&ibuSJ{Z3M&Df7!yqj2NK}FEuKK$#&bE z5tMw_wATSWS;Uy^G{t$GYQ?kXtg~$olAwZ;*_W+}pm3Zd+C8SKSgTAnmM?;q?gjp~ z`P$SHo1Ie?u&mr+>tRl~bGW$3FuE2y)n?WqC@nkIn$u>`m76_DMHdr8Z6vJORJVzvSMd4@qp28>>#W-k1el-q?uZ6=Bj_H+p#Bf5@Ef z$KDbSeg35J_1Z)1Yf9(N{VQ%@K6%`!4qt(F{-~I`$0W`my3~@Potu|;2lSMurzati z7cY-ylE%CHZo<=t-iIV$Py%j1vWCMkh+IIcnfBmTbTs_x?cMaRd1*W10AX5(dV1}; z41=BXc;{YN=m=d4e(KGTfnhoS`03M@jm0V{m_U{r?(oh!Px2nsj$s8lZCmtEN6k(b?}NLHcyxrA4Z+ArC{{Ci=%iHxnW-Z zg^~nmYF625rzD{>?Zj;zbLf*lVwkTsY8G2b7G4_#MV^`hRo&eoxxFJ{?_l31|}(9Rn7J{@cHwZ9-$xMSu!`vCs>Z-;G){%^KnR zI(&dc6VYPNZn!%#cW*D)MXTPSrlsXifOoAtI ziP+%^S;6#DgM)oVT1G}W+|O`JZ9=LA!wSCfB0T8)>`>l*{`?7(s`d0Me_C4E8^dFi z_^~{b+GW85QA&3Rpi{QKY#W+$L`i=6pg-aIGxnO(2Cqs%f^+XS^0_%o9`3m z91aomucfBmkpFu-B7zP&F3LIq=(FrLNfxY)F;AX!bgIy*3!%;E<0F29`;n2GNob>F zB`yjNP^8?hKkbG2n;?HrEa`%DAUvn= zXu#z4WNydyq<0nAB8b+GRTw=kVbv-srj2fktzW^xJX4#z;06T~MlG;%ZJjT=0aV4; zW}AqVX79|A#Q5NUa~Hs<(fooLl?x;aIsALfY4H3y`bF52=KWzYZ`?p}Zw{RcE;({& z@6Z6?Mw|MsEcg-5F}&yITcF(z^6*f=979-iaM~3?m_e)*sfWoB)+oY#_Fqgvt4|U& z2QCm5qZ~$T9s8f2Nu z;h%2~!K{AUxWv7hIW*1asIYKlntC#!)1X{J%p~}hh|`SffwXYWA{jCQcwHe3qcM}l zyOXH1(NUM+y?nUaV?qLH5jV0AzSD9raJs)alNh71!LV+a@J&Rw5Ij8J*bha-@7!~g z1djW z*s_Eejg&&@_l>BLdQrfh#)vgQn7iSU%M|}CaqKqH*p6@?+y(z;n14O7+Y9az!mdtK zhj(f|)I)u!s>(XHbh4DQ?QjH5M1nH9KHYe?nKshpq(^0Hv(I09HiZ z`8?)3HwQ;@r4W6d0;KxQB*L_+l%g?s0_H^;$okCZu3X_l)p`qEUVUf}CEWH9l4?7e zgU?L>PnB-0d%QF%diSx>O!z{)crYAL@T`{OtMJ79V`B7naB+?1Lt3XmT@D!x?IfG> zPp8yE?70rE7J8`leC@Yy5Ba#&i!gX=6DVFN1nkF;YY>v`Uus-}-FaA4lD)BWx95~D z^WMa$GeivluRg#Zrg*o(y*vzt(RYJp#~@pR{Zzme!<60l%kVPMWC%3(Oe6i*&`=<{ zLRir=!vFM9TK_cs_1OEc9XNnKgIO@MJ82LMh^XqJ2p05PLJI?(=M2`{8x14P0YrBj z(1!vJfoLsz5FH(eV|Ps9_eOnq7F`}wAaTtg86rV=2)*D+3d1urCFqk4L4N)-h(zdv z)3YsujQn4x3(=oA)mI5s`szr&Jaa6hBqHqsjYBeS-B;g#8u;LyK&Ba2m%l zB6>trl=j#wpg9P6=Vul{?o0x`%TU}x|KhK_HsgMFD|!C&s4eiW;lQ@a{op3pU3%d; zE0IVsaUE_x_^!|dD&L%aVr(rVk~0y)PV5GE(&uz^#32Ky>*`XF2+JKp=OlJwOOX#; zMg;OfGc$<<1Q`ytcn4%?`&ZxKUO614v##^4ke9dz}B z=uL%Er@wK*!h#!5>-*jGATerq!m-scOWe?j?=)IOjovVd?cBK&oMgjO6vE}(A3o``? zGoJQCp79=Z{`g8n=g3<&|MhL{B@>~?M@z65}KRn=h>W#=<724W{TxNsWR*VXy+wEnNTqLij^YIYIMQ!scc%(D1gx_rqJ7u zu5wZIv+O;~iNAb{PHOn3y@P{;#CcW-h?zXRGPDdSIXS9`ln&0$+W8k#abf>%TRm92 zjVy7q9#$dFkK>(M0;dh5jo|s1<7e)u<2D%nBGXB8KZqLc#M*5taNc0|XRX{)f) zjT;r;zej@Ip6sjK!AY+eQ6hdONs|^A>mQ=TDMps)cglU1XJd6nHN8d@bPnQsD#je} z1}WigtY}yze!+G@ReGuX7;T}uVTGW%&e2FKqJx@DyiQn3$RQ>Er%=wALwNec2ElQD}}5G%n`R7KQ)o zgjE9x4vg57_F-_;g|IW=74qQA63H4cLSdvaIk0oI&W}y&D%y-ejcDZAXhYjI9yT*4 zOz)>or5zN*Tg0&8)$yyn z{Az<`;^<(mS*G`U_6p_Cxl5Ox$yV({K>hVg8fUp$wZ7borF=$NIrA>Zzx%sJhFTHgf--_*3)9)M1^?{kQ)d;qHGP($}IjuZx&RwaJUBwKM{AQ^($_| zEaKrqc{uv8L`SL5xK@d~&jz9KfuX?U;zcIl5I|W4PW558-fH|vzCQ*3!~QvCKO>wOV>5r?5)KQ!hXvrx2(-6EF4u)A&+Wm z`>aT|!TW5DD=6=Y@}gPPen$9hULJ9pAXe$KXU|p(-2MyZt&iPQhJbQVR8+6alu|Yk zyRiWO7t(^33r`AuE&wM~qk#~bK#=MDcG|yD&lHVIJ*$!S2qvLWuJIf&5XwHyGH)-$ ze;{FLfUYi6Z7T$1=)FMn<+$+zWMi!OGc0AcZ2~D@X(%Wz0ca@2%?KDKBN$>Q5VRd^ zTEQV91Tfrpk;dWPblGQ^T|R22S__%8wZjA4<^>+Z?K$R7ab4;R#4OP*Psr z60p2HJT`LBSnnb6fYYt5pO?DIV41*WKy*0+AqEFiVh6kcGtIt9EeOV|zOk_y#Pl71 zn%df`K71ghYpwS*<9(-OCnhJ$frg0^8!{>g(Be%2;{uFGOi_KVk$$#S&M?_C2g@K( ziq7tHr_ca97Qd%lW993!E z^a9>?v=H6fOnD*kDo$H4v$I>fa42=_0ATO*ua8>*SwBHw2WQ0wlKM+bcsGWysH)bx z+-y&Vg5o&QEx~w1R~jp^g*glz4H0&QF?m z%UO`g+{2P-t25zPJa^P(qhyCK&i!HaoJ12gmFtj>rFTgQM5FQob&kS3yHNABW$b-_ zm-7&#qV2nXe;8>p#MSWe?cnF8l ztfIoj!xIE{l10*!LOMk$YI1rSjfQfyjZsp7B^REa`iPQt&#CLr3O~_pm1&UQ(Or1A z99+*5N(k5aKYM`}xcQ$nRKD5qsP)k(wF>Q4M|TGi+m^c)S+NcagGSU#%zfi!EbbhQg8Tc52ubX6fInOh zKjJ>o)CBEM+l7Y+P|7R7Y0DWex7UCD+J{Ca61cbOtUXya2q~c+-Q3fts51yRI7PNAk;3oUS)@vH^Zxha zE0@&cfl)V#e%9Ad#3|1L8Kr&jdp%~RcL8~Y@U{XKBKT3UoP1pRX*q;5mCb68oVi?p z%D%Hq^#ZsFFgHu%MHAD3yrnyh?Q&+HYjD#rf8C?18u6Q9YoczU#$j`DiI@{Nc;fTaO;>kpDwI3S1PC}hHpcOw+I4w0{lBGpZeQH+ zc~w(zcJRr0 zd6n)t88$!hL)-(fG~OVT(NoxT^XGs7e2|dOx+gMJ`gpF^S?&i&-LrL^!uUXA1Zn8c z_ANCNNw`Pw!rP?JpT8TMOl+I;S7L|1J+V!}>*Hm;c0VQtQ~s3m-RF3>1#EMzoh{EU zjZ?}wNa5#x>fO!D!xf2EV^*-5tE0QRg;^uLxZQZ~-aWaO`sDYOaO@DE^n2z$XVdz- zW~%?#@uMK%He;vWQzfBem!6guvy#3WOgSpkZfyZ$<8Eo}_nS+MRm%J2;5lO1XvT1h z0sW7(WHJF$$Rd}fT(R%Wk3g;eyif!0D8O08HtLZeu0{B2_x(-D zz(7mQt-9{D>t5)hpCH#3xX)#Lt&yQfr*&~ix{b6f-ph?@U z5thX~?!Igwd~#}x=>No( z?m2qNw@{8)Dd+Y@52NpjpOTXDNAv5%u@$3#spHCd+@R?9T^zgqaVZH2359YRy2c(? zOiIM$+~>83M_il|Fh6<+F9b|5QIw2QE0{v8J&DklqLHEmhi|&4qzhuLF%b3<{kP{h zJYMfWM7*M$OdkgjF@S+CR@Sz?_!@q@d)qg%&_9tlPV4V-H}Ta$uYmbY1f&A9nIW3g zH;{L+U0?q$>j?@w{zl`dyc>-oloLG6c6O3`k8frE`;@E`G$bBC$lRZwLhtN4wh{d0 zkpL@Vl33>HtulAMF3-SP_8ZmXDtw9igxeJWhj~2q&DE?xVBhi~C3%NP-a~_L9DG8d zy(Iztmj^1+ypy|61v{6|Y%LVKSy}y+azkoWdG|~Q_jP+V^X2fvGh?fZbK$4-M0?+K zrhdywQMyx^mG11;GUlwN5Eq+g6X3PQ1Nd{s=fK6wZhy6-TeH%$em`~UFI(KqK~8>E zKd;Z)(C}52&*Dhx9???`*M3P~-d8&9sQ6dh_5S#$!007s#*NusXQ`>b!F7AQOh?JWx|5*h|nT*4DY6Gt7dUz$>t+P(aqJA=nL{s7ZVaHt&}EB89I2H@wfDlhj#cFipQGp(kzisq!lN1Z=n#lcl@3CHOU z>@~Zi(_L?%xO?6$LmzbbF(Dxi9B&JHDtaUj`+I9`(y?Ho{Rf;(L&Ru9y33)I`nK<_ zX1y7AhDXL6Mmo9P*&Pv6e|>onL%YlzCPu=B6yj5Ymj6z3E>j8>ThQ*>ojk7Q@1liJ zO{mkpr^x7iM@k=lz#R_&vr0vml&_Ci9T;qyvJ8`*P-u_m+8;G{3&0< zNg?`8MP+3857lZLwYEHJbeiKacfr+u$MNpYfywHaV^a>^xWU#+`i7t z{Aozsg5D2tkNLa!%SRo~WJIm6!a*O0Oo761Q^VI1?ewq9*S`hkzjNW@61`KI!2hUY z0k5Lx+tRjR`SNmBMvb4vj_KK46n>)xg+-f1OT_s4!jlnw#{afQ+%7IQv8g@6Cct656gkL1}+Kho?tdi+?Q)~~~x-wn;W zf=V8&IcyU7@O}kt&cU}M&1ZKfMz^;pUyPCP7Lj=B{>x@e$v(RKNJ81u3jcDS;-^wh z@rOBHG^DGiuJ%XQ>3w}Le26S;&269_JJ!KG8aWP+yjhE>BY>YZXo7akOFg=M`BxXB#y zAZnLo*lxq%#y2I0F6dmis;hmS`b~_vWgmh_`Ug$N4txKeORf2OH>}&qE*e~P7hCxK z{JtdmFvu?ibZ&WO7sgOE4^f{aq98&i3&*o_Zuc5xwh49MLtdg@ZwXc?1P zWo`81lRX1jD+@iYamU_R?1mGm_Q~wrbW+9}A&{}=aP-SFOr7V|Y!mmH=uj9+luIA% zll@E0{1{UnLZbyk#f{bfZlkXF^6}P(rqmsOoJO0-5h-<6SDkKov8u*}Ao!7Ac*^74 z6|(DQ|27WGZy&mAcfv`N{xh4|tEBkHI~V)lucujA>b~q1N}CuDi%I67HYz%9JE9TK z6hYDKFSfg~?(??O`}d>b0ZGKmZ?GHUwA=FRVGu}ful{^(nC*rEu^6(Tj8BDS?{jr^ zFueMHuaZ(<*-7d$GrP03h7jofYNpMEitSF zZ8AF^`rwoh7?;xw-V1;XL61}%{Eks#EWlqx!yH%v$Vks`{%vp0HhO~Ae+IpGMMbCK zKmZB)_CqOFi@B*OG49LcQV(=9g#KUPqqxE@?pB&U!n-daDIt~DdAMPdFFxjeY^?r^ zHhKcH-3}|y_!^ zYNe=!vV$8O!&diYtCka{tFp7>WUIzkoer7Qug0GL=_65)#3DacwJb&a&XWbZ&kbLW z`t$^}?AOh6jWQH`pGK~JwJSHuXLZAL&Lq>iule~0=XKo;^5{CF$PUJvl6Up`Eg}}h zBW`^n=Vh?}Fb~Jj=9vwVV{7(gEs>vfH0(~lA8`NSEU?Ywp|ZYIxJO-y!2&7&fV)Wg?`!+c(%Rp5b|_w`cH5?5TzqL|eeI9bO{%gyTL&lSTB)03-6eOQ z2q|Qzj@}-&YTcM)I!%4x5-4_+9eckow~crG;r*?Ulu_c%{6=Y7^M!n8;hCXuNnW+K z5{0l_r>;VcHs{vwDKEQ=MK>mXKV;CcYp+PLZBr`wrPVh7x8TO#eW~>cRsB)MRrP)S zOAB?(BtOCGjVJfx;%bBM)6UM#4nOm&pHj(IIQxCBKU`7i!lTf?=hSEw{QQFQy`C&R z;67~F%`GLx?KKx=co+D@Y;Lf2>ozint2K;wcW&6S3MXEubhvr_*EKB$_?UNRw_Qf{ zv-ImW4Ro0^yit#q<_O-4Fwei|odLK2Z`BK><$$;=E%et_F9KE)gI7DJKucG&wMAne zfRTbI`_;=NI4I~Df+M8m0ec{ry`W%;;WnMcZV%vxC}zm}hOvQi*RSVxzC(jFMxTAc z$hp0uj+bK|YBDbT(FQt4CI5x%1-#6}0BoR6^KW~W&Ky^!ug4bpx$;$+yy@im)BU}@ zAHlv5qu<601l!q$gzkOwdc{Of&+c*obO@$}oWfmZuH}_Zb9eKl}qpvUj zrN=-m?VE9tg&t!9$`!7tKH7B|=i7 z!*4nRu~kZS=gBqhXes1nVm0}VgT5RGwr<;c{8#^`r}++kpG*Vyo_4;PZqAqf zF3u@n+~^da)cfAbU!hCYt$c|Wf6t#W4(D9g6X);9lUFsN`J&j(|G3ciydL*$1?tZm zZ>2si+@3n!s}L<|6y(ELPnEM5MK#avp4WnpA|fA_2PR8h-dI}m7pLgetvJTG@pg}q zk&#H&&x#tRkhxBcdA&vi@KoamPv5cS*rlDDHgnW!^o(KkaQ|LlXAr5WvFEfUHEOO^ zaDj*9WUYR_J*;^)T&LPus~0}B#Mi-;r`vK4jeqBo#RQ1jmZ*nJf_sE)b-e8>#v&rl zKR(T*XTjYcK-H@uO>&49aPY2Y5ifG)5D^Fq=T_w|xo>&BEm7_+i}>TTUzf(8idT1O zrsl_VWPX03FRYPm#Ic;mI?Oe->}*leT4dSNn5-N7WY^?aj(Cx4#C@Zp>rT!CSM>GM z$J{x2f?A%OOioFuEKk>Ta~-4#uZzDj@>1_Lo9THkk4iBQB`n?pzCob+3U=qzQr+P7( zRpct0_Q&|FjqZX6QgffuWNuSPUH7$Q5_Fu+DYI&SwyBMSW61`JTR-+?$n|e36~<-W zMpv$6gg|JA`&Sb7aDf?H5RlV9EfWqts3tf|q`*VGyde-n8=fofg5_Knr)aECVe_1i z7vCFm{xfC)!dStLpTVFyDBTK}dj|1|Q$oTh{S>TrDwqZbZ@FQqXAEYz??SH-M#X_p zf~zbRev5~NrQmUh+zB1Z2}~y7Vst{#%>2BLYa!E>uZRCi7+^36*k}^6?+nVk@!zG_ ztDRu)gTC_B7QY>+=8Fy_wQa8?QK)2E*(IvrBaiI}c#PFPVV{Z zQRSga=C-%9uhmVBy~{}sFxnX}=^e7cx=xmzV*jkZ{Ed-vn4U;a|Pm7V(S z`r;nbcNG?P8wfWRhlsw3{)Q>YTX!h$_?G!NTI^k<-H<`BuMr@(9d-90HgytX)+D$g%ZQ=D=$%>%Qu1@gw)EPmG4T)*M) z`(qsWfzG*}qdsRk^WC@jzH0hxrroujIi31Nl*)+kGbZeF%2KAgrMJ9~rTkX=Z{t=K zTGZX2gr_s(xwy7lUP;Ck^W@i#7X@{FBW`UvR3B$Pc_?9WV4-J-N^fIu|J$eouS;#$ z+_AMZho!E#J8ibIwSBV^Z8F?mD&byOcp}4q$%mEIrlWnDhdbc!iqngui*;?$Z)b0C zd(P7F4b>*cm;C#=mzse+I@!QOLtm)yId|y`+h5??wM#bJFhcpq0KfYcv#aN?8XB%&_x3d}sP4a3Ah%Un|Ku6} zt}oWg7OIbZv+{&Bn3=zwS6Omy|7nAy8j+ZM)aN6B{NzRx>j zqDpg7L1Eqa%{H(d?;tyu>F01aE7Hx2h=BHsm-OOobIMHO$!Ur3^7MpWZHb;S`B4u;n+bJMRW%$AQN*gUzR}UI;DQ-g zw#N@a1SAoC_?XPCC$JpfK9X7CaRLKaKT%BqDf zloL>_r7t2D#S#|5XX&CF!cj%MgL@w@tI-6q21#+NBR-jj))T64NI64WQ7 zbb4gQf7&XQ$w)8QL^i7z$0r)meW47kI=J3vaq=K3il29A;k9Pe*TJZCy&{YC9vzVk zmFJH>k9>Gq&=lallRvjUqi}a3jT|Kvjl8l!C{wy%K0~C?x9iQR$=eUuU$Xk3&@Z`} z=jZ~bbS8CHZtmOr9TqkgA@}SqBykJty;{2VR9`faBPwBM;@ekI4?**0b?QgNKdQ@9 z-k4i1a-C<#exGG&*nfYbWv?TzoQeae zswdR-G{oB5^WR^qt$BMwz&~)vo@V<_tk&>@1;^q%t}HH3b;?^VT%?anSvt^Dfhz{jcJYFnnqg;B|=yM7$35g7?Iz6?iMvh8j zokywDN;e=D?eTT!|GfLh#pJU)Ar)eT{@L}R1Q$f3?NgcLj)lKbEbr) zVn=LOO?d|S5<|Bko%Da_`}-R-Z$nd4%|zDf=;U0nyd{v^z*pYzl?C1_36;}fInSr# z!;2?OxgfRU>}v1FQbQ07{GJu?t{#YUlc2kM{%(kx)%aS3AyRrfV8|Xs1*4#Y{FTTz;`X{cop{41Ck1a5nJ1qmb$Unez(HA6vJPN z>Xk-S1wl(+Vh391(3{DXVXJkXO0emB?opvng*GjltM-nLH9fm&ClKY>9+3UF>*NB7eDah=bK?G+osPyV@g?As76~tGZ;hixbQpT-;8*Y^*#kuvgA-YYXqdf zI;Ps+&u183hw(Cw1#s0_ct`#}jJ>ht~mp5yuN>2MsMk^8={`?}8aeZI$Q@LFq5K1EA< z7U{=b!_mcZ_myJhd}wN%g7WG;P~r(NE=JY2dNQdXp6~;+J&uQ}>=_jpJGHFvDtAuV z1xBevq3Cel^#RZJ*%FsypWpj8b1sZU7eQzH`K3G>L<^{iH3Mq~9>FuLM_?7e*=qsh zfEKnUxKaXD4XSj>IR)o0)fy~w?3qkekGMFSv}h8OiyTwn%}|Hr$@Km6U1DU;GX#R_ zV0K-5XH1Dpd{xqfuf0f{p@2|}J4ti;_2#SZt~wuCh?VG~p|ZOvvOzy99Qz{n;PMpw zj0{_3Wv((}v}?&=Y5ylMf#X7MV~Wj*A;wTno~BRHf4Q=2genbP;#v69!tWW>8fc`1 zWMAlW9(H3Xcj7U5wH!I8@zD6~ZPO{yAf7X}#Up>mD(;NXp~kVTC%GybA4=LR1_j^aZyp! z@9b!xTzXs4<*iHn0;Ia%)`}!;Sg|g>uCMT~rw_LEyLaNmsql3DP{za#qn7Uj)Tyr~ zRk_Yq$enMJ+gc$l%ci7rz}EBmp=yIM?U7z;N!Eah<6~)dqvI5%ZZQ-w!SdPHyUseE z_*tzylMEDB3pbPRkC%F5fNRR{aPwfHT~C!{0fQVH)BI5kZ4 zw8R{_ax$EdXCRQE7|=8T)|Ia6EYZo4T2hj{pxbHNJR4sr)0y-pB|z%aw`OOtjyc~f zKVRQRVPPVORJT6!n8Ytu9Ek&7?<6N@{$=a7*)q>uIN^Omajxls>Q$Ac& zWx)Z0DeefzZFG5$ozAISz_9Rh!E@mW@^I8)vAgWvMi{?;U9iJ_$ASbrIzJ7W*sXrG zS>2d!oMTdm)KSoKo}<0l%yk9mxH~q?VneYUCf3(lPq7>yCHK$J>dcak+UC%p{;rG; zIpYuIwf_R^^*|(+R?g6gSVGAD(PoIGB&EJ?o-A0 zOP(#Fy6pU{7P%L$m{A6qGXGd7aph_sV)ELEuWdYje91GqDB0s{wEa#SrO}ZSta2eC zvZ}om%s-y)H|6M(*-%aglH&q$#q%_0A40vS`kWG+lgu==luFdkOja<3r7dqbH_K`_-QWVGB2ezm4~AS6f(R6(}m6v1i zj%GlQ)KTf_=u$O!;4YP3j-u11BZ_N=;x5ZBn%71s7)qW0+{|zM{IM@F`w{pB4g?^UZ;JmHWJ2r z62@WPaHn?+FV>cCx`CB$2flF)!3B(j-Dio z$Pq(vXY!ni--f@>0TldKwJ5LVv=A zJ6)c1l{9gOq73!j7_xsqu>aNdx7tS7xH(zrj#9k5y!#(Lx_IPpYi;-`jJ9j|9Q|`$ zpYkAmdbzP^Q@njEs_oFHL9*+mBY*gKEIaNV8fwV5e!U&_e?ZnVCejYu13Rn@6LS-U zSF6)a=G1UTir6eKFKcRzxaGdetQiFgCLllGrY)#brZJzr z_^r>1%PH$Fyhv_9dr1VdVxU%rA&5^PIy+oN@Deobc{cQ>CIJ>pqC?Ydt1m*ea&?O|U}b5qmKG9&q^C)@+( zWM@2UySHJ~GV?`akmYdK>+-!eFKv_4?Ali&JLXimvb~L3Vvkn#T?_m}rusvxGlGBD zlkq9{6@S-tZL8}z{VWz&tVu4Vg_)fBaBSE}M`^aT;#(DK_g$z0Zj@ z`k;AXVcC!MG2PWJh1hbp7@l1An&E-e-6Xqcj#H}*yZK+0HSgXy8`Mr|`@@FY_I6^D zpVYmjt!x^Z;#vL9`b9+tSC$`Xgz6Pru;g)woqPre4hACP1LbL)4*L)G0wYes40dr1 zj0G<|blCmWqN~yQ{(Umo)FdcL7Iw_y{{vUI4G;#Ei@snrCW;CB8zXpI`OpL0(B07ZHrLEloLqRTYqR?n_p@;=48;TM z)km)6{L-=WJuNxHuHIQCTPr^4zY%g7<<^`@+gwbqNq?2|Q-JS}47k31S=#b(saGCd z&_kDO^XfQz^0Gvp-3)PB9pg+0SqpxuVf`pRN?z1$vunvOyR2as<>wb372j5$r7;fW4~(WS0Gy!e5Vz#H;fLHs2$Y0=DmJIt zpNE<;diW~R2rx4FBx zs6NBS=!m$~d5a0vI;;M##@Fa<+7sqRb95H1-Z^uA$gVWxXb@uH+>C~S$J~NRaS3mz zp`o;_!l|DPyM3=d5l4z`dA%^v>i2gds~53>=ILcADL>(j)o>)4`>CiTu3e+UIRt@~ z`%v`0R$?XhtAAh;3@G4to?=dQ9;^hb{#PP6oiNxk2Kx{?6i3I#$2nc*Z}}L(yCtV$TfS~Wf(pY-)UX!&6}QaQOPFZ`flicGnT#zDIbByRlvR{X zKfF9Nl=^^e{$liFJ4r=;@WR!6o-E zQ?@Hvs#$gSr)Y*ZzND(q3$3lHsGrfQFAn+qvDoHm|4igNwgQW_m&)(FOtqV6+34Ah zND1D|ThYgI)-Rq@>MLH^IK@%d#3CCd&ABNiS9fVtD?4T!tDCuQJ~}oU@@~q8*Vkrd`R~;yxFTV17gH zxfz!dx|8&PqN2zdSR*O zzwgzsN_r9{_Y|qcTzGg&Qd(Bf(=0ZOW=V;!*#hKM4jhO*eE4F5z`^RzY{FmX(wFU8 z3#?`g`~4^k7QVgydG^M|DKw>r4c)_U?xuff_**-bUHfgO+*7joM@26S-?G5`Kyto9s(BK=*F*~KU;YR-Cjd!l;A3%u^1(wD=QlW;MUvh z*ld6NQWs{dpyESf?{pp-K;O3ca+{N>^%tP!q*UqR08@*I!MPczH<<-VDWMZxXgwG& zR=VL^@nq}*xRqGKK{R&MZ`fJGbmC|3&$b(0`OY>m$8L4RnP3ViMmnZSjb>V_+pIM^ zpv2dFd2?4Ezrq6uxBPS7xxV zuv`QX!(w-TV0Pu|-t5kB$$|lEL58wBHQlWTG5WChWx3?E+Gw$>UpaAy`4x8E^2b=p z$0KDpGc4-58WpJcb1xaP$qY&bpWB z8<^fhMUVA7G|&~N=eQ<=7Z8VP{_&2on-tO zjz|UG&MlGj$FxIzKejwFaZoKD_*kcl-QUQg@Lj?BD2mb3=1fWq@1o_n2k&9Q@Fe6~pau zsmRi@Gg2nMq{w(6E#akcPv-uG2Ns>LI9GTq66LIPvaV^Kv;BG*ni;pI`}$K)WK2E} zWBOF&A;@sODEU#quA8Z;6rLOIcQ$5&WN6BpW%ZxX(sxyaxvh-F3j~*=A^cZ@`L8Ns zDB^K~(e>hUuN012WU7gwUc-{@*Vg%zXK^TthW&KaLk%bQ=}kL4S3-1u|9%|vgt4bH zN&U~n)x|NUw&fL-w3~7vSy-SzR^>ZR>AK91-9v*^IU@p<8yEZn0b^D=cJBg=YOq0% zViw|u-X>bGAZHQonOKu!gVojVIWeuc4H+)*Km8%?f?t4|%KIE00ZbhmA(E=5rc6eCzl51srz#_( z|Cm$Fk8aQu9n2Z-EWo7T;9uU#;E6U%AT%^+WjBhTPls4&;NmLQ3))C_^cBKz)IySbO%UgJkn zb0)8^pbUxgquAK1faUt=)@lJ9HqVn?<^f_4z9GxJea=P{USJWvYirO)4oh<)=$}a00)CA`2zn`RA$gVH9Y3C6+ zur1~@8QIx#xw@6jv-|6=|E_7<(t1Sr`uLpuAb&_w7%lawopqllkzmsD8Tq~+t@rjd zWcS@oz!62OKtCXi7{TR-dW*W_#iamww5_?Na3zV(Z#o?Q??--%i+uR-GE|2Dp-fL- zZgYDk_vAv;Q3m2(pU%1J3O<>s!_;8h@3rzQ0SX|Y^gk@lw9*Odele)9+YapIF-|j4 zd;zCMV-pi%$OTOqR48rNIgPIY&pO^;TJVpj(;-c8oQN+*{M>p@gpzkpcfi{?f%f+q z43Hlj5bGrl!vFWD!wD~W`1ql8$&W9ZrMFYc#FSz%lhr)uuxX3Hp6Bjg{uZKisd=zv z>37x?GANt;(W!wldH|c+68QAS)*p}2_Vrq}m&{e2vETSs>1PTwQg5y{hngU2#hFW- zVP$drGfO2t8=f*L6Bn`)8rSuy^>qLmZ*wt= zTdjOkNt<4+-gsSXd7YKlT=I*29Ic?;Nem#lllOeaKH##T?YUO`90(yRuN?*Jxz#8M zS#MWu)Ovxx-;5MW?HY&3X<3LI5Ku*M7_tN+$kLb>egL%y_RM2w=)9z0QtAJ{$i{N* z+!eIl2;d8NH!Xk4GK*aotS}>JfHPSSLm+PGG&RU+_l)(}nbwT?b;B#W8Dug}oH}{l z2+jfXhicoZ@7PW@oI!iS2@TZ}tN!o234>09zufoyyfocld;h^!gBup!MzW2YS9GhK znqJrxNMDg>nO#Ms?Rb;1YfdQ6k6lcC!ev+8XDf_pE3V3REtU!$2!BM&@5~kFx5pT% zrFLjAGv5iH?r4tEBZrzoAsPgPJU~$k4Yqrr_naR_nH?89TjIR3I6d&n<}mQ@$Yq-z zHX&SkXS*(Z=W#mcCm)yD;qbWH_QW%xm0+TA5&J|2g_HKqy(SaM5(LNug>LDbV7bFH z1GGopP6%ssb(gYmDCKyAM#8a*Z21J? z_9#Ku$G=B>G6H@Si=oH+e(720M~AU}sV7fjBgNYZIPO0{*No}=v|h;`OvVVX%zSIi z;ay}X4?zxo5X&c)rk?$9Vz^M`xJFJM%t*qdnXk^xGOpHaijNg;@2p-=M*EmUx9Uc> zNkZ7e2-V}0w}1ZB0?h1ugQVmlyKbvj;<02euTQIA91;6%F|tqKu|BKVrlFF0TafD! zclDZjrVCK<&PaT*us;)O(tuq8|Ew5BB+!(UwU~5u|F_nn_6M{%_Gi{-ox8wwqr+ch zVoyqU)%9ucIs21kidcyw8VDohTqQw@5DLy*LwWR2roW8KqE^RNFl_pH;zlzUd4%1c z?`r$MAGkF-AaAwOxh8e!z41<(Hr>k#dbuB+Vn1K(LYC>~IqhyVr6A(e>kgqB&1^!H!Pb3XsuvJZ`~`UoF# z@_ECpJ9m>a{a&&wD=QtbUNN3jCpCaDk!;$9fn@#L8WL{%oou~OOfhG6?fMCUXVDJ^ z;R#1cvLkXe|;Kre$&U7CJOg#k;UFN{Sa(C`5`Qpu}?|FDH@RQ zohXncLyu&GI80^Mo5KJ>I?OH~!NS79j|q{l!bd!Lj^WPn#mxYNC10<$!nB*pr8Q)l zzLkzQ=&W2hf%;S!jdwf0LXY8i=KP<{3){Ie2TudH*_okq>WAEu#)7nV&2yHU0L=8> z?i2OuY(8iHqGj9C@wKJ*?fJPTdSf;?xJw7yBv69xa6EFGWmx&1(ftQM<`vC8ms+kD z5B9FL6)JW42RtzNI(;uVj$b|L)$-n73WlPF9v1hHy%lg@zhrV&`|H{f!E-#UANS}4 z`0Fngntm)VHTW?mEw*=W>e*}J^Wmpt^vp+Vz3dP1P)w?4Ljxx&-|ols@Tc=9+nz?B zj~*_yJCV~kl%_EArFi$CfT7AKA(&C2r!X5_FBKUn|T7J6+J!G4U%!BDvr&F#4HH2((qNC9m`y)v~ zTQm3O&FV+6i!cG+0ZGS1@N(h(>W_6h3#P4T=pQ&1A=09$NLaGsp~!*j9RmU7ej6a1 zemn~mN`?Rl0e~DHeggnxfQO|#s#$fS$AP^-Xdi);2A&5*+(7OEP$c;>#RJKM(I{s!bgFL`LsyNx#YKTQVjnlKq7^bgQ5OLF*xoPN`$O(1XY#UvX9 zNfCUrPv8WGjj+0e%8Gt+jf{&6sje0#sA{|EF2HXn5S#9OwdV%!hi_^GZN05+4}g^F zIqVmEJ+9;sd|t30e8B&NqNNcOUoW{YM}QhaoD|^U%jzru*g3PhWCzQSbVb2`r|Tj9 z`lE~$*|U$Vbyn`+TokHyZ%w$Qm(x2mBo+Fp*oNNk@8LR=r+b0J(ipoYe0Du-cNWiE z%g^PO9HU-sD!EFP`#*dY-uSudhscC{9{6`4*v-2$YjXuWQ`Fu-!dOUqD8 zqvk2*S^E4a{E=yQA!Qf!)n`b*Ep%0G`x_fva&gUb`e9hSQ5^T}fFGL%i-^;X#UFoF z-KwQRH?I``{?hV_&u-X-LdgHY%{4vG{Gpmnf0viv7m950$vN*iB7@jg&|fU<`0Kl> z^v;k;whs>~n+8|@LheE$?g{t!c+UQaHVCO>kWD+9xF6Ct=kcifMhW4kjlhbV$N+(ZVrF3pfR7j_ zbO=hP7kzgDk^kNraJceLL^!Vk*UHmR1or2EpL|4_kh9&*vSLTIF()aSM$z!tfL#IF zmG+UC6^|6}J#^k%c<UX?VHuF<-g&#Nu}e5`VE( zcE=1-8#1|lLJC)h-JD)wuJ-*B{uQ4|Ds;3_TCC`eG+`hnMn|dYXa7vbRNAvFySTV1 zy$RKsYmPMikarwNp3yF_3(b3>uBi&;YQ0+=G7{~8pb|Q!Z*On`UJ9t`Ij9I>N=t|t z16>BJDKi*ZtfjoUSX)2P-A64){p7_vT-Q}V!U^yiq8p*g`Ko-h;wOmVQL?w9=8v!? zGcTCoZwLoRNX0>svK2iv;u>(-S2sNW{uVsjxU#WjaZ<=$C`(n9Il{O3c-rGR8zYZa z3!ak^g<`TJgONobJ~ z21#Mk-h}NhKR+GxDF_xGS}Wd6Qkho6S_!>-B0{%8k&|$KCuG1!UFF5a=P&KPHpUK1 zky@`E5TdT-cMp=?&9N+b;U!l=N4{*M*@~P;`OLGLh;Kp+bkEnTDUdd4|8pQYzxRBW6?P zsnw~;bVgU3=1#Mnd7b!8p*sJ=`E<_%ZlB|eNq1#UCaW(bq@O6ev%cn}m-Ft=3)C6N+gn z?S8%GX#3M0Vq3P1O^nqHWtU7la39?>$;4OWM)9(^P<4zS_yY-3({YtGKD`_D(Rz%6 z=MQXwMrw^qo8)G?1#WwKzWrY$AG|T4=M`m){N7u0dH&a@f`%c6_*Q0q_nBnTQpzU6 z01IpEZF_rem-C?8+-{OS)X7m;Q=)a6n424lQK11KE$xE6kmH1d#5SP72v;v;S|md4 zi9~|0&RO)+$e{?3o{>Qa6fMHSMjv`M=-7TQH{lt1B+X3t_4!jWC8nls$5~ED5J4$Z z6qS8sjGF^rBfO&^KMW(`*XYW1;#kHz)xuH)a}4kB@B=7odRRDdq$jQkislbR~8~0*xTVde^2qtmc(`G)K z)X3RFdSUq@%Ux(*bLr`WjPq&g&hpIr4eBCxk?zi=wF&ded*}+)wfl%M@BenDq$Kmw z#>8Z9UB|~f`BIHj0$FZp1`>m@vBEY-pl#<6U%GTD&Qf3tDMzm=r_g+V(bi~2rcI&D+(b|gN_-o3$XelYNhW^NMv*!Bl({bFg6 zn340~XYApM%3G8Vghx$Gds8avn^0a;HHS#IK?%+<|CM}w8Y5eN(P4mc z$z~Ef+*C_-E!+8E4ogziTHhTJTAM38Ug8RX3@LE%Y4cg(@fEmFuk;V;dMp`31ylHs zCgc(Hmxf!OG`Gx6x`vuR%UK{J5fW;bJXUEjc94ShJNP=32M=ylP*5P`-$Uc)lRW-9 zo<~Li*kJ>h1B74_EF&`-)b}|Agh7c6*C@igA6lKynu{RV3WG#~; z{YMg^4|?;*&7QMVG_r5a=A|(_Ut?UCQxpyKXMG`lWTx+h1yXOCrt3T%0NG_0PCql*T_35OA9HtgmtTou0D= zQPRw}^9r&wmv!_c6>{RVLXATs83bIZy+AwXdR@d{b-l#!PpVvpsz&3wb(NCS$(u%% z+w(PppDG0I+UDakJv-+$n4|NU-&5%9#=5`@_RO}{=#2-LTv}}+B9LOy5Uyv0xHV$J z9_0D1zh{8#I?c}Ros@JOCRtgRa+8woK^TB>wki5zI~Ol+?yM$;)Gz=rxN$>ODbY2m z!um%4`E2^-57Wk??WYQ!B?wj(k75gbn+f0OjW4l3Ou`Ac>9mJ6YdFnUD4WE)Y6Gv5 z>8?F_CHMRLQ)_WlrHvfQ39ct-jvUzoVw1P8uln%v3nTz(>uV}0a-YztI7Th(*(PIY zL?}l?#1I6hb1#0h2nUF7c6D6|tV_M*;jSkzJ=M<3UG$qiw%DGqvLjqt(a|s*>qrt! zgC5(@3rnIVcGjAMs>aWwX(%&4TMF_DlzixQ?#$OZq{ZD3*7cz?XQs4|%T#0i=D0|m z*EI$`W$n!w&0W>&j#dLA6R%Y(&X-KQ;E;H$mr&t!^$bm8R#_O$`GhZ*M-xsJ+%_AM zXcTH=-`L10WB4*+Uqp4(t50xA-;;wgU%ingI^wd#@;t5m+Fwg%VHa6gDI;CEDtni4o#l8M})T8D}s8FVD>_B)z z*s7ziS#HV$Sy(F<4a6)v!$t;j>U;EYjuO_H15gN;W`1NA8o6NI?b7CyoU8XoR;P4A zJ?^au!^GHFw^RITsfnxAhl8^5dSGT|&t-d%Q?qNab>#`RTl{wT9fpzuDEE z)izY`xzGaH=>$H0P~QT-Kf}ZtDY`y1h2Y8dx~GRKfIFB#urz=(a2ts|p%vJ7sL&w< zW;uIEYIk1c{`|C}G1z-HEh~%s>tI3&Bckp!riEfsQacb9$@CO%LG}F=$Z~Pl&Ee2q zdQxvpRfR1Z>QkJgltb-)-xR{nhtdjJi&K#5c z%4HTIwxKICm;TfHMqqLYspnrhJKoMV-Cd-3l7n4v>WP-th#npNrSoi+sf_*=V;?ao zV{PyGaoAZzot>Hq9z*Ju_adCFSqGj)skw(}ML&ekAsFl3_w(BUm;n@Z-G{X~m+fpq0oPxf zeL?kvnq!=&hg_tax*f!*wzYZtr#qX?`p7RGeRqGgtkUNBXm~qUt4w?5tTz1+PsjTY zqE_-}tt19SbJzbQuyog!i8<$guNAvtd{abWV_eQ1tjuI<$=;P^TQSMK_cR$L->)%y zA114vo_TG4iRT30HX@>$UKv+8FV~)Mac|e1)UocO@VX_{sHMfGU@o0{laS78vbh%F zH^SC`rH#TvHA+{vSE<`4wC=gm?&eZ#cU?^EmbvNiQ@k`9sPSJix$4*#y}Yl<-mo?d zG=p*N<=hEc`(J;5x#;OxfkyODn%UoTnpAy6z&C(N>VwlyuZV+($#+X!F=er5bw9Vt zcH!WU)YQD9oGM3KE7+2hmk(JuU37u+eq#3Z$oG1MoVs3D2CLpSy_0||Q({o0TI6Jk zAPbM6fS9VhP`Jv%w(}KZdb2j^EFoccXUXDVX9QXOhoZf3WNLtnaAHo*9>4=DROF5h zHba3g5wRG^2qH_;;@44DzW>?bumbdXF%pD!tS^pSvuO1eS6ZR#>17_3%L9WN3G*+1 z%|0)&)M@AB;qfgl=7HZA1c2`$UQ|sxoSqwaYqH#Aa)?zX%ypF!ITx}iD%nynW^_}I z#l^?#D&371oRjzpBaOtgG`|d$G(%J)7ae~eCTvCsv$P~y5C(iT+5ef8a&cuIq%h{v ziX4nvuJ~(WM_ONVX3ISBPB)r{KZ{NO0A*(o_u@_Qz(*TNku*Wle0|rWs>52Gv>gs5 z;vF`kSGJIf-SSiV<|>7z??206;&HNavO0Xm^327*cHUP{zFxOp&~RSQ9b6Zk5UY2c zqot!a?0YeLNWxqrr@pd$AyY6XJ#XjHcS}1{PNbZ(v<_6teXv=v%Jh9CxyCvDk0c*E z=co#~%PV^vT<5NGw=^}2z+UK$;S70nG55(Or^u+LnM@vjp{2_)e_Q>uaEMYW2%U4U zGF2%WZCe>W00A%q=iTM+noZ434HTyaK=A*#{(7%k{KnE-N@n4u%SrylzP`7g3NeuK z-h8I-?3riU(rohSozsSUL_}0wT`NU+Q>bbt~J9}1JQk~jk@fR|mF(%7b zd%20_2fIKLX#QYts__Y3yhw{Vq}xP37EAN6@E{aiJ}lBrewSF2c^U-asNo+!MA@k| zK2JsY`|G}F+K?bDFjQG3JgkJ)n-nd)wk~Uw#E%FtNDiiZ@Ql1zxGBO;$+YTrQOI^} zXMSfsHj$*E8{-F$m({4dzkXPUMTYrQ^k3S#v5%r% zB3{N-ZZ7(rlt9oOOmu2ncb(6-{KE4_vN0L!N-IWdC*Q&QF$Zpb?;A1SC;Mi4wYeZ! z!9?>GEwUlbTaRs{v$xdzq8~X=JG>BkFxpM{(B-AYjtCddrpBz;ic8OXXI@*G59^!k zAo;(z8P&eS?g#o~Ut8;goLoB@#p~ZBroW=KS(qAn#%-QAI+~D_nn_jyfP+D7y%kc` z_UfhWdcQv2o5y__9{E=8wS&`oL?)#&UX7e4S#0I6)5AyildP+DO}%?cNp-;Mew`=+ z+P7Y##&bX4@!D{5?Z>mY4mPae_MwdG4M`;%PRSm{vHm=x9rEjIycRHtsLHK&w>Y%< z|6l>sZ}C;{%QXm-T^ii9;l7fGF7!h*)?V7#-d_Ew6&%lZ;0g=NIZm#+pBX*YcWDk$ zZTmVnc*v!y@Kp5>m^jou7QdMbpSbArq@tvPpf4jttfAmtBxDU`Uz{FTecsX0G_agy zi|XXidih*eqJOW@B8fB&TP#DuLlRc4Fn zeS7h8#rX2cW|@}hKNGo}g`X8EULM(sB|f&StfBcig2U#;>h_dWbHUID@q6xURMG4F zEITH^M!!C}%4z)e;QVCAUNK8Q!~7P*{wfc8fJ!wdtEty~=d&&@Xusu?e*P%!h)PtJ z!AMJOY^?c)NMQU_ZRM<7S_C)wWi8fF4abBErIaV194EI6GBET91ZBN!x;hvym~T~2 z<}c28_>#-PN|d}q&T`q<)W(5gXJJIYWa0Y zu3qHl|1f?DK6H>t+C+lmx4yYK(UBpBVo1szSf4aQPU3}O!)Js=SmW%30bKAAK{_yH ziP{VhVlCsPnM4tCehX(}TH^8A}p7v-FO&Ett4MYtv1-6k06?W|2 zTbR)iXcctk2~~F|cpPjZb#}H}PZh%57XE+4%L%_h)+4zJ!IVsrgz*++cQFVOMF&V9 zaA@p`L^NVy(2@P>j=P?#F~R7)3xUP%rFe}rmSDDKAYztWwe52Bk1=MJpc>e^zIJor z&hzi?-RZHKj1AK^Bkt_4+kG@#TMHf23>K!!IcFoo&RyW~7H7TZLGMmM&r2cT~^4~rPD8KvA0;WF|X%couCV}W;MED=v^uG_j{K^g5lS^ zdsNO@=IPI5OU^!COMpCIYdh^svwvEffaC08m%qO==7pZHG_gnrD~Ly1>2&1VNu}tP za|e}O6DrTjIjTtS~m!sbp`sRH!6UQ0Wu< zY3XRc_E0iS_c{(5$Z!`#n$3;`Qti|%*j`!Y=$Bf?AKX`1+kfc%G^BCsX`d%~%U!=3 z5@PcMni3(=G(_$&oQ-}?blIJHFmqqcu1q;u&UX!s=65Jw{PbDlv)Alwk@;fUp7PJE zna6dvkl1Y;8*@LG7%kS^>*(pcml;U*;>?+R{SWNkYqR7o4pX|nocAg#?{nM`(w*p| z(h*=O@w1gZanutdysd4|T@ar{(@0`suUXxh&CSC=7rr}JO~m9g|y_JohG;r6Pu z7I35ePN{k_=-`1u98g`#S$JBF=>kE)?Q`kVEVerI=Fv^11j|_~-O7MNMmP1xJ|-ud}bZtWQDjz&p}hL;dG(`M{>O)&`oOY7z;@SekAD z2i+sLiub*pwZ*W1f9^9MY<4;tULRjgyQZ6`zb)p!w}@!y?)-EpV|@IT-S@41F^_jX z5)&Kex1g0rS*RLFoUc8weBUBb|gRo8*|izyA}a+?zAI zDsYQohHHd2u=pb_)w)2sA1>xO64 za^b(>_Vw~%AqTPynLdLBE%%f1-cfN3@_MfnM@o&7lBDGn{F3g81hLuvUL8MmsjXFf ziteTi56vLMrJYcJtFGZ6g z8b`nUY~lT;Rba8ZPW1J|gy_&0!R&{ZMs})KX;e|Z9WFV^tLc^$D`2x{SN<-ig-XXy zSGBLm$jaD^&z~UGK=5;0TdGTBV-}N)wr%et_e<7CWMpJ1HQ(ioHGf@vFH5?+ppn~< zK~D4VX+kLS(4WHgJI}U6Z6e*2i+O)6-$xm?d|pzahP}~xbsaUA$LuG5Y2G^hyPH=)3;yBJLYB7wR$mid0XuV3{hIgTW}_KO<+$#K)NYBUywz4ky-7 zV`wA34VBeCK{%~I9r;avph*9(izkewE0_P&Js%IxGx;2E#(P3}`@-J-q(RA}3%>`v z8ddjF{JgMFtbBKUqn)tiO_!4{f2OvDo;b35|Mk;Q%^!TRw7po+g7b@US;sh*Sev#e zey6X{)Qhk_j*|}=9_sm1?r6hw63hBYg-E6AP+U{^%%74$^-mGfhlJvJ_xf(%?*Aa9 ze(_U5C#0%0DGvKPMzhxr1yfvH826%@8)XyBd5BENgPh{U*uB2P;EA~v+c5nscX*A% zz*{~|>(!wHUXzYfqO6hX7CH> z7!y&ofeq>eGhBjU0+cRdD%?Z-` z!T$Fe1&a2qRumQwC$DR2eXSYa@|fF`n*Z}1-_)p+_Ve}~U-AW_oH@KUJUr7gN@7o( zBHg~Qcskiy;`JPxg7otR0utySz>#_W3co9-9(_ZuVgdT{AdEv zPAztJ73&kd(`6}*N9wwlVYA}8aS$4iHHUhxN=R($N{jZ~MSJ*gFf3K?olj1kzzXz3 z;R<8CWA?0P;-Mdkn@ObhiI^aCynlZmYRQ0=Y(t-h5SCQbn6@4vhMKUX0bz^f(h$+d z+qP|+8jn(x;9fnnnmC99(DO-AHaj5QG@$xGXm#kuZj>T$Y|L$<;k%dCm^HPr zxWzQ)aISy4!E@uA>a9Owxy{wXIcztNtOXCkXoyJn;j#oxc1r#ejAkeCQ9f8v$4;Hv zfy15X%!|uiEnv0ZTmzM4GK?Az4pV*g%N9;7#K1JdKYi24YPi?YlC_;!r9_%rTPrda zVJ0V}J2Emllr>c)wyTkiQTgTfNBb>47VL0dS{nP1O|R=bLY1VF$t@)%1ux?Lc+xO( zYXHB6$zDdh`^^bv=C9>bRa;1{v3v(mifuL#$5qG`u-ONSGlHCT5J>D-N9~SQPp#JKq~Eg&EnH$RQ^HzeO7U6#k4$x)P0zM!<9t59v&6o1RB)ocGqTu`zvAulu0( zou+yUoNh>I)*vF~JI~$xT_-Qj4G4fQdarOm>L{_+yZYq96?}$&`Yy9OKJp~OKnO$2 zWBUX4-Ql7*UVEH)ZyBh%?kg1BnXi&q+FKZRZX-6@~N@iC>t zD=XIi^@hZkz?75Feab;n&7!kIO74aK#ut^G>YgjC6oyb zM=}UeON=Xd-~I{kPw$L1`+&y=alIX&{oh`iHGVS~BBkuMzwGZ%GT4G9sW2t;H(zak z6CdKb&+hzoq|%yj2SM`8ZhuWi#kr!)ed>te(MxxqWuH;y)1_28GfZ=^1`b)TXUJ)= z3eLIywYd6}M{cYzqT&7f{g6<;4@Vvtl4<>9HXFf>`vx~(z$GC@J%mstg(8cNS>4c( z5j0@9laj&UYI}ePVo3554gV%2+1s(+ckbOQC8JDcvRr#GjUbfUJji` zTqdE7heEIxC_X@!DEZH%WL?rdA>`&y-kVdK>F!EwL#Wyd)^)%10o@{t{7;EP6z`y{;a$;WfL4kpy zl9Jo`9VWIOI1s))2+?47&J0-`O2&Ob^f@Qe5pM|(4s>mpxKB_;x9dt;-8swt;!I+4 z^Cx9-IW@^^+wX7ox;>m!@M6^N$wo{ne8 zy}*6p+{{QcXWOSkPbLsyuN73>-+Y+gZhLi{P)Nn98$v5`=ANeJSAD#W3luC~4BOvYtWBTxs9{#IL_Wa#{N7)i?Ck6`LHYb}%uTwN=nnbi@Ek z3Cr6}=qF88%RKh`PsmBL32i6ne+f&LLW8UBsfi2#26I^B$u^>!y!HW^_f>?sr>wR#&@catI$3@Bqn>E91n`!V?T?MjS?r>EcwHy zaX}F~Sfn2)NY;Jv4)id`ibj9@4XlPgO$b+P;SSY!LPs}t)7WRv&70?VIqyA)qQ216 zqqPbrS$xMZ4aK`Z`?Z- z(15ig!UCS#^^uE?uvlJQsNV1ap9RuOKJxJ8J27n*@h1t&Nh0VE!Jnij-A5khIJz$S zZyz29psWsNzm(L}gq6LxPKaoCPr77_kAE}7Mhu`QMHtuvXy+rJ*5owcMCCDevd((o zAio5o85YR)+qSlK*bxTi=4|%-a*y18Xdvl8Pn^hs`d`%3nqss__yWtr5wcG$%l!Oy zqjL>Gf5tkuK+W(#@|14r03R` zCwD~L1Ew*arTdw zf=rGfEW+^{##5AG`?;?rsmRKb1HHF{6f5XT7#4?6JxQ;B-`RN&L*inKPlUvLGEz-A zl(GTO?FC&uv*_Ot1%w4r9$NRCA{2JwN=Hl!MNcUT`A?3IALr$zftH|kYlrxmCs?BJ zium^HR|q8hkHMV;kZ-4{$_3T|@&Ddq`UaO)z3V(ag)A0+k1g-9a3M#Ag?kwp0RV?e zO8tLgrDM>kB0LO{mtTekC&cVEi_EAXo%=90mfhY3>7BTE_5djOVKFhg;8gOlua7Kl z52aEN@qQ#2X-dFsibR3{z4kTP=QmJeV-xxSU7ji*M~2VK%RAQ!Il?so;zp(Sm_2uQ z(i?-159uzNNMf+_{_o<(@Ma)C`vGH4G^z46A0G6-b_oxoHC~V&*_Us4xJu+Yak`17 zNfX!9eHu6C_J0Q7JL6;|lEPa)1o%fNjimtG*(3?!5+w3ts*9zK&ys!xQ!7l8q zwnGn&%K9bo(EP7uBAxn*YkB3lA%yPhsat1(HAR^Wj=w1Aa9iK`VE=!&mq}Jnw%s6z zZNRkF;c&;x#NFH~I>!IqX%;mttwTg#c_<(u zZR_anKFCj%Ox;vly9qcoQ5Y!`dlJbYI`}9{HvRtn8%d2<2MhQ=l$2`3kP81K9t6r} zaYn)(5~9w>sqHA4D3#3Lvy;UUs_ ztw0xx=pJOQZDg}l?P<^t{z1#c$5$%fo1;LHXxz`%pilAN4~c+?qH93JXxNIUY2psP z1nepe2n)a&9>&B3K!_inUdPUy*^L3T6FU8sxolz56RSIx5&RK0vrkGQ{N-bq0Iw|+ zD&h190&r9O;i_6hTpuODkN`r{G|9)nNcI>88 zO{2w!b8+LtV_~m!n1KQAy#BCO2hQUdDsp`KX>_%7prOwDRH9o6Iz`XL-+m|3(GDcAt3@ehCSjf zA4*(?v?97e^~hi!Mn|8rXT?i5ZSfP$S>4`7@!$u9-)g?9G*e zGtvJR|E}*)!K;DVln+pwG3&(tW6V9j<#VxtXZJQFLc z4AvL%XW|!_20*Ed!FvZT^8XS+H3RrTZ#_OWSPEge!1XvleHzejAe>)uYm!v{t*gv% ztTF4CV!rK}{gtMU;SocHV#AkedyQ4w^;Y zD5(MdKK1Wz5KhJ2%!Q_1+aYK5Z)HU5y6ggkoDi|^@9+Ou9Zp>GlWj{U#nDal(rgX= zjH*@cJI*o|Y$$PCVid$}b`bWK!Gwz(QbGX!UfGZD2P(WqNUHn0PO0rSm}K6Mi(|lc zC%!FiK_c`?konbt?Usb>@87k&WAUg*zel;+V~wB}5ohz0Cr{uD(jaxzeJdPuVTdUG zzk8U8nM4h`Ay8i8__q)C6JO*;29BJG?hHZ>81IZnL*#Ry&>2D*63~2_|IQX{P%}p2 zQJqv6sJ6q`0%A@82Gsw2 z()WKyHBzg)#Jk>11D*$$;D@b%O2GzpMX)WVz-E@da)pv#@LzzS`!h9o6wZtsZdd)a zy_>!KrM3RO_6p8xOmGPn;7R$|x9f5L78};>Vd4wE!2U$`wgowhBytGC^d6$luK;kv zk~+=CcHsa0f?MHXt`tN}cF{yOA};*=c`*pXtT_HV2eINA|NX{$aAANL+l^zAcp%6r z^P%m5v-2BL`~StLGu<%m``6~{=-7udm$(O?G5&`yVoLohhe_%Iw>ddDj$)6~!;>5k zk{7o9c&C{U*R7M#?^4vzAaXAlax3LN2CUI(V{PFdIRP~=2#siOODHQpO6KtMmmYsE zdg`+HbBo#aIAj)$NHC`{&0>OyGC>{0^kAktBIAupe1Rj2U2MT+sIz;>FFmevCqkY=9cHl(($IObDR zQzR0;5m)##(h+13_W|4dMgs-G8^DOLJ=E0d!-1G02j^CTbBVEN?l2h8-~agK%Vu;w zVg8+KNs5R2g`oL{?XY;@YZC+jL@{t@lt+*HV9XvtC;;0Pd86B3r=h?-MMx0i8$ZL^ z5h(#;1pwBXxYnr0o&`*uteN`|QWIn(cm%3v0iZlWEA#q~*XOV}vR}Fh7BbEt`)U|| z5U}qX=*WT#kH6pA+)ORDGP4i2E6uD!bOz;A6?23^$b#-7StcNH!bU#XMfVKEhI)I} zGkX*yFv;FsW9QbbTLI$}vj~X&hw>~zUHK+2?qHwPL5kO%SbCf6F z;|LQ4R~V-J#wOU19h!M^l1a~$*}mX+V05<&YS~~np`&&O9usNGnv%%6BXbYOG!J@g zI;Jsg09}KaGydkHA0@Qb(c5~XIbHuJTFr=}PPORLC00&Z85z>+(ykay6N+~Tg{+>f zpjnO)ygab09sqwJ{YF%VbTi(X5srZ#quwQx6nj_0aF_JgkjpYK*hOOJH*MXzk%Zog zoj|0BN$_YPAg&rh7MzyOp&|J5{%7r>Q{wuLRwF<6kTBYQP=k)&to;mRNsQLE@7$SW zv5aRTC}KqJ;_3Mrt-Pe)@t2>$C2S!4_*HHoB8bWfcrQ|7tDb=YBZ5PMSpeAQ-=HP< zIc_o|!fs%C#C;^;`tCcskUW6#V??lr6SO)VvGzMI!f6Akr@x<_stGYYp5;7(U1Gso zi=B@Gu_WLS7y!bm@7wnXIT2ABgNTsJUl2gy$`u#fqE7Uix*=2t4xl~!9-t09dI_Ru zw#Dbx7^wnHY!WCF5|6bH?(t1elZNvzU$QZ6$=v3=GJ6ow#3L{x*RwiAm}kzMfdOUx z>+z4hMjZBx+7l=Jv9iXhc}q>r9t;|hxPM;&h{QV)etP^O?Vx@LTcnFQUU0h|O4Yc-osV{l zzU@zdTR%h;oM&7m8R7>zSe>V7Kodqf{nx20u-nW}+(0S&BdUFfUf$i;1muf8juN2g z06jJ#$HjOWI%vEQND>(dFq?va6FltQ)n!`(cz`M*GAK!mJS9BZowmHJuJ?%6exsIq zAE%D^3|?MdqOF|h+X7uAGA>SaI1o7^ax1lTy=`#(I{;?_=+<4HpA>LhA2svuuXdulx8p`m0(Lm`S{}e67Hg4s+=7}?hOXgCmXPr=9m|N>Dilsnywe0 zBv~RViC`4`1tMOC6dEP!R1ELRJ-CIQx*Po{ao0g8 z7F6U6!Q{l6_0QtH4x2Hs`M+TiIyy&z6~Q2XhL6DPagKlA=>m8x&3Vm<^Z_<+7wQBL zxDm_;r{aPsZ?FC{uB@N{(0}@KB+Tue3SCfapvnXfykT?-IUPVcNjN#EZx~a0o&xFQO7=E(HpU z2)bE!5xhvK7ZF{6Xi6q}rvbwpfgSnxa=p9&6cUq6#N+V8IEZN8cLP$U_3&iA(+$CE zVL?GE6azrAl>aw)LJ+^tIl#iMq@4D5wZ$N_DgAvSJe0;6;zchdB_uXt zXeiOsjiL3Pp!^{~Is`{VtLqkIm%l`LVr=Ec@ zR`q8wl6D)Kz#Z@tiAWAjDnzJ@DJ~4BPRae(&!i%L0nLw$6=ApqEERF`3GT~C$Hl*K z(VJt$ck*rkpomuCjew~RVI~IYAB-(aPEIBm9RC^buo9tMl$0K_4aDOZm^%xu+j*39 z2~H?fYyM3H5XEgE0_nXjs9Heq$qj7QPq`|9Ff0 zg<}uVql|uJm6~F1F0S+_X9FpzCx2Hcivlp+JmxWWj*WREn%D-QYfr;Igmsb|{N~1i znc8Qq2&3|{sp>g#r1XXFPyB;;WfO2_WJ^~GoaSvf@zabLpk0=FfAS`gG7%=|+&MBl z#8xo22=|R#_wOERgo3S&c4smYGFfzt9={93LRn{h#W=zBMyspSQ#@WYLNpBg`GQn9 zRqxwgl)|@#9U%^;#}teyBL~NB;>FMDCDyZHD&mkS>{HHl?#GSB<~~5&Pw+#CT+_}j z@9)#{+7n^{>ox})fvW3zG4~m96W?w_`1T2DUFSGNC3MR+u#9&M_Vtzijddv~$jQ&b z=%N(oiCp9&@z@v6F;IsTp{7F%mqEBn5ZbrBE=cJ|@WY8Tyz1=jL>{SD2oEHSqbzCwg5du&vq1^j40(wKAR*0i?>0(-}H!i z*93(0@+B3i7pnJg0)Wu(TF)?`PGQmaJ$}Bqftb0CaQ0&tAC^f3%EX;wYYV$EM1d1J zvJeI*>Zp5It2|_zU=MIC4e!MXmK|XW0GDhqxWH?>&>OH{$QdwK z`9)WplKnFB@?~InZ6y)7AK=PORX9;E9}^egRx&J?TIzK&G>E(Zo9y8x6U{})+r1Yp zS+)V$0%?1ro-BzlJAlCIhc%(tVTtfPcN>u_Ga$$Q(a`W1mrAqomM5hNk5JWL87k-}JAxlb zg%&hda4}HDCBTV2th?=TxwCk1RV6T_4LD0=eEd@=UlL_>q*GZfBm)AJud0$VH|InM z;u{$FlUzMV6yj6&3K#F7d*VG=Iy#7rv;c{yW0j2_tlyspD0oBWbraMoJn4iFz?tC~ zV`6c;#ga`sf(@b(a^42D3W%^g25q-grmz7y&tosI%VAY%up0T+qckjxM4$zB6Eb*) zr~`@j)~aw@m1o8+F;oM&Q^E{!r-e~PjIcYw#I!lW@eeiDfQEZkB{Df=uQ@TRhj@SR zx<#abi~R(udV)&Q!}xes;xzwPhPj1AO!tNIXG6F%M$ciuZxqDuIqOU(pX{LC@KjPU zLHEl|&VZzS*ql9kMBF~t)zv8t9K%U42S%++bZR#=oL5$+!oUW1lmxbe_HhnjAf`nc zpWz44gn9*_SEJ9K*Z5#bA+oWN=m67)#E+5COT#wWxgsmQ?bZa;X6NN3(C8YWe5 z-?8H}93t)Mj7ELr3slOE!W zf^!J$8X-LDw*aDX*dz%9o23Yf1;P53WKbwfLB(eWNE1{C4zO`%G)f~zL%TuBpIk&* z1*vWX0JXx7UTGYw5=h+<{h9=x6eabUFtzN;Dk@uWR7qFvtE(bdE?OL7Keb!w%H@hW z9MqQgPtiHXYe8V&aAv@GC(GGW$O-4d%doxxkwo|{DynVRB#aShsP)BU4rf6Yc^BIE zorgF@L`2YPvHc}}gu*m2Z5qk51b!g~9jM&y#Wmy=TYn&NK0dxcF=tUsVcbqjON;t7 zyF(>PU-ibijBL!l7Z4mQ*XzQQKLM&rT#l$dCItONWos?Nrel&f%$ZascG1){d&j0d zd+i%2Zb~vpvBtc7_eV7&CM&D{ZA*g6s7OoH#+aw-RPlv#u{X~@ywlutd2HN=Jtui2 zWPoX*NQWaub=)F+I&@sHKR2@D%tnIk1#^2ykb&Nna*g+K+D1xo^2rX=P!3~~U7cg& zZ60Jn$j0b4Y}}Y@KX(czzY|fcppgx>47J{hKEW+Un8@%e$E?ZmmEo{BIlbhclj`ZN zKYywb!)~y`o^;;$P{!wyag^eE3`|UqQCUF%h6)0csAxF6gs$7HjG(Y^H=-k?LTvue zfV<%Zmd=1`E+XBVYsHIDv68`|g5>+E52N$6ArB46yl7+rf7u(re&68Ujp7Agk@y%J z8)Fk$72l8}77h$9*E>r8IfNDbAe6pPlBj~i=;;rl6sNj!oTga>zt@t7W+y-NhT%~| zMeJYU!=e=9J?UOiiWl6IiP?*&0C@~z-8VPCJWJ0GqwMmUm>E<81Sk!QfXFlsrKQqc z7s=^e%`d$q9+Je#$+?V-b9tdZ0;@MnmywzI`gU5Bn!0+_>3|0l)OaX*PWlILJ|Ek= z&+o@=o|i1@no`O=SHN$1kTa&3EKYsm#7QjM*Z%tIdPK&17X@B^{(P)Z7f(dYmIrH; z7V1%q=UH4S)gfa$4rVz7*k9hjgm)IIsc~LZH%m%M|5T4Cl93ygfb{p>f8BX=Bll?#O9LvZ$KdPCe3LVe0Q9OkRjmB3HOe2NY1h;uS!Z zZD$Q_R5c3fAjvZnm3f6mP)OnVm(o&{yVt+))ytQkCmG|K^Vwat&K=qCyQfDIiQMGW z6xTgVoW+y~r$wR@uqH6M9U!SstUPpFhbqy6Y_ZH*3!*pz(lLW)H12eU0X)l&;r`O&MG%OawykXvHg{g4%OPQX^ z(Mx{Zd+5+t#5zEh&PAS%)(`2kTuu#3EX3ypT}kk3&agP@e5duvVo)$dk+@J+VpB=H zJ@Ny;2)lpb??({RF3`6ug|To|U!HOOP7rWYJ7K3=u)K&AS*0l(AeZzYB@ zS`F26TB{LnrxbTtf2c3WViq7pp^(r}|5nSZ=#ki`77e}zN^$p4WLE~w50AdLT&19R z{1DIwRDpDfPbS_Uc-v@%9A9!(*t}~K2>Vh=Ds`#mB3qo>l=+{?Dmoqt}l_RaBBV8$i`VgBO| z!sk`99V;R-o#UpR@2xa_OEk1F$E&8^50>s?`gP_VZwWqO>)fNM%(j`u$Mk7>^#Og~ zHRnr3hE<^@<0_9Kwb~nZE3@qf-oSL`OzY1Z+lV!G-*9Pfn7F$GE9?-&A-vJjAd~C= zW-XwQliSZXbV2>X1f%F^OTce8`qEwJ3p(F$=tm%)a9o^w{W63dARyADN9#6=X?@u# zSk`T(yXwb|jtH1dFe|=%h>Xxp=Ha)#AZ5-zu=mfO$2&xe*Pd4oiuG6&rsLAf>`x;9 z@O1lD7Ntg@9yE6*eyr{|(xNk`{-<%@^K6|%Smr90lP`B1ZgG3saj73EHgE-h#F2-D zPY};Qu0ZMCo~6M-4WD%0BAmb&o|?QLW(s~Vo4+&p7ddbhY(x1?o+(e}y~#R&6TX??+tHs_k~ z(`nd5hRX7`KD!!gm9gv2g06jD8YD&iMMbt;Nz$a#+#XOCZ6qPJi>@O2`HY`{>M5;m z5A8>9Rg!m|^6q^*q8(B-nxAgl{pw771YJ|IW}L-R^814c;`(zf4(%5I@F#c3D9K0H z{4TboJ!&&_t|9%%OG`HCm|EV!`aj*45pI#yKg{Y>T6mlz(wW{(sVwAqF5TjSSeW5Z z;WMWj7fz_p#vE+Rw~wf{-}OduFqru0;ahY+hjJL(@-{!nF_U;#bd@N8Lrln+FgFXI zA+w5*iUj||ZOwwq)=uKG(jxawX)Ux5Ai|fMH%rReL|G_9b{853XHE2WEmRy9v)kLz zCz?0qK0&^kyPy2S;68)ds+!w8hXpMUV{{?S(xLDIEDA5PPedcTu*J|}N4p*Wu(1Un ziExTy@5uZZ@zK#OQYFDk|J?3}3+BBV*^8ss>l$_5suVsO`{`xOZ=2Gv6=m|Fwz>x^ zZUU)mlu+KhS^qO-XL1A`e@aJJ+W|Uo+cy$sf35P@yhYiO#ORh! zw7j!%($k}I+jY42$AjoIJ18R8ev9VXJ<*lEd~>0zA+I_u&HTi*%x50`9Da#%jmF(q z=R86`zo6q!ZtF1_&qksua2^p=W_e4wg@`^aN0z(7~oZo1*a zWOUq2x%^?_m}|6?^%qxM8?)G(w(Z_tR*_HtO68SdODL~1O@GSKt*>2W&BH&pc1O4c z)YS{yF7xbX_cw@M6WSn=d~Zb4b1}!hrme2Eo7%{`Zg3EGn0$x#Qv$_Ab<@kkv}ma`F}c;bEOvhheYYL9A zy;mt~t~%K!X10{cD|5+8M1)28t2cLC^&k_4{NcLe?}4FV_50<1Yqh0GZ1vvoW7(ub zQ|niG3q^4l?S#n~%Je&X%LDWmc5g=+q5E6+m9Y+c(uWe;iYrX<18q6X+SCGMW=eVCiO0Tm+Af!YUHvoqv{PyMs} zKh9>@JBMViJj_Wsc;M9mgo56^z~7o{mAyN*w@$A(~oR^F9ej!%UbfA90U7ASCw=441__cz_q*xi$Zo-CDB+{#Sx0@gj7NVwRt zby6wqT6LZu9ImdEJXcHq6!-LCa*XCod8o(!FEMYjPcWR}Dl_Rj7}F}FnC(DaZ^R3* zrF54x`Q(-sWbw>$p=aZjxlrhTDHD?>u@`~e6CxpC=n$}-I!5v;7^W3`(Nvc7TK3G- z+<>u&;$5-Dw%?s2o2U2OFY__#P{%NofK%F~8BTOV4JjKxeA{P00zI0`NhriODf`+oCfH3{^UJxw1f0ut+ghL^ZGG0wbxt6fA zOhw^j?|*OsHaF1}bi{6Ff3AT^|$lFl`?zX9%cdGVB#p4YnI9WQp;^bcLigg!Fr(GG$w|hRV_yfA`tSd_bh-Pu*X@Cr%g;ka<1gRV5^#PR zC$D=?BzPaVR}6LCG+rQ;2HwdzxKtt~9ws*SwVc-@{z}^6*O%w%1T8~38JQLHWe}HM zi?uP88#ZtY3o}{leVp05?P&|I(A?Or-)#rN1P4#NHKjU8tFf`g6*XMe2lT3z5gE~rW!ahIKVPv2 zB&XR_JMG$XxbJhC(p%-~K%PIsMvd>hb5a6eR9@7~%njT&Nl&>#FH9=+47~uN@8w`y zZnMctGI>MHeBE95vbWV%C`$7d1(G|d50zfvu-m^<@jEYs`Q2)*Iwo?Kxkpe4W z{D!XEG1vPWL0bw0wcy!-ljZ0LKtjlAeSz4}(_F}od^u1u*zi?u*zEZ&v!%UsD=$B7 z^-edFC!r8gZznq25@)E3Mu!FCCQ&}K)`v0vOmHh}e(#?N9sCGwL4=>M8hTe~8{Vnj zfFZlnzDgTl77-~|*g_gvVqcl1u+R`=B*LVeGgd-&Uu1f>IUN%JZ8G|DX}{R1OMX3` zp6WiUs#0wdp*ap5q66g_C2l9EVX|Qm>~H zC;ypYup5^;Fer!De?S1(k;1=iXf%_w;gX` zH(Q==xg5;5<$CSU@%MHMG+vH0H*W+jyfjo&8r3mm9ZX~oBHERB4EuI|@MN{9O`6tU z>lFBv%jq;jMH*|*bwh}Gy)cgwPH5hbkQQ;6lL7$%}Y z+lBy&y!vByckib9GU9lSMMXua6*&l!h}7Pw;hmKC1w_mtGV2jDy;XT~QkWNB*xc|V zK~$wtCG?CF4Qc$imt`4JB3GQe-H$T2y*O|(M87$FcAEFlr%w#;t$Xc~Z_>_O)j{$k zU^g!iVKrRlHJo!LKHPkK%&~}$^MOv2iKJm<{b<*ycBxlzR>HoSe*&xXX1mK{jE^xL z-$T9UTd59zMxUFGi!=YPZ!k^I$bEi)+6B#`z76k=?LP<=af^I;@@-C%{zm?W4Y8Zy zEV>4I$Uppc(y>^IIQwF+-+S|w1B`Co`X57?^8Vb*$nH}8w3SeQ9qKhEZi9G)>z;!L z%c_kVxw{fLspX`<|6QHt0E+bbo$qeya%=OQC8sB$3P{vpk&(wBvmz`!!n#Y1MB2AK zej7Eraq~Ytmm@Q;boI8m6y|5uW$1)Hykn;0j)$@s`@WeJoHJ9IUMT)}gk=y^oY&Wb zd2TBD7x3ORgrw=Q3=Pc^?Y-SLfYEzGVAjY{eTH&2b}ueNQo{#e_b zY5GT0IA`=$e|DQ;%Shw2F3l1T&wD59nV~wbh^E9i|8aY}-t%D|srM>xe*d1yDCy=r z%@pnCW3Z;TI=NKBunTU!1`O@Y2$ z2)k{4{NobMNZDIOEGbLKF?;lN*uE<6D@~{NrDPBW!+UaOhR6C4aj)koD3q8>E|_Y>-nrr4xC=bSx&Lp}n?YI$kk}OD0Be*RDTjoMwLh z{%JQmSXJfdlsWw4NmSMOD=bt3`>ms_eyQB~mGSfs-7k1Oydi(X^5D!3dztrC(verL zo9U2qjBh!iRp>H)P_Fta{8g{Q#$vkR$vamL%CgJCT0|QA`24iF&x~NWsbLu5m&dE> z&LoA68l zbZz(#J#BWPMC8|bnB_jyGM@5E^^)&pQ3DrEJn zeI>u{{Ws?fLob*KPdd@No>yfX6;rec68ZIY90y?GD!+o1CB=GL`}aTE?-M@nFEBy; zO~E7)tN#5G;;-*_UxW$$uQyDV`VpYpfBsFE7c-!of8UVeDHTo6!&CqB2L7Fe^!Jkv zJy^#xhlWF&I9QZ}1UcAa-x9Arkp?5VdmpS1uUe@gHo)=~1f6jB$(n|pP39@QJUA}P9O z@3Omm{vW3K8}4Zmds4QcM13GB@DY3p63@Oe|6#5~;jRiZ+vS06Nx!nAaA3GhY-(rH zsMyCYS$B zV_4LMP{JcHY%KE$*Q>J=+p=@itC#Cm+>VQu_+>0gu{$1Z`NrY(7-z_iI8*LBE>VkZMCJ*_ zlUpNKTq!r79WN4@G_7_2`BUJDv!KSf8qgo9opg^-o!lPa=%aKa?oL*7tQLDqwwaey z^o`G|y0u|1L~R-w#@Y(yPx{d8SbV1w-s(Xqo~V}gkU)QojVnNJM&sq_b$3reTHg~P z@1r8$zIDeLc1&sko1XEGN#@9{bY^Z!X@C98EkdEF=bG}pg!4$Ft(0CIZQUJZGqei-`-pRKu&wVAzrdWE+pY$V>mkH?`SIU;W z+RmmEVBT@*I43>l7 zWw%BxmVU*ZIXC?c+Y%*$7n`l%ltjk`(XnUTrJYET6k>Hfp~HK}u)&<5e%2P9%-Zd5IgJS^2c82eMgg|v{;OMvGzRQ+&6`M zWTv3xgd3|SXWSd~u`4rO+xHbX&J_(7{qj6YhU!h5Hl?EV5nO13B0+Q(|J`Epg{DMG z@i)4oyNgK^?Mvgg)cU8sB8wlySsRXl6x>P!{2u+iR%gDH)U-d1az_qOKtz|!FLWj>Pzb{|jAE+9CEh3>v|J4Zirv{ib19zR0Rb~#hK8DZE! zoi0g1p=M@&#_vQ**idO!NWo~E3WuRhU%eo79jiSB{eXw$YdcDW`_c2Q{@E&=Jyz>z zeK)oG^y-|35|fePJ>0mo+x+?(y=mL~FK^RyXb+ch81tWY_k3JGNqa zZdGE0Vxp|H^tj)F)vUW?im{#%k^EMxA6NcyQcF8f+>tXC(m``(s9wfGTf1od8owFB z4YQ|S4Ne9yW|JKSULww8-2s=y5I_8*I!R;a!%=Q{v|@B_LLcd7ppfx~M@vqXZ*S;M z-cbwaXj&qE4x4Jy=l2&odfr#Ua|X`09eOfDTLrw>u&Gl^Q#SYcix(xS=Q>_1is*j- z?5q4vk+aNXJN@Sh2ylD?O^a;4$4~z4wVY&MMx><*&%Xj%b-n)-*S#aWSb?q5u>SqI zC$k5>tBW^JjVev>tI#PJnieN zu)ONQbep* zmWd<3e9B4edZhK8w)1n^Yuze8n37vXKmFMAs4j`$ZvH;Fp$j7(M?wqjJl*!>R>Ui=_a3zS4v_8x?`)0vDf}~I)rYsm#%7A6pnw*X+iDk4ogt}nG<(fV$2hObEvacc?5>-x zVGgtHZ+msdkw(I;@hBItRX{26^k85A^iH$ANBfHy&wDC1Z5d(&IFl|@4J2PR9 zQ+e~lbB-NGs^s+ARJ61es0dz^kwH_LpO0v{Lmdx%8$-%^^r3yPx0V+<6N^sT8Q_rA zjY=9U3ntSvL-h$>{#pAkU*n0KE(;@#ip}twtUTW0btWx6JL(*xyy#LCGpQ-L^@p)R zXMz6P7s@kZzx=2zjw=<3tK&DaT}}S*E=%^UYVj_Z*!0=Q@g@Hkv?|=}_6z^Zw990H zJSIC#TCz$$emoNM_(iR0%pu-C#sFoQgcc2h^@tziB+UQ&SL;@A%gg;aDyEJexPI$a zsb5)SYw_Ga*P6DbW~S@aW$R1=yhD|*m;I&ww{qLS5O$&p_;Ka6rUM<3ly>`&P(HdzV47?V+(Oo- z)pOh9y8E4Pp;@EpGH)Go zj_%Uv=Vi;wb&EXxeZ5P3yxjVa)`Wr!M-v=vuCh)2>f?PMV9q#ygWM@3hvx8qI|JNY z2A!4^2U#!ucyt}73!z2%?D2CJ2jt3HT7ntfGV7M&=u^y-Y6TY zj;?(9?5ibuxd1q6lxm-5BFibJEd4#5zIjujqH(QnJ5rHztEY@9mYmP;`*|>8*Ujo= zN4ttcJ&i@hnRCD5bW(WAU>7M5+lC}OFvz%co}~6uD8GvyEJ6zBiUv&7`Hp zl^l8*xhI$1@?FwuOJM{sBPlQHSAAiKw&_o47ktG?qO{c1-)MjM0<_~NJ*sjnE_z;s z&An*!aJ*Ntw?c7W0Ef)wJ5=lnCn`BT`jzB#LWgJ%(8x*N-$ZH;H#4v+{NOxBqWmwY z?M&FPn))?`G)3D};quNhGagmpqGg#EGX(EVs6Aoa$ji!V7a^M;Ad?gBmBf2CFJH*u zSakN`!=t%22Z6+qK7}ipzJJ9Qlw`MYi*pquNxOb@NDIu&?V+OBJWJacgDk9I?W4Wn zUe{)SjDfuS+*&CCpdjyCFXi21nNYKhT?$Ta*DSm3ov1xYZLQa95 zml3J8(4zYFeG7Y^=hLFE0y;j;d|z|ojFSuf2;EOFsZUK3QcA@)L>?_EziW52Pq&R0 zLooFEmGn*=E5F%`$VGPY8aL{p?g%G>Hfi1U%!%p>k#N#$zX%>l&8l!X_R!1A>PcC; z@J^oN$Gc}Y=p0yb_!KhmHcnl;L-bLlg?6Tm4QV`k;PXe9Psf;lZENVLYj%uBN8fVk*~oCo{dgJ=0VIYod0|VEK+t{X(~QqGpB~{|i-sLp?vt34m;r zZKoQKSPgT-kqJzvdt|cr z-r42d(sS|Fw!-?$T3p<>dL#j;8PsxdZGe zZxs9@OI23h^6cx^x0d`5!4+sNaAL3HdwBE-<^5~IU+lm5Y_B^e$jHd>^qJ>|dwe&? zuX6SXvQo8l^nQu+)AaCmlCe~_3@=qnvfKY;2knQx(PHz3Kh&_!JD2ktZ^@}?s1L&n|g3Auuk;V zK^*WMN4bKR=8pWvDdXo2d@cyH@RU*i_!4=0!7;1LB@?Oc zs!k`t3k&9%-g3S4rQl%WLi*a>(9zaIVs;cH;3P$r4kWd_yZXV4A$;?DRvczDq?FnG z$BH9FWb;eAx)#_ix5@brsq>BXG;s9@#>mkY1c2lOh1$s;-7Cb)_JG@iFCsg z!$y9qyQy*7D?-(}O%XJAjNYvz@Fxr}@ifS-T+0{=;32R>r3^(!e7WVeVNdW1aXqz2 z2ONrzRZgN_LWHp{!1r#osF*I8v`F>H7N2$Z@iA!gDJJ{z?(+8k*!2pJ@y?3IwJBle z<)Y2Vx?d#+r||E0UJPY@X=z2z$LIYZVt1w0+Wz&$U=SlH7{p?%zp zyC+bqY4seZ*7r`^{fDR_=hpvg&%Sx9HYo%Pg<)aEG@_JPH5Cq>PjAvX_ADOrL~{{w zl=kR&eY>aO7+;$#ASAfqG;*ERRp#fYOqgmGfB71ztA4t_Lr=lMq5aZiRT7*=UUEmvUk>3Q`?=QC!IRkbQt>=H z@mXTYniclr#j?NtB$2(h?T%2j>=*Q_>+flPoowpbKJzUj9?5k1SQe4{61O|pcRgHZq2EBtF zu{uQ|B70Dje|1vhJTmXQClw#WdbQs`PymqU`$PSVjQw$D7DtQ1PH%8Is+t@l$IAhV zMt@i=oq*%i3Ax8%YFXA#Pc5vx<1uP=LBVCh+sqy>hzn9RmJ1V**cQ+Q4DxIar#Jm% z-xra-OrPFA_JKpq+`Kw7G?EyPg);IXp?i1pW`Wbb>3H2vx)d?x7%Z7$7<=i5mH?2+hgn?K?i6Db|tboe@l0F=3LB-ceHMNeiO`u$Efz)*-Q#YzBEg zzjyyT04qON5R=fa6r-UJm<&Z_AZq4$vvv-R{z>`HaaV^AKpOeFY4U3XaCUQ}7eCZ! zp>i2Ms^$jsdsMiwZYrz~cNGLV#n?pQl`XS3)_lVZr6`LitcDs&&HA?t@h7?=1WS&Q zt&P*+SQUC7CgacEBh+5-zAW@lTC3A;vRK3vk1YaHGcxp>cW>tsF`Ie6M>$@F`s#~k ztByquCtrcmNLb!3s~b_abk^WNx@!iXzOeyKUQsq{V?3s{r-%2Y-(4L!PlTVs?-kJrA;M{QP~$81+wW)9hlfHM+l_JU(YHFyH$#79mAq57Hu&1ir- z1HW=OWMXR915--sb*rp*6L{#)mS!#hf4XiA#7Uiu5CRvNT_ut98clQXF>$l2m$K}H> zq2n(`#{VXQMHVWqJy(dr)j0ysroX2T<{VK|0}V#k?dEep(ge?|$GpyqPR9Gbvau_m zyxn74dkyoq_njcvdD}YVzFj^1D`)xQl8w-?e`QeagqMBeU|Sh#GVcXmb(iGIN6>Bo(3K+vF0&U=tR(l<{kORAHB!op_BV=6 z)3-Pn`1q=RQdwBE{0A4H6Dyi*d;k6qa8B)J2F{_y-D{o1e1mcw&lP>uuox6S%-+7O zAc0+0OWHhSP0srNL{c|iTm2b6I;``5;k}g^4r#+3a@g&Rg5SV`MyK1G5dIBZ46ZvJ z&n>ttM$4`67tdfOm90}M`$e|i>t0N<95&*iS>`wE?v@jItI_!Age}AgECDU9{}0pa@aM<*|TDhBY&%3RCKWBF{&nw$*WA#<^b4+zk-K}!(Img zfkgK0I1VYS&7$&H+q_pC+Lge=XV=+d5l1$nH8r*@E<(?4HFoFfjxpgKS&JRKJ02a` zG~HdEb!Yb1$JnIeD+pCdz;?gcCyD-Z{J4rL|NjS&9L_TPDj1Y&yK(@j&Rg2BgXqa# z>oZx-)!jY3rsfe~vr$XSOG=_certD*VS+X-P&xuKMZz(4n#FbX(b522^&mIh|A@Gz zCl{!1*CEI&NQ`0kxOUifK(K3aYH6UWTaDckJoed`ulyntpsy_3w7J0f-&1^niLV($;n z6AlX>d@UPQ8O+CDH@p4+AD4~fc|3iL)n@vUEe|B4Xs8B3>P zC^P5We1E5Yp^&}x=%@J9eY7i0=5v$0-r+kx#Kl_&_au(@=^J}hhZe1c{`of2>V{uw z*6_AG>_dK@EBG~p+bg+GSdp&>FI~LSyndT%{lvL|T?CwYZs2laV$w|Vjjw|0ZR4>f#lJ^~A3u)S?4O|0l~ZYa z*Mj5n#3#x^!v-Wa8&4ay9BRole*EZ>2tt*ZHW^N%ktTtI>@t=!bvze8gr2GUvR#FA zTJI-m%kEJhRAx~>o}v30f~6kJy4Fsa*QZ zmToO--rFl}4K`T1;ja`FK0X2uo7!KVuGf18KN|(d>EBLQUYYdFtn_Ef-%|AoZW$br zMr2-NyZPKub$5M4^}h$yXo^D~Y#s39cAj<6EbwLgCKf!;7yH&?PazjjY8ij)&HwNn z@7}4V*yM1@%nsAww}pw>9d&V8Y2js(eNVOZw}<@JG=nD5uCDa<)-SMkb^jl(TH#v9 zEJsta&Nsh6d&`BMjTjXF{{AXg2*2r#u0X@gJGI`}CNg(tLrFvHhbdYY zXgx=7jLPfOWo{(ASsLZOUI-$@3qV=>F>M8UE-| zw8-2TF^&}Hp=X7*mKg&Z%?(IU10|N>`Jx9i;SGsMZS5Hmy7X*kl6gt`2bE^xG@Lkb zf=eX4M(7#1Pt8SABhy}bLe8ze{>BHCB5w+w4Ve(WHz1%ZCHKbKf6IyBoD-F~PeT2u zg@u33RBSh_*td60E81dwjMQ#Y^Yjs!8(L;cvn(%a6xD@xb~tL5~!-CP}*Li%?{}wmtM_#a1(2?;@l0daOrJK|#H4H~_wF{P4(r zx7Xos!>rM7dJ+zih2(j<{3qM2wn+C{i_M)16hD9F8;EV)xC@19R`7{0(JayN8ec8@ z)x_S^uXijuzPhHSMNh{_eEaq9U!MYxt9ui~KYgJ3&6@lwo@>3F9My_z3D0E2{T}jpy1bQcb#BJ{Z z8q<-x+O!mp|IDdRZZq6xR#PMY)caZ0wDAF}QxC!HWo2d66tH0}>sL+CeoX&qORDXX znP!pW6TtNwQ^!+W&WQBuuhY(!m(@pu1Hd}jni>6MqnU9Wf6Bc1jo@PjdXmQ!d=<`? zZtF{=`cL9nZQIO6f_D~n9|u+zq=Q{Y11?Iwff?SSp%j(eYoN4|!(3W!PW8^%k$XfK5da5fFAuZJJP z#Ce@UTQluD<@gOB7PxIgzTWqXkz)r~bkBeQi?lT)xHtlXgKM!NB<|Sn7jvotR@{ij zhxr<%_xAlq!1cJVY!Y}YNfrcSkaDIAw79i$>HGEc|8mhHoDT2XS5YmtvgPdbs}{p{ zHh&qL<>rjc==lTHI+3z>i{5XvlVkDko9CM*yFOUr4_aEw?>Jh+#i&~+gGr|?S~Lkl z{fgA=Y5u0;`5`Fod;qdzK0C#Ws}!Jq5WB5_ClLED#~u`x?KDOP16|KU5t#!R8+5>~?5OI*J_@kFhHy0z&K0 zo1C;?U>j3zrsXrI;7FtUzBmj{qgCdBR{-Qw0te2Xx`b%B$Tt{3QP#OYLJSF z2IY3&7@L~}nWY%M~)Lj-2lNjY4D<0G2S@AJKt^}E%6_@}e`pq~M^wUd^}tw4K=w`oQA^o6~zOS^O@fsEJXH7|{{W`~6RvW_!MxmN9CI1(*Z*+MwY z1bKb}@KY%oAM`(Hb*cp1UQX^hXzX%x>(2A*aG`_?ZE*gb#%oEcYlU+iXaRlOh42%Y@c`PoZ%Uzy$9!OTmQkV%ME7oZgkv~)B6p=*!77QuZLq#^Z8hh2y-pVf*#MT`Y@u3DifYeM6x$a9RNr}~}8 zg>QfGTT=bMqf*YZ`XC6(rR8K~T;?wSQKl;FSctX=!C)hk3x{P@tK(#{%3&o01b$A> z*L2^};Skv%k(_#MY-8eBk3088VF62 zo}SMyS(PB%%D>)W_?g`xiqkS~6Y#a2ZD?G=u*?mo%@=RWeF#G-F5PwbXCTk9}E8}^$Okc;o;`(EZ4FV!I6mijV2~c81O@@@=X(yNh?jR1~3?q;yw**4^uCdy;huAIHQnp}W0eaTC2{__ciNlk3vJpItE?GFT(ah!LXgmO!1t=V+_4WO{omZ|oMU(Qk^c=1nJx%saf4SxB>?6GdgRG|K;@|2 zwx5nsnlLnn+%Uqz^=l3&%7=Jvqi=l=Ae2WBK8corSxl6wC5tdOrlc#EUC?6c4Ach* z4NGQbW?eTX$p=)pX!xPGp-}`TAi{Xh^L)hf>Y$a~VY-r^#a{+j`>Q;=lYiw?U^hP4 zK_|68+y-*t((%eu7+)y!LW!I)Jv;l=G1MyDH^{G3qLaXpCf*LL+k9X=I#F$g(a0(t zZKJrs8i*{#PAmlh9)xN?ra66zSB!Z^{2P&wZT`DZ;b{8SabDp?ORKJ%Jf~UTUdWGl zt@mI1;(rAR>uz&f6@4Zid3}DOlYrX(ZRI3i54$P6A(lo6k3(^mbx70ofrMCD(j`_r zF}j~T?QM>vPAv5zuhG@l|AIc@$%%>6|Iu$tT5s2mItzSa7ttjO(HqohGzx!(dLPY} zxN%CDc{{zNRQ-_NpOMzGqqNykD$LzoU4$k$q<=lrB>v{=4E}$6kpXlQy~u=KCqf%f zxub>BZHrk1^MAdMd6DQx7IJ!3Ih->!U$c)y!*BW>q7N9+vi|QxOg2Ci#G-x+p(Cr} z)k!D-Y;aLDWip(UQ=+-w@C6<5Uu%FdPNHMeeEF;4pD~Kx9}C?GIqe!~5UTt9CB+sb z6It$mG=7&=SNotOM1uZ8ymkUj)|q!oMT+Vm6Zrau19H6_T|3#opa-TgO&OUS-P`8X$Yp zD16J%@Zz5S5=3s_G{VITZh`ec)z^nEd=ADPkxsG4MMWQ^GIv6$d5F%!+&mIuotbx< z_abpZFT3Q&Se1kzJ`)-=3lXFAEC*}9`~^KzxQW2+vj@xtL`Hi4ZPbTW?d$*6sQ-dX zzx;P+=L4Lk{cOt5YevKmDG(jX2GxD&Ld4-lv%2K$P{aDkBA*mmXASESp!4(Rl*nlS zwKASM<^6AOF9~JWpLtg7*gn!rcGX>w&!lz?yo@#lXzrueMFLWTte}bDFNo&n2Y(Kx zKqQRN$z-9TfT)WE1_rEa2#`nm^fF4wh7LlOSq@*{O$K2Arz;klb7sC zdv!v+L|$yg^_$*J40O-DT~jh}0c#xelp+lO^&$%*brDRmuTq^>>=ISj5edwUwT0mG zj4kN)d`m?uZUxkC$Ov>ELIgYlzWDOk=)Yfn8GX6BP)#E=`dldXvz@;C`z%Jo+`7xP z$9Eoq?^l4qX&Om0Lk%9MwDJ!9eQ3q?hfYsMeTV{UrkUUcI2?&y+i+g=H9sV{k(7!@>JijPz$gu>x+uHD)ny_KK<5Xc%e?nRt^ox zz>j!(69GOz(n}_3@^w=+Y3Xm zZIc*Q@lGX4=D~)|p5D-%dU5i$6mbPEaQ*jffK(MV9Q41}M~{jSN7Qkg-H(e|pP=dk zv1yPwj4e;#t4GetHet^t6-$h=x`u}uZe5Fg=&Zr9(Vb>#nH?=Rb`T)#J9bYihYfu$%Zuq36W8w{ic1*Ahs>F%-tDd`5K z8|f~QE-C4d?rxZ{#|{4WKK@@|AJ2=&7i%fZdB=5)ILElgILG-C%TZEMeSrDEP)7T{ z&xqQ5tOEFbJs<+sVT(Y%FFObvXAwM>6u<$-VKVRq_^k*?NGj>1MMS3Q&ZT);0vM5l ziP-fKc0&|!TQtES)_Zb^p0&*)(}o9N-*L4>c9@+q8706Wy?cG~$CBmyiM zD6ymj<{KIqC3(+%&ZGw(Go(wqpAulU7$pXz)`lnHm-GkufN|T1DQc47G)^q&;WNSuwj?kugL(( zWZ^?Ear``kPBPF=g=x?R*4Q)sp;(~1fB%bNPqHOM(T zRG(3Oe}5QoqW^pI=DTLyc$gQ3aLof*4#~NoQ!$)d+-bxAt7uW&EHIhqn?w+esMr}5 zRrB(>J~|EoPkak(XQ{=X3n<{<)ddp@!+VIMVe`OT^qga)8*{{?6@^6%TQ-9CRCws*ju zd*6Ci`rW&CC?LDl29~Mm*04@c#lcSCMe_O#BUZY)=8<_1fiCBl)cnGhQG>H zwy?8HhDU(C$A`oVJcjNyTrvTKU>hEN9Vl{wz`7++!qbUw{uP8SsI|Y3ShQGys}Dfa z5l;m4Hmp1m;=l39$T|Rl@xnAuz_-%j_uqCWNs*9B!Nmf_&TW!U)43)^^I8LuOD$Ns z3P)>X+)_|b5LmjrXmI!lk0>&;4d@0DVlhNf`CuNqCso|}ZW9f{(nSDW6A&cT!{xx_ z09Be{ccNEUmq;E$H_U3(dk?05$pU*XA-^3X9J{ieT_N$@)Kf&%g4qria^-{3#+SYV z*UWWdj(?!x6+~0ltUpIrpIEPl^c8D?3Zws$&kK)FFJ_Tm*hQLnS2r7vD zkrg=?`ehmpj$oib3;=BQ^8crGK!F<63@;p@P;Ncv_jwn-G1yNy4DD|ITznsq=MfOp z&yOJHG5ySdKx+upkjYRO0mop(q_R{hFb8~p(whYaV_<-$O~FXSA5i=| zuZ #&Dqwd5#+R4#KMl2>}$aiFcRVn@4gOYJ;ST4FgFyP&eNN&j-T0*(FAvCe8u| zNATR3Q=F~=dx$z$mRC$08t^>GXPq6RDp57 z-wFE2n4Jl6=1Y~kQCC-oz#X`E4FFnj^kD!Pgl7tnYd-YNW#xN|EV!--D6}5$8<~TBN_q(wpVIEa1MZQ>?WxaFfHp(Hw7Tl z3-c(2(A)B7aD1J_y3)=H?bm{{oAfyI6N8ALd`+GpUN`>QaHj zcskb`NaO__`W=k6z>AX>pFoebjXpm6X$%Uc;UkPRn1Y*Ph z>s#@d6GInKG~~39qMQvsgud*;Wh@9NHwq}gVT#YPin1fvT!iY0Gz}iGb0>o-K=^-v z^c}{L4OTd^LmnYnw>i+OC<=fIFhzSEIRTRyAlHjZO8qIfHQ2(h;?Y=uofhdN<*|4J zRJihAAG|Hmxr}w|Klr#T1A^;yncdIr4Mp1I{JeM!pKa>1Zx?_P6vj};0FtqsZ6j(4 zV1PPeuDh~j^J8+*3?Mosa^6@#= zoNj(1Dw+p!qnN2&3vL_$n$7A+1mG|2LRBL0wqeW~k_Dy^eH?(=296l2<;8+61K#O& zDg*12C~!A05?V1Ai5tlb{yMxseQ<>cRX7h2@1g*SCWDm*=Bk~5l}Ikr7O40~b5KZL z`3~a|+<;J0S4*qR#<1#mQ_2d&$S)09+MshAD$+d$N`R;j&!SP6E} z4Qt`2ICSa(q>F>~x>cB=l23z7T!XlFAs8{)Y^AJ+OhD%>BGRo6slW(mQ3YE2W_qg& zKs_H-eR{$l6XewelZU=cQqaBudU_BSDGw1uJ6`-<`vq;-xn?I!olHboLD;}5cC&^M zE`a5QX%9?&EB)Y^p|q%*86g|irTc42B7x^s2f_|IGlH8pk?9=roKv!FTp4{W5YISv za~rsVXfMOO0be-;@F>Qgc_7ckQdll4nCGHUh<*LKE?6pA^?!qW^x!9;2g1+E!88Em zG=%vtp5pP#J~+TrR`TLfJ4cfWL7#BwTR=mB!*Ig8n!S^15>+1P0g3u879fA8eDb*Js ztkp~l!IVLkzX(`1Rb&21Pe znG4~tuGSg=D3E-^zVzEbeZ3Hl>{A|b&jd+2(4rD!A$TboZP~!7A zm}(Cp8xb(=b4b$3_7G^f)PiV`Ir;*-bO`;6cu>~fYz-1Mwc(Ut8RVK@SpRNCS*|jp zdWAy(pdX9leM=zcMdrxJS42n`8ucNQ|G2n@q}bVc?Uo5C#r&iE7)JG%xLa|TEfc*?P*X$+gk#>gOk*_`W? z-I%PWQ_Lg;V2r@PfMtoZhi^V)Rxhamqu>Ryj_lM%o*~#uGBA&^uDdD_=)C^*6$7SL zh>9U!dVKz+NZ3svRMiFzf*74@wWL{>qS{&L^DPD+H6f+w=sT?Z;n>pj&9|L|gD0Yj z|H1YDYI0!U<45UlA+$~bF7+0Rib6hjehD1+LGH5DQqWV1Alrgr-)@8#?u^_OroTg~ z?*9EG&>+0CZ4>{U4=EEjpfxYCzX)^Ew#@kp919JPBO?i6UM%ku1>vB&c}=))Sf5 z0Z0%63b{;?8;3aa5?zzANU#^+esDCw>K0!90hZFz!U7pziKMy_h!kjhuC5Vb_1dps zI?pkXL!vlLPuAHxiHB6$@ z24)6?eht?DuwpQ#4BP1JBV=_(g`2INO9S6$xlu5X0Js}nuggFjpi)Cfl7NS(d17#% zksjO}`8K~Z{}r626p9p2ahg|b5%VJ})K1$ApCPjc;0~q=jGR67<-bqe0Y^sz;2ojv z`nsz)KhUOc0J7QAI2njXSO0*k|JQHc!-dtm~ zbcGCYz!+1F{*|NZiRNYy*tbiUmT=vt$$7 z-_%~6GfXLm$nDD4x>i$*y(V*u_+_Tehg8-EOo@*(vIW%gh-1x?(w>!FAyupXF`{zK z@!yUARM5-A&Tn2_6noaW^h6ThB~+ViCUtIyBF>E`REi*nD#L%2e~-;%;vb_IW)Cd= zH*Ym-eVWVQn2xEXBV|0qrygAJAp$KC%4ImpdCXcdApvMc9>$p`YcO@0f@p(f>{J^B0&*b~M!$!dpQklc*E#zP`rGx)&m z_>Y0pp0lr|mT$wD&`k9%@WET}tqS6h3;g)=UTohMZJxQO;*}7j^ZDpz!1TgE5~;KO z${bKH-QlzGM1VJv@MF8tex}aX+l>nI^xKJ1Lv|)!m4jmwB7%Mv+$8PZ{jKhAn3(HnuHWc8ypLg3GJl@e#Q3TyZk;sN(VeRELKNQ|HttK)PMo(}e7o0Nfxx@|hqJjq@HNnDlE=Aj zUOqzOKy+gE8(kH+f!Fv9_(frqMN<$hH&j6xv}ER}RJf4w-m(7fI;_reBOTdyph61Z!RGw^Q4_~Wg>%#z^v zRXw`k;#p=hEB0a9CPNc-#agmcM4g`nrc?snnqSvxu+O)&r)yv!b*4UG z?2FBKUoOSUtXUGgJSSXodf$AP=uwDOCvvOB&dQp`HTMRH1@S6{N;_e+L%En+Y?d+( zpiCH@0Uj8RRXop={;yOR@qxzwpEpV?i&s(te#Cynfbvkr6WXA$8wah;an_X60&%W4 zs@IF0X81tMsU`7Kfc4nFLMeChiOO95yP!E7xs=r{%f|j=Bn0pfkWcQ_IYP|~f5&yy z*t&UxtTxB10*|BgWT@&I%KyS@+A3v|BD31#{41s^GI`{%M>if$M3gSM) z=Ccyb-Wh5XSN1R@f+c?WcjiO^Ro~Z#Q)Sb(?NRB@$HSXR8HB?66fllrYIW2ZAa~Sm z%rG21?OQM5m|Y&1{588?OQ~LF5n;rcE|-{UkQeLT9IuCHWFJGn<;>;_3QBUTjUUui zaYFt@?Ry=}xzF7WKVT*FjqD=qI$9AK_Bzbf2|B zQI4N@LMWrTG>TnKIb2F;^Z99&u|u}DBl0jK^Et8mDpXU@;Y0zE2AqXkp1Lg>=)kD6 z-T$o;1gIDj5qZ?$)~=-7cyl1!5wGf-{^7%j!LH9if%#xa;K#TGjjgZ)qY+)4RpDse z;enYtYRTKOJ7}FS!7#UE-Mr!8dqmdN3}HFcPC}3v0zJ4YR4>>fU#-Zl@4`)z(I;6) zvqL0c@*r?eBGdoWEt|@E;i%W#;Ksp!fo>O_wq~X~>Cn;=Z8-WZq0XO5z4f2LCSADL zk-HkFF~?tVNB*aJ^=!Wb`opJBljV*iIJ|dW4;CHZjZ!LB9h9vAzJ74UH$HG!EmgOHdpQf|chyr&$%dPTEdVl;BurcZYA?{#_CDcV4U+9Hi^pt9GVM#tl>F z4H|7|XVNd-ZT}iYR}}}dlUaJ5W9jm%^cPsJu|}=PkBAwW><(f*$muVq*+5WgvrT(Q`Q#HafiGHk$0W`Tl; zi`EIhc|gD${{jq7{WU=a_F0AM82c`_ghva%N=ZriiYeJP?_%ze zYtQ+O9{(wSFIaxe1E!6oaQR1zt<)sf2F7IQH`>AgX{+guplcnEMI1!(hG8!;Sj$ss zcLbEyezpgpb75a7eKYjD~gMRzI&^BtOF zITFqe&@%_xMeoFrV#`oSz}}?2D5o*^C;Solv~|GNf&n}qtpH;*u9yq)+R&qqI{I50 zJocrKQah=sr>1=x#VHx&{Gg4oKk+B?H!XC|%o0gI z8p+CWpOD|hU4U&F}Z#nf)raK-OJayUPrGm2Bq{U`!j z*r6d@3%EzZt?0-UraV>2tHE%%f%Q)P2SU{ijB4h6*-zfRdu6n?!IL$_RoB%VfOf+y zwK|{=W6}8Y>uq(~@K>3Uasl$jF4p7{m_HVh>6_iXr1H}tSNI6BqOdjK8$bT_jrCQ= z&q!|QUDs<3!#Fubdr4SYTh}+x%=WjXdYo3b+HQqDg<&-_lOLrl%PnYUT4pj#MmK3O z3@$cDY@Lob4pzyNKnTt=<4!Y{S)0hHQsC1Xmi^N1eovMBW~fYK9(;G02oFSo*K(PP z9OraKra~f=ImhPG zOTZHw!j|;SOSA1K7~b`dy0o7(Ek$y#I=};Lul8Sj1CrPRKo8IK^uI}roV|4#dU~u* zijR{ky(^)ezOU*qFWz^V$J}2TjMMd7abKRE+7rGwv2x>apkTuKcuO!-``2P^J5{a2 zDXiTiQfW(aIz6b0dEc;u+P7Ms7Re#vvbY2h$NqL*?uMWJayOZ$Bg-t;xUgmGoM`1I zmc#`?GFrPm+qkDu2Zmj2ZTiltXoJSR#Wmh};E(1ngG$xHc2 zvT62j!i-AFBTOs)lM*M8I7#JyTG^k>&*633z68J=aJS`r&A5-t9E{f?#;sY$9h3ak zctnnyBK{l>>#8I(8df5BPDqYrV-b^RHeOYiv*h}EFi{Y5m((ZXIj^hsJJKo-U&2Q# z1V%S*TE*dHm9ZI369V++U~UAiZo-3(MjO*pVz^hY+c*5WPq>mh*0>}>|G?_Op_7k` z!7a#|_qkH{e<{X97~udsEF490pU}$U0T$YtV_xraPB<{!umVqZSh1m6VF!xoHE7bg z*E5GlhA&f&la-q<<#O89J2quW%xx+jD^NPPdBoKbjq~-@)FI#@{wJ>2343wjggx}6 zBwAKzKN`I1O>mPsVluzx`cBjX9Qn8$Ie z=6UveLRAqwA?7qPPc32~p++f0*Z`4;j0KE&Q9mxzElT?8n+G8cS}~Y zHu^1@5|->oCs8`}Wz-ei%a7euE20P4XW5+_jz!q_7Kh(kYPo&R$}**o<@p3{SaGuZ z!9t_s(`%2#R5;wPRvu9^hL@J#l2UZ{kXcQB_UD~{MKT}>pE#b#y>ax19b-4&;g|P9 z;c?CJ;gfZC)QbFf6G6p~ zlAW9N{W=b7E6;&bBn*078~E2i!N6h4>=__=#A46pXp6gg7PD$ zSDlX>1hptNRyTNZ%X9drExtZkB9QMB%c&An2Cxq7w}IT|WMGu;$@K;SWXiXlGl$_a z$7upEzt>Zjj3#4jnGsANQ7{w*6ELe!pk>FujpTO{l8T80HXHQlYc6$%K_JL(-*r7< zN}Q&!ehwGE^E3jAk2Y&D$&rtVuvM`}b|ED<6cmO=_1!N}6^E(>2%`2BxBIJ|o&vd1 z4{Jq)JayssCh7$F1r@J?4$n`fO+=z@oK+$;;gR&7niiRzP_8a@O$q&M5Z!`->>|?( zUFJw}2AmB31SdghAY%X!Lw~b?#c(jW6v@koP(^k5Aax^JMu7BriPHJMnzzqODI`8Z*&tL65&++)lr8yE|FPt^iXvpx$sc$17m8 z2+>^ww~C*7=Y9d!_}8i&M*d)ghAIZG!r|X^Asl>t`|A$M2%jz$BBu2hA!PY{&U32)3i}p6Qv^I6YKAF4C6@Uk0thkk zmFfc=TyoK*SA33<-P(*)K#&w_m`@u7v9YuLnoezWy@ck`*mwUDx0NeQ!yGAMIZBg$ z#?g9i3%m_NQR}Q^mq%bk6PXSSd}uP}J~Gkkxk3(xHMt}5LySgSC@!%MK(y!QX}Q2h zK#8I^)0siE>BkKRS5EO&Ld0k?2=-YG8jd+6X=UYA{XQlJyCqYR`#X}q$&Hhl*i2%$ znth;mp!z#|=#3bLeN_b4-C9TA{5FQlMrAO>`&L$Mj6E~L=pV5fZeB1TmaG=DP8efi zbZcF`J+l6zG-fFCx!wI`RP&CtdX-+eBj1VFc z=?DSJ+EO<`%*j+oYhvFCBiwj3sMU|Zr@YO#6mmaOap{KsaIHd!S&^m#i>6# z2hYufIaw@26WEd9;hyE#Jg4WJZmZ7T`{vr39ZbJ{r(U3?E6?)iQh!PVMsQIToJMEvzz#w=zm4hv#|7MRkd!2cZSQ{Wdq?Lu6g?z{ih;_VL@98YJ zHtRtBA2+REjBZEU<&BOQLA;vnHT_q1_Z!vnPP9b*SwX%2x11vdcmI{=*e_PAE#|T2 zL%nfz#`(!_P^fxfd}MaVisxCe+e*ol_iW?wpR{7zh>Lw)%t@(tPdRzF%C{WkrbH6}h+lyj5N!fzIPIM-}P z6hQzmC(eD}EJbHY>7S|y3%sC^*5AJ}AF6?zKKSJuB_b*`-CfTBb8I|CZB=RRX+kCB zl?tKYlS<6|nLSe^pa86a0D)a$yG_xYhmez^t#w7R2V*()-!HxWUoF6{0fa6P@*+SU zaHnHJ|5sgiS-dIt$y*vj)uQ4}u2*nLYQFz@KNTbEpSyl9_6ZiN+2m`~nERd%bRRZN z=Ok}>tl6~)s)Yg*;6%WRJYPq*cf-JGtGEs$oUJstuwuP|{=Gll_2EiXP%kq^x``1G zfa|zcS4^i?B^~Ih8_Y@r^%i002zG|kEf)g?2?+PS5l}hHa^<5HU#)oofSa}4?;XEs znp*M9qO?C#skx1R8Z?mw9cwHf8>JNHe$+vG7{O)eEy< z*ExvessH|v68ib$J4plw9bYnpnZ@dtkLv@2?vwTQmzry=Zy<|gY5F_oAOw;%0T6Uu zbB}CK^LDphm^Iub9j)bGt2g>=0=^sSaUs4zB$bPRjrOzdE=d_*D zo?f=4v}Vi3tUqSB*bK2DE}M?W6xvR{9~&K$j#>*|kNP<8J$gN!cTLMT%4e{)ZEq_D zu(@!asVEUhH!IsD;nMGfYuLp7Qzz(WpDGTxppFeTJ}QRaP9CTRu2k;KQVtvZ-}UZW zPZ!NrXI>t+zpvRvY1e~I5EgxD{5sA33H6au0LhoWHhW`F!2zY!yW!%sXJyh0C6jYg zkYn_>_o(s*gm+AC@cipnvH1QM>FOA7s*Bc~OyXJfp@MvN$Oc}L+R^v6uAjIZOJsIbAzGyKS?q1=R?8fg68Rl)0 zUr{Wy?lKDolG=9n<%>qxAxe86S(uIo+G2aEdRT5XNQ@F95&o=b8U^R~W`I9G<1_pK zY}^P!ftUbD;BTzX%uhEhYK3)umv9BVm#nZ5=pyWn1?L+hC1gK8ZV||D3zk60MLKLV zby!GZrC(x#~OwWrWG6eook069Ch3$dCC~_gAZr zEt>v3m{a1ruo0HCwx9BAu)r~)Lvj zCYFiB8n^m!vrGE+^P`WdCC=0S_KZc!s!IpN4!11)?Xv-^J&=C8IIcZzJhZg4dUX}f zwIG**gEHAcpYA0yA3EZI&h!*I8z57Hj($CNQ>hANO=#!=D`TdPdjV$F3uwAy6aS{J zcCK=0+$@_-5`W$qZgRJIoRIq7z3>iGt(oqRtsJIfo^pl$VdwKD-&`NThR?Tlrt4(IQ0v0vTJZ@9j_$qX!E|bTDl61Qo|@c3vwy_Nuy;thMB4|&(!)xplzQNEj`r0tOA zc^0p0DH+U2)-PsqW4iid_)o~ObXT@6fgMZ+NO6;?;aJTi?%~6SY3@xN|Eeh*77q;S zl6ke^SnnH@ZdZq`j)B2cTxUWpI#%vC9bJyT>a(}=))kZJbg=(;XO0;j7SB|tPjo?wI2hAAO%rZe8fMW)$eK4@#bbqzueC2e-XmER?9Y z5_uKN(yRHODQO+XDgRs~@z=V^d1u5u99rJ~2u}Oz7PtR{`0+fJ^V1xYyLML(SF5$T zsD|e3zj)v#qz-?@Db!m3%v)qHjPk%G{WDhi#UY>fZOIQwnXqX83gjI1LEN`!9=pIr z&J4C!n*)urts$+{vt-=n!n;G;BR)-#glo2LRw=@Q8s5O=qfy+0Qx&Hu*P`w+L)w#( zviATB0hK1Jc+>doza#|E?rRY6(}0SwrlrXd%>8+xg|(=3&&*FANA8|(`j z5m1nPR%sXbs-AhS;MlU#RMIa2XYeE#wUa~k z*-@)gH(}Zb3JPt@Ij0@FhdG_X5fDhVg9;eb2ChMJf;@_ak1x41b$+_@x)iP#)3>K| zv?<|_kN^xr$-mwe&66PNcbAVZ@`~@$#>#fiO~z&%Y;4(hy&>c*C^Spur+!?P!yakN zJ}aK#mR!cAeCi%+Mk;EmX(8m)0%l~A!kFyrQGOk}`&;^PclS+(+5k68`)lS1nC6u~m$lIWOtx2NY>Gq8q2P|kWs4S--QZ(71pb#+qjO(jq(llDWH?YwtQ*>N) z8+zK{Joowow5TnVqt8N)K|C@&~vW8QG3mpxNtg)3Zjow}8T~RA8wLZf6vN>o?*Oe+Eh@_5F zm{}0(0YzW_vcl3O&!wFJA^SKiJqF+IY0&kw3Ckx_R04F zNfuT0&n@^8s>8^^aUBKKEVQdov7CqbH(lHk!LHvti#~c~*sV2{jE&kiI_3sp5zpe| z%q{j;ndtudTmvO^w0{kVH)RL znTlthZT6lITBOu4ZWBZw*QW72P&>Y5Vm$}so8rN@6L8yAwU zApX4E$Ea!eA~eJ30V3OWZ6`SmO7Ze>X#%jnKVR8(T;RZ~kIC*j=78-vxWJoZtcm*M zggdt!fg%$Ts5zZB6s_7~m8a?ST;gYBG{qYCmp>`Bsu-)LMb1g?X>Krpwz^-A#-4s} zFMJu2Uhg59t6#ANagz4pUJx9CEJIL0B(G09sH8{?Xu^=Yp<7PXFBh^f7oL58S^Ix? zmtM1i2Svc5E)#SD9#=DU&t;_lR5LObI&pwkgmBL`NFN)oAV>Lt7o@B{q6fpWm0hPj z3Vjhk90n4LikbZz99~(j{W-^$q~gosT&s9GaNF#AC7jh5Y%%N@an(b}z{&6%_!}?7 zYTkF<<-WyoV+)O}&`-=d)H-^c%Sg#oB;Z&tRVkkiD8AwWf+Oh@RLXIuK;axA@1n-$ zjQv#G0xH>^n_<-ffYc<}3=+yLhAh1akMKa08R*40E7CVQn!b6>W~xdD!19G@igoVjg6%;ikMIXr8=?|){4f9+|p%$H|9DuzCsvz-7!Vq zX<wzOBK3_>iXKD;v+KuWG|$mH}`49(h>H2d%+s5{I~9W$iS48OizDw z>O=-Q!Et`=mTd(JiTYA@ua{btt~bw-qt@Gh8b%A-V~AJhu4_#-xHLobWMSFdoOvMs zJzvU+r3+Ws(SNfugc3?ME{+q1zC5s1yaPK=Bn8_JXUtI7#I?J-dnWdI5t$1I$)*q) z1<*nR)k0qe0n{sAKehPhHodU14HXXQew04;P5Sqb+AR(w=?urnN0(@^nc>`T0k))N|Z^f6Vfj?fc^*#Jg;`^Q@!qBtR!LZ$7a zk2H0Nf6{|`F%|Y%*AL~VLk1R+gfG%x#6O&JGTGgj(SK$Uxm{|rfhB-d(vx-6YS#c+V!>hs4fH*n*nzC}Ytu1rorf~iNSXd}i1 zWL8X+j~Lv12wBeyj!Msa#dKZZloehaK<2>lFxQ2SOJ_}m-2;BY zcF@`@0lob5gZU|4mXPbhJMZmb`v^rWzo3F04(Y9vPalZOfcEk88 z=1=C};6NBH4lhc1?pJz{&PAv+oC6hZiz^&J%Ju`VkckTEE+(#B#l}v6q0Lb7HimR! z{(p<1K6!gzgK~Nys54w1EsW!2;5w_FiG2e_QXM|zJmd}m&;bIhh2$@F5~`D7Ef(ix zk%FLR1t@s*fW#uoSkX*(%Y4`e8_opNprTksz?}%naT>?QV)w?~$+@|?kp>TOn&k6B z6+R}_0_5>gj4CD3TK#A<3koPtyb47nxYlkz_V$Ef;gNc6XfGs28hzM!`4cVz^7Tv< zg?@ba|I4$wv%E+M_~ti_Fqs^0-DsWw_z*5=dKdE;2dJ9rpb{tzW;`H;|5VrR!=`0) z-35^lkuyfVt6uUiz|Z{KEo?7eYgx_Mu_B#3RFf?;#?n#Sf=bLO%k50h z9{u0_mYE}NL(w@MmFYOi5$#WnEb=jv!whS4HnY3UwpqhH> zJ+2VKPIFfBm-)BQk{BuNL<(EMy`WN+XLvPNf5*nx3;JS1X<<7u{^Yh)KT=bPf`T|5 z=z*W^h#5w@KLYE|b9pMrAadZMdOgUj7!(I@Zz)5m^!xLi!j5{SQwY+C3%z>U?tKV|HR5ED zFIBw^F~g_9S`!ONsE35ohUgyJPOJHv8#t@{Z85|4826*vk=f+%GDv~`dwCEH{J$Hl z0jcJfP%RDpI5(mC*M6j-;flTgamj3}cYZ!zM~BRnWZ1E25bbZ@ULoNN+UeighsBdI zozM~o^~ed(sDL!01j~V)_?vE8x`vBu09BdL4{EtS@z4zF%=blMO|+r@G9D-i92^~= zCrRE$#?&Egnu8wauZKoT_G@Y$K-TE->({R<24g9R>d{AgD!JO1LEq_EiXSi-9N>fO8lI=z;A6f(xI zjS-Mc0WCHEy$DFY0K#%(Sn9^*B>P0l%JfrCStGw&dPinR;(`ZWRa~L6 zl)irFv3}>5R@Z@iZdA?e4Vw$7vp?S*FU6ey4P`?1koNCiqr`q)J^$A_ z2C^IL@z5WY1=BerC#rt^dL|)fV^aXVW&!iOP>lbg)M_S0jvR%0!*qB45_~#y8XBop zODtXmHo|BOD#td1IM>iIJq#3TfJ#UM6Zu3U^ik*K=S$f--C<)gkSd|;wdE1?sInkFH-7Qf?ye%x6U07x0^cb5`$Y<1 z=PlzZEVYkVc2N1x{+%HVVORvr$G6TNRPgVEmX?*JLp?d2Q0x;=xI){x zba!G%2|dYqk?~tT6V{`K>+etBf@f+O9Tgm$f(vC<9MW%h7!6>_0fWzv17X%r-yk85~rB_O&88PI!r;x94~007IMh;O&Kt z>KveOQPlHSkfWG;IS8V&C7)aK#BnJHT2iLxoaM z#@Yk@R+$<&C;?6cV?#qafT2V%@^sMU{+szK>P;~5Lu5_AKyq0;ORHKF=%Bly+vDl3 zUgvCaNCv08}#&3u_iK}00+M;+xYo4HtKjjH5{ z^1Y|KVT>wrz*nH?OU$VY%@s-X7%1_88^>99;K>k97w9XCneB+lfJR`6K=tStg``Ik zFB}H7Si+dorB~ZRJPqm^hvD_6pV6KEfSa&*4%gb-PW+-<5wv)8dPgWtr_pZ#`hzyN zw&dXjRc>8Ft*h&ZfVHfl1Rm?2M2R5i$l{fdvm^=TLB>@eL3efBp!lcFpQ{qr>h|ZMqKCy2kw|OkERAd2j znWL_i{1Sdn>hsEUHo`lZ8e}Nt7q15{G94HPg=P(KV~L?2pH)8$kA3Z*WhCM*%j zn5f6Uc{3jP4-CQQ1|~+nXiaZDGN*%K8oH_%YP@G!`W~R~o4%EauKG{H2)d-9PnqKV zTcR!$s`K(Y889@HSilbhEswT;eq83|EKpSQ>h8`EHu+L=$cD+^r z5W{IQaKE&Str8PevxvxXjrRiZ7!V6Md^Z~@`Ie~}d$=6L+!Du~>;;L`d zu?LxmNV;;aHng$^Lmx9DPZRZAKgNb`gLn!ULw36l%vCZ8x7j^sE^}ATKl%P4>J2Ai7~T-HK&gf;?4qNi)9dM!(diiK z{6Z;s;urVb&9F&-J@@mwVme->0gVNDb`tP;!m}H`0ty7tC@#~$_wOr(7JB>wqETOP zB=K1i%~r+&soJA?;o(7jB<@;qFLCDth8Yu8< zKu5osF;VLmC=k0(G?uH=SO8tfMAvZ^N3%izQ+2y^1i@7Jpg-g3axtMTkvW0t`5|(v z6r2iczIHGOw`_H!GA@3L&{y;}{R=HW%@cZ!B#HP&-#CHC@DF_S;rCO5jTKySLA~e0 z+^Tj`#snCh>VLYir{`yKq{4Cp3XQK5v1-CciFs!m72ti1mL@cQD>? zzM!AW39P?H$HpcH&BVEOys-E_wL_N?(RvE}`Q1RXI5Rz;S{%hG-cw2@LcJr+r~7}nNqKD4+g06D1*7m&6};lTVy%Bi zCj};c+DSJ?H;_i|4qteYq294va#G2k?!M+nGYm4`MA_JyyLZq@e49R;<;mWS+b$GI z#n(~z#yyKKu*nV*fX(JBG@#MJb0Vg3{oPB%sx@7fWv*33eAh==S!*HLF&YGQR})iT9zul}UV zhj*u8^`(Cc|{@ZHs(CG9q>zKl*l5x0oaI*K2rIK%`P6J_xkb*b zc2L~l6BloCz`&q((z5u=^-Hs*2XIt7#~d=qc12q4F(o`oTFlt1d0yrucz<+t%Wso8+Ug!xvrh#BH<$Q$knjfwxkIM8;~1M&hTDys%L7+H|KR zLop`hJen@MMw#d?2j5(z}8{>#E?)fcPcEdpp0RYPqBsR?k{<#!sa!)2sU3v%_9J z95&OJh=9VMOIS84%h6kLG3TQy8A6K@z-~YcC89khJKUZd5*#v zcD|AujmfjIOe_7pg!grQs!kpCkJr{bJ{Wb&RxeHb!K3MSRW}+VmjYW=IX-=n8!*r-jEUIYrc}r2pw89?>IrPLv^^LFv~XEDSf z(*ezbPfS71V_!EpZpghUS{Y$^U;EH>%vCU4`_uEytaeM8lTJg>TjPGS8cK9`jWgmm2k_BHLvygHjc`25pHBssdyIIxuaF{LPD;K;&P7* zvqU7PeFUHt#3b_O9f=28>F#wc<@AP9Jw>~^m8sL-1&`Hyf>jck*Xihgho4D;_|Ot} zrZq?&JiVi{<6@xJwBrffVR`ik8~Pj1E_3Q_KRrzNtml$N@tdr6I*V#ydKRXqF4SeCtDM@H;0`d zvNu#Ps#a5_pbBhO$flnO|&H6XLPIGE)N-_E9Bx%~y~_h$m8L|%59RHLLY z+tp(|CgsEGPG6?V&Xhmn`2>B(m9d6c@sQsrFxuci90M4OO%_FE2K?jrcb7NmCl;~W zwF>x7l%>{qInhRG8^Ud0sIV9NU&L*%(3`W!I_foN3XvE2$Ib0Zq`A5?vKrK4Npuge z2zROZk~A?$>4U7&Rcn#?5*`L1s~A(hXFIcK8)Rpe&8%9k8@bq*O+jE_5OVJ%P?2%} z$>q&PA^(q3{SD+%(8`!OJ{4H6xV$+dxpi>C{E9?i zMWq1V$mw8M&`oUYM%ma`cE_2exmy-?F4Z5KcR?#80%uuNZ=domZ80jzXi0P*X!3L1s)$Oo`mM&D9Y+eqxhnX8pJN!CmssLeG0>x3#+Vjjr`G} z?@N+CfcL60!`r*e)^o+%mZZ4Rym&iSz9$FaBI+$7J))K%w=py=s&=0HZDq@fz0kdR zqO9E&Cwz~7|HDwBo7Ag6xCowUSJ@8}Lc2t#_d2WfUo@8p9JDs>DhR-u=8jRU>#Khf zGhBb7?og3~i2aW9av1ln^$v7&3iK_ZQ2%=H0>*yu$!2d=0~n(pKpG4HE@%6k_cb1K z%u$K0(xSCPF#_h36m{ObrM@eiPj?Z4aD*cnL<%&%%W3de#mp`V($VjWl*MEKP+8os zO&KcDE9NpAWCBapml3Sg!@an)GMm%NeRO&#L_;Hbs^LZ2rI@Ucdn@L&dximEgCZBX z`N2$U%mnN0){s!D{L99hv#fD=_t*8mn2qB2Wqn(Wg8IjC>#)hw;lLx8NE#P*g&E_D zBq#HNo*%+A46@8x-a|@h3S6}-dQy!tYZj|N+&6|9OXqrbGpMsQ%fDgXxUt#1m;`ST zwPJ5=`#d-PvhDi%>DXF9g>dLl`KhN6iJ(MO@d_tVz;THOE_vi})uYnDt7J;a3d>vj zdoCtgNlK?eOM5)_9VUT^H8sIZ!Iw}ERL`*fbvi04yIgF0Zi2zV!K4EhN2@uoj?q-Q z)8B=Nc{BZ+i||dI3=h(M^&3xwoV}cc&`t0Y|6}sY#AVt^110G+(&44Sr3yczF)_YZeFYm#!f~lb6b010l4g6=+PSSu& zt&=|qd)^;hU7a;@t7^o5CDAc;tw`A_4#t_gfBh(>Nb4vq$&s!lZ%vS;P`db((@L4# zc4gh}_@9Ak2WmD7UisvK`Zzh~&a@RJsgY6=evjlMylXE6JC<@Ur%<(y$5nH$b!W7< z#U&W%M+Wyfwhzc-a8ONzNR0R3(=9SGW(g}>36-y*(Pf3{^Nn=w zMs>+CSlHKO?~6!1*T5xv-KDd&NIJl|wJ4XYr3zfC9z`&IIr@QWq>OuL+NqhdDUl5xjA9)|1$E zG4p>%;Oy*t8C~2NKNsG8Bn^|4!jn7IgC}9k{5>8!1 zEFpVc!ahH*K202djCWuokWJGmhG7a(yrrS5Khq8FaRbP}QBYFOOa~vwIigVAXAzsO zxMi(ZjO(Ga#EEL1>gMjI0ss`>xZ{jZxQh|#TZ}5X5BDBF3hCjFV!ksTJ}+J&7ecbs z8?hC6IP8MvIoT=*K4}YPR#8Zj!teX)HVSAsHnLzbTkr_ixc!r9x>7kLbku_x#OyRG z$x;z?t$pxQEWcr+HB1ehkl7ekcT%g;&EsQ-wG+y~#0p2N@D=Ak^Ps?TU>H!JV`UaxnqwpC?qY9t(vuZjVO5`52~0BARgLW~ z`axOA)mZuAoKIqF?#d;O|2oXAx?UbHB{)4&=XIT#U5x)aQC{Xi_uv4%8GeTxkW5g@ zQ-aFeZFHJeN_xFK|0VUxZ~6X01gAPfJbF7^*wSFHp2N~e%)VXqlApUNFei-ZFA@0p zwy%abli=7eZPKn_(a))bg}7rAa@3oYxAjPJPp+o9nbqyZT-W$XyznfO?B+hQFsE;I z^oI?E4o%RbioQX|BPEZcW&;yTuPM>+5w{I>Xzj})VXZ=X)nN1oO%=ArPRxag_3CP? z-#PLx0=M1SXCq@+VD`@eda0$WRal6}%HI0L4*d+Z1QaD3{ib0&e(M&=r6y-W7;^_C zk26*6Y2bFfQV^a%vhmt0tL@GH#=3@N@E4($^rRz5v=?%QS)R$*!-sdtS zh=>yiM7m~SbmBmOR#Z_#$5#JtOd`Q8!y_{U*Fp>-at%MlRkJbW+sn5Vi1C2XyQiG= zaG@!;2cX)w;fMAd#;a^kV_0S7mM=(I9=Z%@f=s%dT6p&L&fq65Tc7P!&f$ zk{U(42opR2Xoc; zkAFV9u{x_D`|@Q+Q}<+#-;WDn0fP4fmbiq#SN6^bRoTP-1x`TE-?tiQ_9bmdrmMVh z{8bPqc8=U2E3$AIh0-{RB#0=3AEZ=*Uc6R4l93e{Rw(54I-!4EgH2i6z3CA~pYC3Z z3EfVrP-Wqg(?Kk}NwF=4wAh-WizjFqwdgzbl#-9<-Wr^sc(TBexF|LMX;H1Ia9Wnp z(247OSN3MB?1swr!2NQ98(j~lUE;dA&=Sp`jkb7!B|UzfKgphi&|Ym(~vMHvlv z)pD_XeG{|5Uj5IC>OCUelT)!EeX~Nhq;(~cdE)}EB*iJSDm#ow+@f|inAvw0dl;5F zCN~Ab`)ytG3Ej`um^e-MXNRz>)MGByI@QrAU5^$#P`eO`6Z3Ac^qWtrOAwxsVDaNP6T4#$WrCuT~Mh zVB3wmXTNE00(nYvXXhEnF^DBbr{>7zx1a5wxax2<*G!_oOzxJmC@v#^_}&;CiJN1}Z#?aQYW*G>5p{9~L;xSm!Bp>m$j>fxS%YmNYGh{2Z32@7WV)iK1j1-{q$1 znCwXHV2&G=59*z9rddhgAjV=PVtqZF^M(?S=dNX}pBc?WGIo8mA$5|4{mQT1tb&pH z>gwVAk@EPu6J18k+i^)h{%Wn0m9`!KbP;EiS1fbYuW_(Nn`1zngj49Yyu>ZTaod+5 zuK`Jq+NS&y=rg%kqQpxq)I_@lCFUY|k0P`sP5`TQf#oPLI0^7C3xYB~J~$&`TMGE2?Jv}X*`JOIA=&s`^UAL2K8trS z_ZPwYP$&29{ASod-3%8)GU%Dye76E5qFMnd8XKOvI=}XXanM|(^q9_EM2yLp?ZC%^3dZ-nhl> z9Edv*jQa6`odVSdK+DDirgFc68<9go&ul zPc!QVUNNP~k?|QFIhm3N5{M6th!0 z1Q$Q6R(*6zWiwXsbFbk$OSKKJC(kB?OtTX3HGB&-tT7d~LI+Q8gnis0UaHe(xmz(GsY9<<0 zoZc!sSb{xyF6z%g=b<8vR`LuM-e=QLMdX5o%%|lG?Gesna0KA7nXLO*LL;`mUqy) z)g&8yCN?wZ^`C?O+smqmF|-^^VZ5NhU01nHrc`F99vmUHVzNU6dNj*L1<8ZNIiqBx?@lJu~Teb{|zWEIF{*@Z3naL@CkmOQ5wz9v-z4WvAVk?3MEKuDw~v)wM!% z)4|@f+p~wP+frWZu{sn&2t3(t;`<)NOlQxSU{Ch0m``sB$1Zc1g(`1Aw_ca_6bgX~xoQ6(i_wkM;-Z)35#{ixF{jc`C(E_9;sU=HVfGV zoB{H&QBfHatx35EP%+DOH87YP>(Edk=JWj#z#~F7Hhj8;cNJ&ewBG%=%g2M}`!+DS zvLiE@r@g&pmOBV=5qr!}1tlC>JwJGth1 zG6>!EW{$4qzYVDNgB=2ZxHVZ`i5txm%&PtUrw2PdBTw&NRP zNl`i5vZ-U0kcF2yH3Xu&m`n^4=N5)5J)S&R1n`UpJtS3UQ4U={>Q2LU=gC3+$V!Cpw^+J94fPh}<+e4gkhY3Qz5wJJhG1v`j%;fg!_pG(; z;s1$O>8h%JTD~IZzjle}aqk>d=$vcmX@GxXwX5IkYYX!Qlc=DeAdnbQ0nbMLm&^{l zmMk6AZ|SF;>Sj;BBFWkuqx#4XQUHr~4>^zaahHpg4OH)NNq>v&(@cAZ)v1vBMPAtn zI^JD&`KW2lvP|sd!OlrL+ppk9b*WiTFqu$JMVuysDX8E{*0kOCObR+>+p~iMt(E2^<*Ji`Uz8RVv2f2E*2YXxf0HE#GA(0ck zc-{wBMVMKYctWz$LpPD@EjntWQ+e~G+LovHlgS99M!q7Olhqp`(>R=cEF zKs=BV_mNJo+MJV-DK}L~Z5h>ajllX4QpVPEx1IOw;qw93#~se|J17;fLHN*DcrR(S z^wtX{E&X~hlJ?-g1wtKhoY}IqnvYbvVQ_PPNX6%Zm72?0ZLPnJj4!Y?w92k*&CSiR zQpt5|TX&SVKbi>uMtX2suRFTQjNf_wG%p>FUbnHn@7i{WK^%OaW;#LVGJ)oSB zfC~idmqknb;YYORZE_p48xeM+-#(Q>dOt$HEWB1!TDakX<9(mLQJ~_dI?$R_qC*bY z&O6O{SvVX0< zEysJtGqS&K`S?~8M9KSOaL(dfX<+wpTG)K9o@3$IaB#f7O`yu^=ywWRtHCzg`^7I1=~N z9jbPVHw=+V*^ILK_x&>%K+Mu`dX>hQ2E=Ol#w=knVN8eEAQ#)#b$PW!A+&G71ECQ6 zR~B|=**yqw^JL3!^_D1OwU7AI%<1k`-S&Dd%HnBf`TbJNV;bJ-ca7NC*yt>3F7gq7 zpu7iuBd%i<%0+#BeW=2k5MCFtt$SC761XXkc4hE0vo+?qTB%IAeh{l2OiK+vEnNWU zc@;n%B@@EkJMm)JH0jO~dnz@=bj4ounDKmP z>fEVIXE_Z+oiY!dmMYXw^yo!^r7ptDgc>~wA}p<4Ksc}iFx)YIRM=hn-m}wL&cWb% zy~00%Qh7fd^03IW;3O}a)%i;WY?KNc*JT?^9yLw-&K4tNVhlNv`vjS}%0s%((TYlu z57!HOUQkBGA2i$`?Ja>>cNJR62@@fGGKHE9_Gw~mLEsueUKB_w$iIlWsLrYTGXr#Q;uS!s7A?B*(^x`rp-{afli^Y!3@>2jde@4=xob`mS~4 z*0+!-J(Oi2klchl`mMd&dS;@a?{Hn}fo(nbC*Ennwa@|~>r6}ghJ9b-E2L7-xDpWF zR0PgXbh2XK;@=PK4F+N_E<)V6;n90jx4^k7p#~UL30Ey@pwP&jHD77KU(oK-kn$9h zSyhJFx@s39%U?Y|WFf98GH;25(aC1zi2tXoYPT#L%N+B*O zVel|QY=LE9KU7@gI!TTB_~Wf$3MbAa17oeuk!Qvmey7ueH}qsWMLr;FNhCHJ3|8^F z_sr))r&8ah(lt%{!lryxM$Hr;FM7gB=~ZR?EmuokKpByk=HymjD5|#i>8&SDa#P<8 z*KWP0{0}Yw;(;I?Zj|uT156XCF=adVkS2&FJ-Gh7#fFvHUPZ#zW?6GIKQDL>tChyg z0Wp5NdFP{m{`^VIWv9Pd+fb7Rsmz-g!#IA)ryCyK8{=20iWySP)5!`D4#AM*)Qy$d zw*f}S^L%46<^R^$r?PQ{hrh=7=1gq+T?8DO9ytea5)o^nGB;Fwr#|>tsJw6)>{Li- z03)3OG!xms!F4*9Ku?lb6jkJg{df7a1zP~L2c|9*ThYxgy9Z+AKGnfcb=(kskog6vxMo`P)Mneq$2 zve(mZ+v#RcCnA|Yprzkd3g~{-5YII6!rT7bj~MA>NWg8ug{&BRPX8rSMw@JiX=E}5N_EohDvI*XnbyC01t zl)yn4I#iVbgaT~9>J3b0_(H{<)tOx)!5%@*q<*(p(RUg)P_MpMo#cLe^=buZ4DB5B zb3eO1;Gn0$054_G{fdmr$k=iox$k4s%iFmnMq;PkIMQSsM(&efPRlS7}uT#gw0kn^SWJ?1*ar7v=LWJA~VdYQY!9#^aqZ` zv%UU35X}=ZRp%cf?2F)W6+3`V%RDLgf0C~Ax0)3jDn+FBmU1cDypPnzY{d}_F+J}d zAl%3gSL+d^D)J|Xe4sH5*nb6hp4x`*0uy@o*5O;O#cUTmJyzvTFMo(O=%HSCIOaOZQbb=Hx{%){z*bTw%H*t*Zj+u zI&~KMf|55@)87<+H=lDwo%tLu?iT-$iL9F<<3nweUI35{^?~{2Zw8~kzduy_kE6Tt z*Yn&rdwc}+|3sZ^s0ynta~TYjgh(6IbudxdUyab5s9&2q;F?G%2tHs4E55V%=uM15~qOs}}pdcMn`WJjy)nP^i!|2&gDh3Vi^;d{zfV zi5PBTu9hOaf;L;bs$Ss%REo$S`nzeLd-h2AOg!PHv`Ub5fUInF=|sLU0EL(x;L=>_ zLTjqL8R#zRK`B$_N+s4KMA2Rc9}L^aA(jcH$I8BPJw)HJtC(nXjFnXgo;s*9s(`>k zIr1)Kb~ePBHfUQDv+LppZ4Nz5^iYF}bFHS5hkbNZ^{G^=UEW3C57p&)oLaRVubQjk zI>L*FjfRoi{B;ZT09u=gf1vI^M;7yk3zVr034yf}-{LcjD$MmQyj(oBV)pasrX>-Y z-w2+IP<^OZcm$OaZgj)T{om|trtl;!)o-`ZTA<@VEHshUEi~?LW(5Y<1q7pf{LgJg zMSI2u4a8()fbecxEK(g2yENXE237c`p;m?P@NXJSAH-oIAHU%2-%sYhcV}%EQrd?8 z6~zFJsIF7aHEFOh8exAZ>%xnk+ey2dBt531Jr{-xK#nW^SqduhHR#RcfocOVn$tn^ zCL5Xx4B>F0w1ScQ`fvn>CRRQA`2-X<(&0f}6cyEhga>HLjgLXfvdoue4`(13RN4R{ zJsJJ+eq?oZHS8;-A!B`gy$L>mzl@4x-?{jQ)C1?a z{jjBwk23}T361@iETPs=9cVE%gG!c^Ui3=Bk*n6< zPJjiW0LhaQIG-sKCRh9gq|2aVPAJa=YTTiZkqrZ#IkuoFi?mLF#*@RG7Qnv0J3dhN z6+mf2=kDFevKH7_|Bp7V$6@ybaL5tG7_QngY^Q!q8_3jX8?IPuFl!ds9lT@hO3=b{^V;K>Ohi zaNm^|JwP9#Cr5*eRoK`7ls}u3r7NI+ARV;%zH&wN$)vk1Kb|KO$c|>p8y9#;>UL%z zMxq5rFp68Zz>tVr{%05J{>*`1VdOp^j|Xluyga5lK3Wcf8KZ(`$u!XLH{gHOmHIDV zazS2ipxO)1O#%I#4Twz;8Jz;tT0=l*r95Zj?0fUEFe2_t1<0GdMBatlWE%wptCWj* zRR-|D%vVbg)da)F4TPWD&ICp|V&&86&g8mUM2u&f4)59?q^Llx@dc>%@jv~zw4E1) zkyECWbq~bC3cLQU-8gnY%V!!$eK^2#fo>V79cK!g)#ZcA(liK*N%<^gHZ4MXtCoR5 z9Q2r`fgp$jtTeyDySE^Qnm67OtAYW^!a{hjQ{5W!M4-MMgANUlR>MM9FZ)&N&~OlW zwS*Pl248rLgQEbn)j-1VF0^(K-#z$T3Zfa73k*_198vWGewzbp%vona2ukP|M4u&c&xcY?-lCZ4QRp9 z2JM_999HX~02W0)4qFo%Nw4%nJDZrKl+-k6hDy0jgmT+KSJq;xtvm%Z2w(MDpy}Xd zRWQ(sWaX&}lE0Ybyl!Y~0@1`C=yb-z?6d}F5fF=pH;@5ip?Le{E;rFMmGQ~Fb>M5U zw{{xA`r_aO=7O9Qi1j8+_ZH-V)DIRk!}uY@L)iSF78+Luq9B(Btwq^kHNf9~d(!z2 zbh1ix24L-+McG@!T|7P2S7b1v;4+DLGc$-7dthjz`}S4g8VcJPV`~Dg zV_(YEomGH0pX$g!(|7>i;`rUc=lBg&pg`(Y%!AUU0L?NzahENSNDEra&Idkhd#?~U zk|gb={`cR1^A3tT59h-qoLXv^%HmJs2u|6N&$WC((9$B?nILFq291K~F4z-Jo++>` zv)*%oew3P!@%y_lTL?t1UxL%&LoYJ#fp)MxOiTj&{LPs|Ah3Hx#>_P!y2~M_Oay3Q z(?~cEp5|+7s~W}~I(=aZnIfpmAWs9fa2CV~Dj1NIRU3eL>R8xa$jJeFQVRMG&9ND;Kp%8ef}j%tM55rw9|R*26)Ff?1vzBA@4``1`!x)eEqhX7@{cJE z=ypSzlE653KwBeqBGEZvR^r5gX)=NEHJl?40u(HW6|y0_*a>a+Em>M0M7iE@vx=A+ zgK+nqUv+R(nBS=$>fL3S^@RjVpvlJz652lA-npQVHa%E@hgIck+C2IQPHTZd(M<>e zqdN>s>I}K9HCJSM9R`Z z{29_k$-rZFV1bUxID?zXB9&)~z+=ycP1;gluYjoCZ9({_^$JODP#@f;77rr=oaN98 z1U+uj2)Tdf<6Xc`Ooe>@|6TXwg@V>E~laFp+59N_0(1 zgc27^yx-*Ts_bN2q(-|e)Aewrw4F!-0OS9&pV}0|sKbm~$dW~5fEly{rPYfMRqoum z>+Stsl{X5cqO@ccK?{xciCM%_?>0F3B=gMv9mn%w6bHM}5LBMduU7l7yW$2@yhrvK zDZuNj}3x_~a8xRF;)XS=9%8z6|LXh*o9Xw_8V>( zmyop{;00qL-@9awAusdl!p^hZ+kB@8a}$^lAGYuAT>5}ciM1LuOByojK!UcVyWToZNw5?CpuFS3;`0XBa56MS#%?#`7SJDw?sz$dm^ zgW;Et>PCJb^FMxY=FD|~B_qD$!Bo0>d9^=*#Y0Il@7(j5#5*mcGx_C*klN0EWLIAo zU+mG=kae#HbOx~o3XqSXT-pBpGtE+4&6o?C=ZJGii*-qf8yhdOJd(ecr@!$tr`lu@ z03wfjGAHo~Yozag;Q;LIpvslR zR>gYcC0G9alHZ1udKNhPn=AnFK(YMZevQ@;aB=(j(KAzPYio6}T0Fat!g5;%{ri@` ze*Sz5LS^(uB{ZuRmyD*)BY$gA{9SsH1-Zjx?Cdz0eK-x-T9xZ~Fi>q_Onlb+_t!Eq z{)p!l6jTMv9K#JQw0f{3_VVqz5|NRS0qxG?BO~WP;T|z_2!559m)8l+t&$CQtgkYm zQ2j{!QwmC!NEidr(-o;h_j94e_aw(3jR0SJ@|)GwfHlj?R!eLi92}gwgL>>3z5|mO z^_MxsA~6scR-S@uPS7uSPJy`vZ*Z8E-}tVuFks{Em#$rmTn{v=K&O~;;tWUkWn`e@ z=iBC_gM&l08tYr6f>zRn&8je^t&ZY(;6JAOiZWo*lEr;xxL@&|J=@53h)XUXFc&0G zz#Iulgvmf!tyN+=S%wOgRf}uqIOVIoW_8b!pYV_9_f1{6{-!!mNQb^HmFtw~HQo36 zD26+#DHApk3{iQwM5BpaJBvD#n1<}=P-Oi;Al?uH-#7KM7~`LEZ8 zhBKYuKYR9XXnaQ>Z!_s>moMlpSCfv8j64If3bmU{ExudplhEwr>@I`4zOu77PE3Az zb(CG&Gas_sb8uNj4WjmoZ3h7j5f*qzsN%H9kXYK#r~7l;0xr}b7*ZhWK6RJ%!rc!) zEMZw6Bj@GPR~RHft3Pgb0m^7{dzf>TQVaLzeyDLkJyselw|l`Bz^HvV6s$8`<3oc< zHn|XSDK_jvod`kxxX~BJH6bn2+*rdfzMh$ahpsLz;@4PFXZR{%Rj$e<7DP2@v^a{- zfO4@m9OTgOJ*Z2EkV6$&hBSq91+Lw|^lUxr^)1vs6|c(sRQnHwbud0q zg*OU~qz)-Q6DKn){y>FZ#>BUR+kA7c*zy&K-!Z}8cu=;|r8dosf&nCRzbYL^w7JeA z<}_@;7NP(fjJPB816TcP=c+tNm+Tp7L7)V$MT7$ksIeVtIu^BuVFij1?4H zJKwvEx{rQ6h`iO}6FV2g&ToD4nQY_G#hRkq34Ogge+8I^dM$aHmwfJ-J8fDQ5~U{O z`+|zdfw#ngxU3>*Ve8aw4o?&Dr`%k3p{}T<$f!?CysJ2Ungdj5g`ssHh7rM^?AS!h z1h`W~hZ2*iZ?+&PA)){D=vki2mx~mYP^iRBwt~1^@g=r7NrUv=4sjum| zP17qad&2C6EV+Y~-e}n>s%E9Ru1@Lodqm&#*M-JgFSde0OTAa@SZN~Z(9{Z1>nRQ< zRHOo8uO6$qj5Quc&Ogk7Uv{cV3++6*lmc@q$udQ7fxJp0F3 zYirkfX!tWhOK5cq7m?*d8&=H|6%`+)x%| zQ+bnQ^WlSp*!>@g_go8=n$+*zKXs8tY2)zYMw^MGWLXO(@Cys;K~aagZIP`^Q|8yr z{zsitz5p5z?}-pLi#uPVQ1PLN@%b=;1j;bz2i?^zlyKtI-VYXh6Y8Pdl=wFqEuEb= zb}Zl%%@jLYG&d#IEKt+mzvd(Og!nMzn7pe50l168!s=NqZ*+=A0+ChTTgynp?d^FT zz4ljiL^VPAI66s*7F85AK01$24cbsh4cMq`Uenb(Noe*hAxWbbDhvb-S3WCy1pXA$ zy>QP&p+*I3T*U92re-6wy_iF9i|ML)8O5s_1uq7&QUyeTpiZ{}Q=Nbne170`8c1?*50VmAFkQV<t2ffNzFh zknX|U^927a-3;@9u(kWqpFTThQW(9aFSt3R9vL{hFW`f->>EjW(Yu26wR2WXsGnC` zoQk(=0|u6`lVhXvsig%ky|9^E0guRj_qt=_i4u*cHF|Npt2OgT3I~E)w(h4<*Lg=f z!5!Xr11-ZyZeqUn&=2m$mkR`jW}aR?dZWL)`<9k@QJzM`ABOBTBXx|=pFWjQRtpBG zbUl-rM`-6mQ?y1mf60JwPe-x`*`eBNE6Ws5)An9FSlc_wddj#d<6h!<@1`pUr+66(ncSTS1?D%l9fMrMvFn?a%p0y7ry1oB zqIw;Tc|n<2hn$)+bIo@xZlG)`_vK3?H2#?>3vuC;IA!%lNuEY_C~^4w zAP@Rk05c`^Wr>u_;#Q|REI?=8kXi2{Gm|?=>>WHU1NA%CetV&Vre{T*MFMKQ--IWV z@ysNUfceU1bf|DzTso#|Y^gl0}SiwPUw>%L(GRrtYvKS7FGd)$&Wj zsL`pP7QQ*;GxhcLSU#$$L-ZM9)I)!AZlJjk1~P&a4P?b8{Z6{93J<-44mu z0+nwKb7Vsd;4#;GPnyNCYdx=ly8PST;JG2iKwfI=8Z0eA>X1CL z)w==<2%E)U7pMP*8$gPNAKa+N-*)U{Uk{A`I(F)mfP|L>c>a#5YmJXL_bl0c|8mC1 zdrbF_)J=4r9+L>#*w((M@2CAx`7^+C$EL<|&9@Y2A78GHYrlI8`6E{4Y%G`0A)Dhi zLMFAh)lwdEgV~JXH_0WufwWVJ*` zgZFOU6Wuwx=c-PL=fPw-4@H5kfN1ISb1{Zo1~V-bw_`_-=1oeMk-pfCj+@oQB`saU zDz@4izPBv=W?k}z@op*2?L_vZ)FIAmf@ojo#=#|lX#pmtlA0PB;$r=z`_BQFWP-11 zn{XRtSUw|vuEnn(=N!4N94mAYEfus?1JC7v`fgOCs!SEcrH)9LhRK9m3_bYdK%PlO z_UY4@Z8w*XUS>QDNhyBAhKS^zMGHQUN84we`b*WejVt-l!M=@+)zvVB;_WWE^##Ay zmO&MTkafy8N=X&#;rNevcTPFD=F_b*xU<#_Y9SKOiIuXQOohVHXRhfrtrrK%^z8%g z7tjd2*7L*)zhb9Ag#gwgo=dMA?zDRxTM^29*Kw(~GXoqINt<@6*Jo;Uv3EXI^FN*~ zXNpe@>9dK1^X(}>_xMe!N_PzIKZJPXa3C$=m=iF%<3nN?x zN@(A{2pJ_%f713JA_}?0%$CY*J!H0Pb(x#?fHN7xEtXIFb}PbX`U@pfKBuCVvzN(^ zm5U1>$qa3;G5q?C9Yy)I;V>~d&uqb~j8Y*qKpkX@hs2-uQ&M2ru^<(P#BNm`QGl;Yx*C3Rw#mpAOU++!{pDQlU3^lr(XW0$kc#Kqt<+T|1D z(->FJ$p-j{7*P)vhle$@JUI4Alh~VWKAwKRZ8W{P{(w4)`X}%E@id z*0_6^Kxa07{W>DhQZ}c`=sVyw-B^_uT{^Q@s%n#xk4~0`hJ47p`cWsbwR*uMPxAFCg#-P=O z3#jY!Uyusv{bzF5mjDSxp>N;N(Y`0Pi{-(S?ImAeJ<^KWUIJZ@un=`)%UX!fv^x;JDK4Ji{>`r#ndPJ?BZ0G3;py3!^70%f+naJOECNQ zL9BFAHI4zgby%0~=a|e0B@Ja45DPrb&E*xeC-9ooV5xpa4*MOu};iB_| zxV*MA%_p9My|YnIjRDV#)6Y$Gl!FOdEW~`1TNt3gr4}--HV+^eW!ElF@a(5MEC`0l*}u!X|wD55mD;>bJf+|RcF=Iq(se+Mp>OQHXDJoK%+RdeU_{8UZ&!OP5;syl@dbCtM*xzpwfPr zy7lyVT?Px&9{bOD0`_4Ud46!X@NI z1$o0t$1(_80?3|_y{n~)-K$qwQ_(87#Vc#*biCJ4vj0{6S!{W^KVf}+mR^2ba4Z9R zRbGw8pv1+U;fz7(TWH%XHlqOQkA=}=znhjomuu1=hm$j^{uCUhXyJ;ziC-Q_&9 z-8j4UoH9J4i=1QdQUzc$t;7?@liQ6721+}c%Rbs~`zK}wl&JXC_LhX`Z7!LW*%DPv zO;cPSUCOh)EMGgElPLbsB;yEAh&+`+sSICY-Cb%udZqSP(41~xMI<~l8JQ(q;Ag1~ z^EMr|B=M~}H-l<|kU=*TL(ceJy%!e-rQ^Pi(34k7?TP%dq{Vx(q*uFO=#zIhUr<)^ z;A$?^R_wc<6B1EpV%eMiqj^S>6WMq>ACjCQhvD&^8H7OUCWLR!G`hOkmJ#56@sW)N zrA(0bPwz1Jk>5Vkurp>0Ra^k(5PPe^Q?a2@+!nBb$2~i(SIF=D8{qnI{dcnQSg^s1 z3w7q|PI4i66CyBK&GI*L>~g%6vbWozv2aA>PD)Fvl&B_)-jbW&+OX}LI>0fRGcKPO z(4rpi>e$D|*d=4EOm4siJk;8lH-c^9U#o0KHe)L07D1iyN=FiD*MDGpO~XF*^@^kk zsM{$0mjnV8X{AMOuW=8M4xcdSBp` zZvK8H38`Ne$Plo>8I!Q^U8qKG8hei>jV}-A|MH{)qt`H!7d3YM_wSa&RpD-#~J2>mY491fQ1_q`N*YP{7VQGxKy+aZ0Ok{MhQY-FmzI@;7FFlT1wWx<1j! z`9KdbsE{%g_!P+ygc8g7<`TPV++G|y-Fs??o@b6_U!w76Jsu(UU;~abeleCTC)ueh zFL3O4m9$1Il3=ajrL=Gjbu;)OI#qFX3x2{!Nl^5^r2O-zMAvuW)X-!>uyu^hZjp`I z?sna;_+rREdJ}7h(F2n|wg|+TvOt^x?)m0Yj2>;j2V$U1>fCvVquFJQF4rxp<-2S~ zcS(>v!@M;>-0qX;`g^*=crnqicImh*X<8v>so=u)lRxZ|6fSGJ zP3qwdG~f+N1x$Ynm^-pLq{RUlim5HVIQY0AzS+ti=T%YR4$Gg1Cz}`9_Ol2O*PoGe z(kwk#7lpH2>v2=h#FL}S+)AFeH#ZEK5el}?_q$A!Z294K5F6TGPBIJ7h6@<3wtRI@ z)^Dz9w@}WnopkCHVF`T5xiaKM62RBy{`z&w@RH4IT@tiPamR;2P=)$+iFtx|t9TIX zmtA0cX_J|)`;UfI*>gTy-}kg8mt9xcuD(eYPR37*;dq@+ajdx%-~5W$O?a}pX*WNd zZ})_QTTwa(Fv`(NmGZK;@lXB_;(^FeN;WO=U}X6mUK zWO2u?gs_&e>4cS=zgEdxxw-BWY01Iaiw zf-|w6IhH!{*c6kQ-I`zv!r%jS9xoDPhA!A`cg&C23o2l4!R!uX6A5q95{j&}9Pc@-EIDdIhY?lp*dYKiGx zZDE`4$s&}^;lt2-$~C^K2Tq-S9$`MS)!67^=rAxQGWL^_wh)+<+BYd3sLOcn0W6}d zz}n%?xv4=rskUaTyfRVmXEimoRNBZrDbGNHu(5QZ36q4wj~`#^zwv6sCFsaFZm3$I zyH*OM7o^)#2R(Ud^=%xh$E8@fq|#?dg?WLWtB{z?WU@B4KrRB?)F|tm7x=nsWoUAI zE(`J9Ae@QW--<$IvhKiPPDmi~;K5IRlX7>h%Re>c=u|g!mxHWnXG7Ezozl3})5)iG zGpxlJ@9m2qHX)6x=u=xsWHcsQHAxul>(yZdsS?U(a#?S z(17#2Mu?DJ2mZFD1v*^K{MC;aa*!ZJ9h)6-oEzs`hjbo@4b$uDb7C$7-s{Ijcynab zahfFwY{KyZCH?tU^7#-Q`zJ)i6RyjJC7$4BiKyZK5oNZ{Jl0*>Zp{ynkLg0U8kfd? zEy@)hQ2)tw{liMs@8ba^+RM>M?hN+h{HW$k&vFk$Q=0EP(FfBB`L*ryk`mjU;wKI5n@bjRDDuVu1)zuV8>d_GcDqY#+3S~@6U^cp{-A}|QfKxl_wcoHF z!#s=+$1T@ZXVop9$aM-8S-n%B=cn)AM-wZPi zbQh=_A$!Xyv7*KD3U)P77BjaUlx%hvLih+7=Lh7Qm`n*@DrrD$hV`V~pn_=ccEUBL z`}n${D5qtC>Ac8yQ5*CgPN2W%tjCqj^hQ7Z%qTfAlXmhEZTg5AKes@QC4`RwfVk=^U*_NZYhl1~r85VE`raTPu)T;IPjS8s& z_;HuE43aPJF!L!APwK6!Jj``;pr{5K!ipv_RzfGw_Gj)%fcO0FS#hP1WPvAlAs!-(Na$WtRYb!L;x8=;2bImAcA zsWE#@^$DxhclCT}Xb>3t#Dmt#YG&;UNMc%N6>=rFlL}4sr)V&;__JZV47D~#J?Ty6 zfim*bNey5MZ>+T22Ho>ZmCXfZr;BwB#8w52mVc?FkmC^7$y#*>*?W1u!Fc_^^mJ|n z7_gJ;O)&|wo6XfI8;@|phP#)8B0QrKHfyPUJ-ZXR>Xw)T=`O73p;`G{DVjajdX7C| zmAGqe&8(^Z{S7cY(PY#aefFarHmgO>_p&1*F%PCYr5q`#b# zr`oTDIl`=KB<@7|b@se&fj>vUR{uBAagCT6v5E4N5jQL;<@9?oe0+Vf*Q)Z2M$Q=>mN-h^gd`uLvD8^Yb)FX< zfRYral_y9_40pLCpm)2~NUq?67FCT$cOVo}RC^xx~&s z(^TmV4pBLMIHv2b-01fVddTW$HpNPx(QNLf!NIH8_m5`urU|(@Wj{K$*2N)1IfWSD zncjyTT&E;v+MT44JW})gSFAFz*}g9`Fd^JS#Afn~&obbo$K3c7!4c|lofn!ff0mq_ ztZpI|ZT?fBY_%_)(Utg9*kMrqJi}@(;EnFiW@+o$U;erxUCUoqN@~3R(R|8hfiNdY z|HeD@4GnpaRc|jlP55x)Wc;Q~ox=V+tGfrn10>? z8MF%vU)8_170??|Q|ro2=_n1GzBs+W2lgF2)(pV97w1d-Ce1M1X7Xk+!bZwab*%E1 zyA58cX{F3SP>Q;6j?+%J$knHoVJgq~)mDT6Q!Ph;ZXX3XTZHhD?!C zGz4Hs1fWA6Zf-CUOkGq?qY5+MD>97(D;gTe$+BWKT2=DJcRQ+|RyiDV^Y7WOVTo0T z1JBo6a7aZiF>0NiM{4h8JC4Y_4ok6r8+5LXm%i|&{wY*Lc7Ty)*&SfyD*}vc4~ny} znJ3D1-}YwX__$V~&70tLtY){+x2VyA%K!sKJ(i za>!gANMDG^C1hP;0&S6$-$c^$gIjk(jt7c>prXeDQH&yc(uoji{jQwRJ!r-PPPm1? zd_IC|JQ-DXMCq@GTNa7DYH`Q_X@072Ze5a`()RkL3&cw6&o@6K>FHd5tv>R?@3);j z>`4kBS9CXvk3SRpRu2L|2uF_$rZ3Q?d`6<@zt?4CD1|#TemI;UMG4w7n43%IO2Zow z%9BizqVy7+``(*&m4nFrmz zn0zTudnoU^U*s-oG|RlR^sEB5Ka(3mT1n*BJXh4m=DJu6hR;eoCrGZeS(m^0^Lf1b zY56QjW-bn_ zymVm~GdJ$41Ao}jns`{scdhRh-eUTAD)6ulAz5*O>AFhs9OMo=yUkC?+0=`5aT!vo zv-ZY0&ma!=;O8EH@(@0Vx2l>PV8v_SGZ^ypJL;@aEop+xXuLQG0bki_uzSAq^%hKaOio>kP! zYON_?R&{Y##njvc1_lQmIy$_lKi^(=TBAu23swdL1_wR7^}w=k0Foi)N&RQK7oS|( z3b86=l$}*(N4S30t#-OYfQ{2CDBc>V)xc2QmwCmZQMzmS-W@f?c^@NcOD!NdZtm`P z25cJ2znHr{{o32^kc0v8Fn)1fm>kiDkbKp`_n{h7c2FzF=7t8Pl$4a~d);IaWT^w{9_Et9k?>D4?|vX1T1{dhj1e>B@ACu);rUbjP~n9w(=dF5v+O z0E{?Z^;4-MbXXgOae=*!ki3E!ta6p4H3XZ@^eYGiuHy|$?RiB+G$gOZ>B5#ra*9(G zg_^H0_1)j)Sh{OTd4{!t*a10`;U51bpii_&r&b_ki5Q2cL~!Fx!%>|pEw+2X|JE{S zXCNiaholdRF2ZXo0tZijHv{k#sY-9vhakx>l~z%LxgwK`RG+GRUm*uxrp;$eKmR4j zw-=giuc60xHD^jL%mh?XmPPomT}N2~&%NN~B|&sHP;z4nOzUU1&Q-7dqT3T>w5?RU zD#uVAIlneP|FB8%sJK05ZOs?5A#MLmDX^DnzH$*4f2VCd)yx$qAt^GmKlF_pO;=~$lTX0 zDmSG~3#z~?hHiYAy0#Xp$+%)Q;vTkc`$R7OiXNWkUC-OF={_sU@qM+zcV!kknc6-; z;YOkE*>msIOj?PB$!i7^D?-&6p}+?PIAZEuP#Y|pk3CrBNvjeX8OnK3V1xLl)*wff za;MQMvIkV2AZBk9S4f+EzS3gMJR#Ien}N>c(O05#8bN1r=$=1=bl*irND;c4cpI2m zs?*ND2v^=9lhuH`u*0T#R|)*hCg7nyNc%ZZ!MEPr(u*X!P@bjCNRs=`3^Bm|3!>SV z<}W#I1mwXip<`_9*?`IPtkS%=(fFE;^w#!7qmICMzO!d<2*eij#=7NX*##QD+PdCW z?lQC><*`&c?3)t-w#CWy8(=A##>O?koo>8&8};kN{mbW&8a!-eP?_q$@Tipf*5h%e zCHBw5^#KF-m|qIoFrOBrO%cUf6;eyR;jI-w%p1-njXgf#u3ldIkTHPNxe}8_D$*EN zA|PPgs{URzBFFeUsTpv)Z;ktK##!meVNwf6C^1f(VlL`Ez#H!$i-XES*bd7}8`v%C zaI&RbW}{aI?(0nEE}kEon9puX^?&%(eOntOS)GO})fvNqw;;!AA`*3xgIBAl8^*BA ze4;O`$2nOl@_p%j8l(95#%@VsF_+%-!c!$ttFJj%5}mGc+RRNK+z*TYWDheA1jocB zc>!CuKyE$KZlXOE51e^Z2uEhoeCVg1*?-U2Q5KT}^ur>Ajs0d!>y zfS!3r8`MuG{e_AgblPyrgKQDOApY3B7eK3%$QAz0meIwV%0h?NZ5Nlt1i5vAp_;=F z+__QLg*{fNK04>1zUk@bMMw;8#1uEO$_9iiK}|y7`&b-*DBPzT$*14_xCr zW*#I+xn!GLm;(b`2>6NrnR!$NdS$l2{clc`z(Z(~(asl;1!OFQ&gA;)W#Zut_is8T z1a=&7zbTAW^5km|l=p!hh~QrOo!95sC0#S2G0Ni+nY`gXtD~TFwfHD1^779I5>%It zW6z9h@`0@B(37hPjdWsCY^V>Y9ccUwu)_0q?cNRJUh>ZeagiD_c!7Lycjv0PB{}T^ zwrhWa4xXl*oE*Y+12*J+cR|$S8>(y}_2%yI41Re_x{Uk-ufPg}QGr@AE&cWdzs{jc zaEUWa>lUMWLP_*wxa0AK1^nB$XP_gb9ieuOhGrtwgaa->!9x1@Kv*pXs@$X5G@3c- z_}FYx!;~%1-l<)8l}UQ&=U2t%co%;e>7&g_Uk z5}&Ee+}+gc6`o1saz#XU^EI|0b6KD)DV<`F4c-w&;)E>v*v4h`i7FcuxQn z0HEgRalG&JnGsP$g5KZ{2yh+>LR4Tdke}*>L;2<%!^AMm0Rc$|M8>i(rN!4T9*)Zb z=ukp_YjpsxHxc;!_(WGqJfYDMOh}oWpMMODR*>xyAjfjUhlv0o0pFtm$~D5n2VONE z(FK-w%R|pvBA$vPET*rlcIm)sH2|>_Az^6n^8n-qL4nZ(^-&t7LMApp#X_T0FhcVO zE0w)qBar6;=5GVCF;Rl(t#z+Sxzb4msPjEQ(L9ANGJwIKE*wIOK0w68V+Fmcol)%8 z`M~EEjbxQT@Hki_Utn3H?x76$|&^I14eF! zfF@)M?!s`f@B7uOSI6nCz!uV=U&!;M@AA|GIU24Yeo97mlO!lD5Pu0z4ZCs<#xf$3 z0iQ?0V!;>V#G$=eGAZ&9?FZv)=6qg}{r*5~9S%ipgkZCH2tzhe$B8Cs0asez- z9)Tj_3!?{PM9cna^$mor46Q{Gxe_AY{PP_sc$#P$FiXs`>f(Aw*zA`3kmNarB24cLk$~5Yy;V ze;r-cm0xP(K9Po9%xpZsivj1Ka=vpAW#Z-H#Z8H6g59wS_C7gV!_?rCAx7D(WfIaH8}RO@MB#d zntp_sUTS1f)%k5;Q8_vQw<}Qpc^1?F62QC#HH*PuVWz6X+RKvSVz@~EE7&?8Z;)hx z9@79sK7d;Mi7=_qM_=UbH^dOTNQ3-9_~dBCx!_IhRlq^P52k|3VSc2%w5+_`*U!T! z4I(6Dx)HL?z<(iAt>!xty^;O#oc5IV{=25ORv#{;n4|t4XkMg(@SQY7$pVc0Uwe3YWbX*$6yF}1}a#9k#s`muh>^4)7Nm7+3$H2Y;IWa=vHW(=@ z0rOjZYi`5&WXBm9HuCOW4%f+{IoSE!cejC3jf4I^u{jWcyTCp`tcszu(HZGh0w_v z8D&6!ovmiZwiI|I*iQ^>#AY{vG+s1fCoCZ$u|MI@%>)wJ3l}b=f`8{108(uUnl$ZF zFQTj*3D!FCr8h1z*hg}lhZiJ#l569&z8D_L0Qt`${BD?A2IF)Rkr#ZpyY^TiHvu93 zKd1evB@8>nAI6l%fq6`a`BETq3I;ua%nu8FUlMLr%{rghhEF28ED>8{9wbO~0SnXR zdGo%-+W1TKURVBr78Ciu2kyT`+N;?B5F>1iHKOwc%)1LiJX-RyB7QN zwi>CjUr)M?ad2=TD!RSh-C6kC{Wj<0cu)bNpNP;0@m;SH)G7VRn~&syKXpYKfe zM+`Mk(hyY!iSyxiB&u3iFtp&i_Y%}C_?&k6AkZi<8RbHj9Z+h7%8z=6g%}bS!na_h z@t>%>AcqU_r5CQ^w1dq+nhStV!qFZ?KHxHPpwoO1A|%iQZ!=JA8V~Lj5j3B!i5q%N zFtI^-0$WWV!T_KReFi5Ds-;a3t~FLySI^o!{{hVglvc_%{J?3X!2CVD;m%@S+dM4!K4|}# zbXy4#E(l0R(5u={h+b7ABUsgNPWwW!7%mz?L{_fqxFv%qPag&T{%i4kdJc9lqzapG z{2)NlN6=i?ix_ANolB1v6!o`*Iu?YzMu@Wtm|R}Y0ymZ0jzvtxX}9?rKtiXzsXLC~ z_mS^{JP_@uaw$*0+W_q%HX&{sDQbTW!Z1)j-+{ZtC%VxfiD)azw~&KaV7sB?i^s^1 zcq~w>`}#pfo=bXqf#(o2I32DDAi~o}j~?mA#v+DL{pSb%`g7McL4*T(BuX|m#Zv2) zo`x^?nL#@N)AgFs=PlwpBakFfRTI<@l^XVBzzCSiESNTkv}}Vd^#@Uzg0uFDba?=o z52Ry=1v!IM#gqFkRhUQ5Zy3C*h9qn?m76m|HKXuAWKc2 z@2G+V9t@hq`?D7;pcT!9ix(fWu??V)2*K&C{6?%cY?D;5gh+7pAQhG{9J>C2N*!qe z;EH|rnJ|2v33{7=(OCciHF9#_XlkP0>~%B?`d;9Wie3s_K`=d4E>lqkk-C6l3FcsG z!fCHx-`VL+k%))p(;4OVJM*x)WP$dH_(_-dr(632WC>D^`BtUt-n{SA5%dvzfzUvwpynr1P^8j$AWgAMy*2m`}gn2S%+93>6Al9 zM+Zrmzb|sup+`Umu%wYF1)2fQmCQslqR%>-%!TSeGACpjCPaNWcz7@Y4&nwRyo1<` zfB@4*q`-Jk7RoN|FvB{%KTo#_q)0G+qGWeh`F+!WMw83ZwN`lLA4tR zecp_b==k%gcryX`$UI2O0ih@k@R#9O!kJ42b@%$_CKDU6i$@M{gulITMauo~XnJr? z!k=h-@WQ1rKwn1CrVcux{5@m6JR||34U#Ot&Z6MrQU$*Tu+ulykZrGUl^u9t4~z)@y$ z*nKA&`s9VGswz5KVL=~^=N(9IF4&Wx`xXk2{`UNg<UgT9)X)eK@M9g{sF8+U<7<9P7#XoCy6AT7oERnlF zLUM>V=r!vo#G^S*@8TCBB!_>$Bl1ZIUXj*6NSpyw(QeS!!;TRK7oN~?Ws(PEXtH2& zz{FUB9tz?vC<*r1PN_m5U-I1 zA4(wsrmf?1ITQmtk#D(xw4QkRAJ^_08V|a(Njv275XV^VZ|VyMWM^k1y)eL&#khZT zsh0+1gjuTr3jtEV&jf~qgn+NrK}Vx!klT1(SZF%I50V$eL&{d}z=aUypw;paC8g=q z6qsj3*a`g&b1bD%&qN@+-h$}d5ZXfRuQ;DjLGvTa#lAmiI~s*pFc%|3$T`C~Y;z&X zg3TPyl{GI`jp2yGk~wa`Q?69%m}bayicN}X#EjCR`Bhs1KeiLSB>qI()> z>In0O8_|~~I=w71Y!;m-O-$T|cL-sW1c<@bc}KCKH3;&5pm{_ZtHN;tN8J@Ka0Bp& zC*A?)8uTHtOkQ1mN28F-hfEm-=_^QZwa~BPA>vK%4MIWWj{u2d0$@Vq<=bGEkp>;> z+uLG=&S0ONj<)2Gwhz!6CLjS6zwpY00ryY{CV1Y82>0i~#~=+CAnu`nEC2>QccS;p z)eJ-fk{g7KY=!dyR;qYq3tlq-xfK1-?s8cM4*vg=AA%F3G&`Fkl&xCcm7%~0D6>9< zWNbF8-w?>fGQOAaIgSJCY({%50jAfo*)7js@lyx zEYR=d2Q{4n9>H^3fCiA3T}X@!ct|@E88{ub8mJ{)P5vpYMN zeu){@k# zpnw@HG1#DrSeVvZLKKY^hsw>*uNfSS0ElI6@;5d#a0s{R3qziBMD*V^UVyMN5GGq{ zrDuHkauWpsF4F$wY4t6h%W%}2q4x%bY8fl+VH+D8EFvP~*&Jk@zMK)XxC*!OOC93W z7HIfmX=eqy4+upI&&WvKUbT&k5JP7PfX{wGvyLz>M>bPa(~p?KaMM`$ui~VT|6VlR zm=*;Th1bk1SFDZOaUTad9dQc4V$DNdwz|DtH!u(mTl5JSXLv0bM8W7AzxPQDzRXi* z329ApDE<9irgv4q`ycMj#MpTXT_(+y1-}8&0zD(+1cXLxCgsTQcn*>N`$1+%3q&GVd;!=h zFE`&=9_E0VZYC?(@M^o#WW1q+&Ko16W-v_h;B;eoVF5{Ns7_b8Mrmz*N*F+i66+OJ zzfo-i1Fwh(0&uIz5RpB2_;8BjIjLJi6135y2UNw!$49=>i4%fN28&n7dq#<15V;@^ zzs?;bR13Hxj8>P+w`;AbIZsYb4k?60MsODZ=k1ppnGZ{ECl z2!`U2Ni=pnZz00!p#g$t%Ip$^)cefsq2_gXAIyk0e+1-e3KBwfY2 zj*eS~hK5E)MmdFrVh0Qi3}+>`w;lF3DFJbCARzTN%FW5CYHYj;cLKqIFTe^9Vqd<8 zK<@^mDSgGJ_W?erZEW;H>L6^?u%vNS0es0=ik?0`FcbPdbQ$^xctargpxMu`w@2aj zNgqA>2&0$@*l@m_la-Z)D8K`trS=qw`#4wKVP(nWz@f!}!2wmme7`liN3slbb|DE$}?g>^3RjZt~v_hx>CM--Z4v zydIDFvYs$7yn!j!d;$XI;FcX69oM$Ekp?RtF-76kQM~h!ejL=;{^Igl77Xgvf#8&o z)9!1RNe~0=Tc)%bv8ee1N`+s)t8OC4X$2GBZ`6VHGv_I?`qt=sOw>3ixFe+h9@o*P z6ykgTQf&A^Bf*Q9gq}RH>d+kVBc^~BDLws7IBw8^%sVLP_NdbV4Y&zrc#zAad@sfy z!Ukq~B_#BwGf5QL?Lfg(*V3vv*ur0b(X{=QN~NxS46!RK9PsN?1afPmMXpp&v(LVU zK0}b-h(If`>x3#J_dd0jL8b5vpbA;4%JC$vaAwMrMjr!mig)f!Jx_zwivbX%jEs!yg#D$Wx1wyN zL`1Gb4~a)lo?yZ9Rvav|_y2eSt7yYs=7T}>9&054V3;RxL?9({F9>2*yK%SisharL z2g!&{I@kSJw$^%G#Ln{gyg4JISTx6P$c0LrPby-0v3XXgX=vcp)b;m=!3hRa*a-J| z9c@%3?tw}2l!Kl?tLy7z3=9GAZkmtlA|9=jo1pC-Y?GQAH`*ocH`8u&I8=&0kUTsR z39W);`8rWJmYuGlVH090Wi#$Vx4@dFChxvJ`O|YYX%FY@Y+r>VJ2Y(KgcHVOZt`KL zW{$P5>DGP3z>E$F1x*kBW=vJCxCIsl%q(=Mx`2Fn6c>pacN{0jI0*#Y8Yp3P%sRb-fw;ilPor+j@n%6I-)4^s!J6@iE%>J_v2}kPxm<29j+KQKM|I- z*rRfszw&3z*n?vIi4?XJ{|$UW+afb?6XOdD!et*T4c!|S9?NAlCkO^UVP+Op5`6$K z@A6R~Jl*Gjz(4@=F~L(OXBgsL+&bpbF~h|wFLl1?a*YqL~2HCfqq~pP;LTuCF`#M>E>P<$qim&e6W4>6wNJoPqmn=+>J}@ zcd{P1@KtP$*ssvTnE@ApY$@l%HIGwIvrg3S`5NaH^bdqcf>PQMY;-{)f-Jy`KXPJjlDKlg5ko&$<(&1Qe?`B$_y|3TlEFy zq*8>xV~3IN^&2Y}dne_m8l%B#?_5@GZT-aSpEq5cF=OII<{0|Z#0T31%LeN(30nFz z;yYCWwL{6sz4`{|_nU4uE6+9oZmtKd?%m}x`-YtG0@omZFmv=kvq}MU8pNF z_4vjzxPZdtZ2^s}D#LXPRN><4#Gd!e{tadSb%P-u&%;c!J)wM)5nM29z)zU8F!*8u z{R`hiO@hz`(#!AQjL!)muW^zD4(Wwf$Ur>3z2k-U8uRtKn%l=NpW8j`UTj{nw>B8E zX6*i$8G)MJ_%z)gsbsZ#6C1nQ-f~_l&j2G$HjT$fnQ)=#F!SWw7Daa5QIck|U*|^C zNO8|&566;Q(vFq56C5z`g@P}0ap>Vjj;YLekvYM!ec{H7HZfKi%S4;Rfc(Xd;FiIo z+zdL)?8fiW*i0k-ett>vFUh^W((v2x?c-%U4)TI!nC)4?ZtP0+%r!DqHV97C%%<&6 zV`r3mtE^q+Nk^$vME0@~H{N1t-D0_u*?7?XY2tS6K8>^a-u`Vi$8GvrDt4&~C+&Ub zqYF>p4manvKHZrmWY(q*8cIJyDconQ9P~9qmaTfmydz|wP;QbY#;MCS?&abY3=D0k z`+%?URNk0Nj_Ck|z1<{p z?=hQ~qsO*BH8wUu)3zf!Ju~xEr$6@}`DuSq%BIS}0;6-EWtWS%dnb9i_Telz4S$&O z-d*kV?<+C6Gj?6W?akgZ_;C$l{Zs^Hsuw%VEDhlt2hTy4y0$-SwN}?qFyUBaVGT7f( zB45u~umbBvT zv%4#{6?k0sRI4oT2^hdo4Tb&HSyPIL!=m;wb3?!A#$o4o3j-m~^s^fXAa~$oOQN1Q zg1YG^Uti{Td_0rUz}#qua13n69>7%q#%#3FA`U?-<^kKw_qotL1pX;iDIQZhucYW( zLyVpE$_1@52gjKT6$myu3ttj=-If_0`FrfV^UP_VPlznvP#k4vd4MA&C6zT&9IO%7 z(hlb!o84v;3k+YZWa{0nooiV{l{JBbq=cJ;!sP>&pKNenWdC%uxw*%))DNae>bvrV z!=ATflXh5l&b4-=>0d)R+-YvLi1}?0lrEX-@RFyR*nEAFvfv=XI-$9Faim1)$AS}k z(Rb59ov$yyW+;9CctSfm_In8{i@3C*=H|qi&{7*>u=8p?BQfiXAohPQ}>#9GivltI1 zSaJ-sC(@aBsD81&OF+<>W!vauu24a{&aSDcS?5n84rM)0e}7#lHbK1Idm0GZ1|$2| z6xy6Z&xv=BmtO^~2VoHTJ$X_;{!p(w;R1AyUgXLlA1>Ezi!uGX`oe4APNGVBA%2Wn z(HtFWe`_&tdEZ6V@l4-rWmw8=in1q%E^OmexEQPDG81ZIgmp{3*Lx1&5p-Vwf~t;= zfH5_P3sobo^MkjTOoo(=KW626-nyfE2St{rXYcPX8DOqtZ7lrsz`XugrKYyNvqFYF zBQv9uGw*n2^ivs$*vfLb=O^E(j&A2&`vrauhvd!-DJt0N$@!ulVV+_S3D4YRee*@oWeByeoyX==uB_iiDydRMg0(QmKi|lWw)OOc_`o_{ z`^xalz3Fw!%BaCtHeprCBs*2jT_Q^**t%k7bhE^MwZiR5whIN3 zd2mzxu<~$(s zr?XLSAf18g{v~G&m>%shN1t=2m3e44XGVZpY3sg!jGOlUBDhJzGSzqk4Ak;SW%7S* zsUiqdB;$n|Lx!Fd{RGEKpYcOczp2D66NN`^()JV_s3MPl{R5V zu=>KpI}&q7a}-3oxgu_f!~+^{v!Wv%2Tf;!yV_E7M8~c-_4W0kun4j%>JxZk8#a%^ zmi~l2wtVzvK42kbmI9NI%f+_s?ip^p36$82Km)$}#UD2O>y)~QsGW+dL zYN0HzJI4zvQB(3V+D>>*@1(kVC5Al#-}bpRVm0UW3eL&N54GdH5AYnJafht3K@pJ)1~jO1h5XQM}35xpfrXR#lEm zE_qVFl|RMueII9fyUKOr1Ic<@L6iQK$z_sMx&o)Ww?zI9e?7Y3tg`N{C<1{CABQ(8 z_h|$ZV>gwY=Flp=(MhL&)yC6QwNhf#!P#v?YUina8Q)$ym6G$%-Jfo+G-C{GPDSJ! zXE)djvEm45ycJVcCL|>#MIx`FI(Ybh`5@3lyo-vS{#QPT{b1t$RX1S4p(syMaK|kz z4SSLxPCieFaqkgJaAs*C?2iPRRL4$|>|kf-juLh#&f-<m#F5|W$ z)7y(xWr-V%L%i#UiP!WG2n+Rl6qhPU$6GOigM*neW-zNjMVvP5|H)UoHarNGZ~W&` z@Az(lur;c1X`uDzeacI6%+8gmiY0DtyZPLeU90^xifg=z9~j3Htxi14?N4YSYfEFw zcp)h%`NQ6-(26M~BKK0V>BX2G{*x9rfrQZ$8bE*4)QnYDH5RNCYXkR%9~kf8x4c!G z&Mzlhp=V)9lK=Cj>g0s*PTV&Nh1>^#oY(*Q^#K?N9?#6DQ;vqok-W?jIU_vM(v1~P zve}q~H^(i@OLxdvX#Fsho|;YnQeewJdOmUN4vzkr!#D*h#%dvKj@|w=cp{4@>(6%- zPSVa}uO9HTaw8Zx+vD+PNT|4Z&of5$L?onQSRz&0p>O)!JoEw*tBF#o z!yq|V1rexb#?1K-#OJ@PGQ2mx)7CkcM|*w*BLTL}=6txVl6l<5rKl%Q8FlBpn928QY-5onz5xZx)wl-9DM5oC~{^X=UE3@#E_EWj1aHMOUf8N-2 zm_=m`6$`;xgl(2Fm>(_Ue0cGB#B?ST7YXrAu^FymR_D31SLm>d9geP&Y|RP57B-Mh zZS~0HZhuTnB`l1JcH^EJXj$lYqAhg{>)iG#``tmNt^86zfPnQD=|}~T_MQMothuEH zW+>nQ2ozvU2utfpopK_~AuZISNC=Ctz!tBlG?(4 z=l82pHuW&Fy6GKFSH2PxOEO4&ES;@P)4A_;+wz-AQ{iG?X&@Xyu?R}Av{s7z5Q0Px zi7BKh6iD63L>m5UAUF6MmtbY;{hx&dO)e(PCO!Hq=j zxx)#}C_ynXodsIw8!}l29p%kI=5LJ?ik6Hj#kR4g|isNt@YO}ub59C zDui2$ai8l;b-Pb>C%eI7I{T5k@zse%>3~Z=Ztw}Vy zKHrp1SH7)%^WjzV3$zb~|3CmQOLL5OVyVQ`%EzoVRH6+wXi>g0XghNtaqDGRhxT21 zy}R|+#7P>CJIel~%%n4m(zhV+9GGBr~aQ?H(JI@nDxw}zPf4Itab8cN~CPj#loZ&Qb5R+Mqe91U#s?e((Y_gL4* zy(U!_3Vt)|LQ2?A{5)~}c7fjo^{Keq(wWj1z&$S-8QbFQQz*4*7omgBr+5U3huLY( zZ?G>YK=JG@qt11He*WEqgFIjd@OmJrds&{3^t!WO97?{x;5I8L* zd9l#(z$%zd4YLX=*Rwzve19K7^GBYkGQ^`x7s&sZLS1$;uZ`>6UufW{0ffqneyxD^Hel;<^o!)DMGd%kgC-TY@QBt8%QgbAX9)U>OG?%@=cJ&b>Ir>9(KqAv6a8rGC276}Zzj?n z%)Yl09w=O1Pj>Y8E5VVO-a=jpf!ssLNT=+MY(H^vV~ehYR~nImUr5@wnNP%o=PhXi zd=d~f$ub+$53a6oclV%){l8m3t;ZOAAaQS>Nq6G7ZW~HB|NF*fzg))(Jar{SkPw_N zcgfUASbUKfuBiV3#g-vXf#-58f1>16y50IY$88z@6?}FhrY}3yVN+w7ML&@JL^dI1PrdlByIiIZ;90?_1l8f&?x`7MDCnqFzaCPV@icgi^6?@h;1F&wuR*j!|^A*YJVm3r~x$U%oSDwdz~f| z0ecz2I5J1czotsv)b1ZI?xGg1S5;x!PA-gjpB00rWkRjpEjN%)muIbAD4=UqUzX)) zI#nlg(PuXx*OE#y!H8a{msWg+OJ&b~q3-&D7%b-xV9bus&3!5oix>anLx4rKfys{^=mO~*5O`LkN*}~VUzZ{7T8j!q@a zXJ24+3v&NU_KBQ;I^49IICJN0YkQn2BdS7$^{Eo0^r6*TO+ecyyeSll-e8=lR8#{F zq|9mc<X0?B0`NPlFc>m3>&EI_dFBuX> z?`6f5iP)%9yf`_(-V_(}I+RMj?9CN=vy_imJbJam!%@J8(tr{eRH^!bQFm%?&7z=y zg|*kSBYX$-WNg7y1pnC?xKZ%$jKwP<)RD||ow`JeM9BIUx*Joy-M*jO1r7_HjQu8x@^jQ@)Q*3y?UOjR2@&H+zsL zJ&6Y5h6IN&v%|7gazJ%1+vD@MyMCh{T?3MxkfVl1pU)cY7Embas`A zjHAgq<7Ceo{9b3NJ>8o(nk5!nB*!B^Zr&K3V_N9)YR{=h;p<-4nPF^CEaR$|6dMQ$ zimXW$9eoz`?!5Y6&!4a(J)n}1sd*cmg97l6cQ-r5A*`oX`SJC>fZtuV2Vn2XdK-wN zAnctRao!QM33CztE!ju4)V&-L8u~h2QFNPlAbV|3@?lhW*03{HO;b$>P61tbaE7f^ zdAQ}74kFrw^o~SjX4Rs?{ImDy>A!pyAcag_GdsWjyUmq?Tv%mkYPm1Mawoq{IkG#u zxJyXmxhUQk)^pwi8Jp6|GuZl!GkfnQC+PvfU<&?Y@z~gk>zqpCf<5)zKt;yn19*{d zC;w*Sap+KZ_>oLQVhssJM@}?5(V*k29seKN`@%?#SK$DNsnFGDyOiuX6z^`B3VCC{ny8TS} z#mB3HISm;3)k31$o`uweq7A}G)TOW z&YFS85B0WEA<5BYm&&~KkM+ZS3yEG1Ug5n-0+xbMSA1=99d!+Kif64RjaOFK6K4kB-h zCObtozR7NdGdWi(_{~G`_MsVqH{jge0wMv>M4-q#4ilOZfkAgl5@;s~gSet0X6 z^uRIc->!}z6lQ&+ukoTL?{a~w={;?op_ajuq|zR+uW&fh9ks32t*oZ3>y_pGxF-3I2StO)*{lT;k9?a^y_jvO8^{00nLQU&%gL@1d0E?&?zVZ*{u8{l#!hjN`;n=gNc-` zTyae3W~ZT{(U)%{a#;zXkuD{Ha*!7@)fv6PIE>YkraC%RiP8HTbd?xvOA)(lkP#@d z7++(EqwIk*fkcrqGv8@;-mi=^=^_%`X2MMFK&E5 zpjyC8=Qb7YoL#vn87FUQXSsBx%062sp6HqF*An6BwZmF-sAX!;HBO1FZ700OJ-Dk= z@0MMUaEOpf_QoI{QggDfU`p@J*7m8lfs${&Q*w8D;)(dL7_+76EhFw^V|&X?@>0a@ z;7Xi*VsZHH*qZ!v@vaS0ScVc}=!xPw?W5@>T8&;SYW;aCmBD1uN<)I=!XdzURmyQ; zqy628Jy&ct@fe^6NHp^xxRO0uBS0C47j?`X!K?04P*h|991HN z64RTJFn>^xLnrqES7qQGfH8|f+ZhVmhKlRI%T>=Qvgl*F>~N?>rk6_mu(EyB7AfTY zs6~G;&e(5IA(`LUA*uPiI=lj;)cG*&>-d6Te2Qf1RgC$HH8bT8)IcH= z@kGT~+ik-?8o7!5_b*~-Hii2#8FqdB#o|s0C7(9#xt5-Sh>q=+uAau1R7k=;tuqY~ z#yUlW@KJ$rcGi|wuiVk4jm|^ut`tck_mZ;&h6G3ozqCFopSLK7U*3!5a!%mvrlN_J z9(yp;R#4VbXVPeauauGul?-W>2}z8_vY*ry0Z1gj(-;Y3@6=@|mwkAc*B#OV&))D1 zx@th1JPOH#%RqP21|c12?(on>I38X8v?AO64_Fs-p+_x?@c>^k3(;(6K?r2wE1nFN zk8f==`8bQKRj0be!(V~f|M*OJ#&m4{A!MJtKpe&(YE`;}kKYJYX9)@%qmi=y_AyU~ zMI`kLXZi%vCK$KV0>e1|5xZd57M5x8Cj-ySE?e|Id-k};0 ziEM3(c7)3o5^@I@7gxE==F5d^xc4?RAoZJkdAjEd(tFRUH+QWOa+$h;!t&SsU;V|# zjB0+wXP=KoJ6^|&L$bsg_E=zE0;5M=N2hMA+Cu@jxTmpZe$rr_F=VTkAjpgc_gH+} zCc>}$N6N5s<%|0jc$~jcUl3QuQ)UlLvRa^=tZr=~1$cQSrOqM~wbMd?R>Wx<02^d% z@fh0&pW(yf+f} zt_)7jg3Wqa1V+_CI`lsWPQvJ{`%5Zd2p=(D%%c2Go)NZs;p=Uc&6Q5Akk~q<^T-(Eo_}Q24*|cjDwWLTGA$@FPvqR;0S~kG_-H zYpAzBzu`gp6Cu*jGB=IC{`m!8bYXqgG$lH&IWnoO^|R)jar=x-cW)_V8}YCD-zb-v z3Eq&nLRvGZ4LQN;ShYTIR=|WHsTa)a`?uJy0AukK5e?-E0|WcW=dSMT>l!88Lkc%8 zwXF$_23}r|09_3HquWZ>d)SZ#!iPTaelX3U3t!m6t!WdBG47cu{o$~C6M%V*hCYol z5r?pe9pp!K;74H)Op0117oZI`wzfC#-qk~sVlMk4ctb<`Cq{CG$|;jNOd{mX@_IQ*J5>T$VOlt84yegbM z%2N_^JruEkd3So@g!>2p#ZTTEIoVaxX|dFP0rDbWZCbTTp31Rvh;84V5_+Aefl+5C zcJINa#1FGndtJY+$Zu<$aF00YvwUB2YcKm>7B9_`OzoSjbbouq6)O_uMbU&?pQxk2 zwmtuAddX?uaFM_w)fy1q0gD5dfNr4)#dJd6I?Rc+z$YKY2|xL!2gE1)%_(~%Io_V3 z4fF6#zXf&H)zQ4D8+KN|Qq!nvse%%;LkjWgZ2z|ahQ$=WD4y`c9fRkM45|RjJWLPj zDWvMwvToM$D0Iy7eR`lgc>MNHA_hxVWB!S3FirHf!()~G@k8!Acu&TK0ez{iP`;8@ zq2~RYF?oG)FE5`V%+U&}kG6|lrgl#K1igw=9V48l;7?cMJlk=8V)j~E%Z`#&T?K87 zN@aA+#^N5M?D1Me>k*v9k2?DLzd^SIgDYX`mQSh_T(mzgg9JemM{Y^+Ub^RI^E7jR z;wcLJ-ph%j%0rgb?&2{h041p$(Cwb#&Q|yh!CL=zy-u2(m>{kL)QYYJ)4ds_b)L3u z`Ps*Mv?7lb0f9{e{X6*;9z1-O0MrszGc1B4V=+0FafS09;)G+XpYC&9wryy5QN7jm z#v;plKQgRYt?-G;9`Gm3;mKgr#)YskrtUHu|B90738@n8Xdxv2#$J=jjJBUW}(PzS)jE(m-fC-g4~dj=;%uGD#=NA#okT|r1zT=$)Ry1OuP zln?=sK|Z%WCct64k600;D2nQSESD&`=jHa1b9%C0%3x)%?7lX?%uefYN7mlbP#xfS z61Wn%Z)@=pz=gbsx7cox3|qkGlKS$v?OXuH{x{?D{~Ijix6sg>YMz`bpLG^NVazx^ zz0c_CcjsR~Rb10a7FI@VTtmvMkDCNFTY({0Zd`u|ro>5M6B~*O-g4BWk63VBURG?B z*v>yj0XG5&6s>?eSuD#L*Nf;_%$Ym>e(QW`cj^B6@kyJQ-(?38;2(nJ`y%FOdeC}u zm(h{~jO*q~cLC(QM z69H;@UDKnbywHdh_#>5<_Vz>ys5IUtd>8w;xpTE2l#Tx4&!9A z*8IKb{?&?7l+^tLb(+~g z#GxCss{KGcpSl{vG%|{vbL06$t~5YKTt~auC`{rjuS!QI;=l6E^r#GWw5$(&cfJE@ z2?WBl!>ZpFoR6cx<83aFctg-lt8(N~S@gE1+M2C}t$>Vak^cDr9Q!{>BM^)MJp3NC zEY%k=CG6$0-^NI0r11L9HwbOiwRF!zrZhgjc=q|E;iVYX{|Nw^HQE+xonz2p`crUf zM?Arf(5Z8ycrT?Cm3$3ky!qb0#C%LGQ{2KIBo<0`TyOjI3JxGIxPCHDh1w<_O14rX zIr?(b(O1sVsdCDKP%c#-&m}LUb)f?Xti}vfOU9$H)oUiOE|2 z;0(jtb6%r_aHz7Z=zxz1OvAIQsuBEQ6*JD(=Q@uUr|0>H6)lmPZgN}9Pe_zp^25YA zRh0^(=Z9)Hnj>ebzXaAZM3R06hPN-SK6&ZZ$iWI1)PLVmDqOWsELTJ}kbzA4?-AYu z%dGoUiNYlYWp9nca>aWVnnndMkHddzel(li)xad;_DM~p#8@cl{66eqagw5x_3*MK z5o7pdLfh7hh~pl`)IW%CmUq9hZ=eBW+3ku4Pc*rKvPEEO>ZUowUu1s8kT&CaQg3eJSUAVXU!t;aubLl|F%mw@X)8 ztwIwL9zcaL&N1D}fzj^PrNYJ7uYq~8#*MVb^P-%jBLj*XshcPkmU+jNwn9oAFOii> zuJky$-slX~A6{3ro6b=Rra(x5;6Fjb>3OU5wf$Lg1kdmoO*&3@1@iPZKmq_(DI6L} z3h(`b+gk>8+8PnR@PY-G+WK}`|2;MwkOkziSqoxf_&8d&OW{q^rKps$fZY2h076hd zT!okyi14rnE__8|`4lKRgt-SrW<)3CI50~AB`Nf?;R^_{bPmdg*(|la{K&8WhVPPY>cuN)Q0ww%^#PN@_w;&PS)!-0!S zsSN@wu!>L!uGG?L&Xxzas zEsX=CohN{FT?0ftn1Ow&Pq^T7`oK|^U)k)JEo;5l)Qz=8&CD3?WGi>fmp}Bnoowp5 zV~ch8**Qx^2dGOZixY~Y+%1Ug&sQk!?=1wVb=|`L$FMPD(OeWA=uwI_vi0%x-J{!m zF|gH}DxH>pPR1hhei4ukd`+wCbB@Hh&$f*wI4#*#ovd6)6-U3;#J16gKQ`UrShC`Z zE37+g?K79xP0TNTfHNQ-%|$x<;&UB?E?xAw*97S%fmstWF{38?ck5GyM;RktfXd2N z!2^?McCd&J1pxUQ=-z+^#`#{L1KjW~#~q+qNR;Xt8HqGKqCu^Oon+R{c!=J853wgD z7qP((*IWmYi*ae8`Dz223b}V6+8HGH%2WJcLm0#U5KW(v1XPj7D*P+WK#w7;Qv42n zoMh^*_R`~mbtNmKhY%n8&9{e??yNw?H34K22KG1u{R4l#y>&$SsIfQK^|4L~@QLlb zXqgMOjiPltru|bu2V5V_Dmv%GG}&Wq2t!#?ynNFcXi`Il+gjm0ecT#4v+LB)Q*c24 z{dR#nMfBKdGV%luL28CQ^`E-T>*gAV}ts^iLtz4H?mT zy0JaccHclY{Y+KRLbxvI91S4Wd<4~k$TpAdndv2GfYYnWzuAjKu3;FS9G?RU#fuo{ z*lnYk8*CbHby_=Qnmz$J6WAK`%%YXvAHDOgfvZP-^do6KSw4U34oMrpoD6`wZKw%1 z)YQzHgcZ?l`4NSiNp59a$U^{Q$j(7_FuhZGqCV@Ey(Q_vRb28zqSTJ&_?lDI4DY3P z5ZsTCPXMT=JDjwYfyBlGOeR6wK7h8B{6pIayJG_BHy#-u9IC_%t6e`aqex0hIy~MX zahTu?f=yXNUSkfI;UX9c0Tl;R#)_CiQLc+s%y99TCQ5#*-o$4s@;Oj&ZmB+c2ca69 zV~;yPuwN-;BUM%Gr^P(J z?2UQC!lG0AVG5oe=O9I)kVwSgCHMJeH)SB5_4*15 zBs8vt80vdvBv7uZ0XhwSfr%II1p$BuH6i^4(o@GNwI)EeB1cpsGD;{QG}IHw{Wvr$ zp)3oTHrl#KWqhdcnUduKgHB^lrYD@3?uFhY`;bD%=>F2{FG4C(Bof&+%BB%D+l@-< z@QA79z zlonN3ie z(Rpg&h;XkTO-jHA56f1ta=GezCpl9v9ic;q zb5IIGOPwSgD9)|I9Y*#!x-ld+B~WFz=styVrB%kL5RhLXsEPxMdy)kn|8@MjwI~mL z`B9Y>$IrJR6jG@iWtitysb2$1Il?Ib5|}>u?`x!dPC5W}Bfy7THZcGnpyhQ1_4uhq zbT9JD~VVB!5zBWgt?l(P|tImHWyv;mT+j6;>91l=!j zR$GcFpD?y)P_JNNWAobZu|h6B-SmeSH9MH+Ps-a>IytEYXqR?(Kn!fc%B}rtZ(_X5 zj8Dc~ft9GWDR*BYlTI*iGT!~X2HDJW!!>{C}-cAQ-K zAtVdv`CirpPP%`;bS(HaZVgvk`pERoc&SjLte!?@HVW4xy8WOL?ULX6f&e|1On%CneUNlIEj&?e>kIsNx< z)xUHRxB;*ZXk9WQd7r#*cJ~Vf*5nhAw_}m5e5v+?KYrmJE;09jc1zIf!MC)O9a`S} z1Rc=navhw077dS@h=(y~!(vtq4Mjo?<@>ua9<5_vfKDAj9f`#g?-p{w5KrQTVKy2~ zCGAd;aD}S^LEzU<*GqdKxI)XUJRP0`2&p$Wo2m)2MUYD7&iow_Y04U1e+rBuuH`|_ zPBTE%wEEm`b{Wa~j|W8|RgTQ*tv$OA3dp=ZkC`JyGF)M+O2Dilvfa*R{a1q}fde@OmG8i1@qzKd#;c9LshK8-64uNrog* zqDd-^Qc7tcM5s)KGE?Tvk&-DSWF}*ohf*?&%#zGg=J`Kw-u-|7_wQpL$9{M3 z)BW7fb**b%Yn^MY^E@p?yiR-Iw%4C@!j|gJQbxSQSU2zbIOF*2Y#(Ao!L^C^f1oqUu4m~cQ3Af5*-HAq2oemXNqgz`kCcySNq9p$r|5Zg>Tmx^)%)a5c+=a z>kTTq9Wvp`3*9Qkd`&}EOglc#P_fG?8XQ}8*?xdaTp^|}dDYrcxLvAC{*TAU2U#18 zdnBu0ExT_2U0}48%G5c(m{I1BDQGbEp$8cw)Dwu$wY9WPVJ3EMM%L|w0&LEY{M7c< z621InrRws-rS%i(+y)(^u0VP;rr9bZ3|GH&*t*J_CuouEnRW)M%RQ&qy5YI{@Tt;{ ztus5d1t0tG5(;dVrz;B7y`X3y!grcMdsq@O`nqB3u`r5` z&dIqGv!g)*lRrYzH%Y~?p6Z`IY+=*8m&RDa;pwuJ6Z%?zuB7eF+g#ui5D;Ky?zw7l z?9SfP?-m&zAC=IwsE?{TO}uiU<{PU`Z%jRkLY++R>;o9yhg z$!aK6W>D5@0znw{Zb3>?%Hxrntl|OFAC7QvL{{?Fo(njCSD`fupP#c^_L*A@KPr}Q zUnRq)Mwv^A@)B!H?skp&V=^VV(-=(C)HWoab@3W6X*!yExBU?3@na@SAEg76ZwGCc zWao&C)T}Pb)BhRGs1zDNyLd3lo%8Qr<(a$IXM*)AMt+I$`VovzcU>rE6hLaa6K^)?%He!8Of>*(t3GGD%u?z)uS3M$-= zZ}yt=-kaHh`IP#jNq^OEmVB$AIitk--6TKr-YC7A1x9z8t#jd?(qn+D&?~4`XTUmY__;sr9F?r6w7~&#cgSNBTf(e^$y{a=w z(x8ysG+iDTdP^Wt8WW!9DW6I|F+LN{J{EP^@qUVFs;jm-li8jcf$vhQrac-{yBb*V z&RuE+R4(3op0Z>a>)w<~9&<{qsK~xIV-X@~{k$p{gL2BbH^rKB15V57$mKqWsR}#2 z>POzKlFU#1uA3Kzx;=hfp<6n3Kvptn#=c731L#BPCOo>9o`QT&;6Li^Zd2*-RSAOFL;YjuuBemiK%!elMxzt5Ly) zANkRfXB@O;E*AyXXnyfxWWBtV^ ziOy$C8xpRO^F(R<)*&(MkP9#cR(l4?n(v!8&q2{c|BCx=zN#c0ogcTN8GUb&L&tJ$ z?v3+mx%5tKb~h7eZS6-pyx;vfpkf~P3|X_zt(A1G@WQH}G!t94!2T}YmJxOv$})Ou z*(5w?I{O%%XB^zzG)&L9?~Ln}r0AC9n%%YUoY;Kx@dFP->&IR59=rz*9`1fP&XDjc zh30~IRv|Vm%`h3GZ=^aJL#r#ewF#3Zi9Q*S$$cP08FIpU_f9H`n-+rA<{AaJvZ&bX zPV6F9y)u3mdWU*sYUHVz1z3mdmygKKhfK{PR_|}l+L}4q;?l=c{PtQr-CjEHsKY+m zrG0EAvqPS(9Dn}qwcWzbyELi1OZ@Rh0_RB?=RFYbPshx-WmVuXuTG`OF^x%emqnwt zeD}wB65ah3Js5~4;)#{EygffYeJrj>kNGEfn&OnwmyzYY_gwu)S_*FUN&5~<*pRR+ z`t=EnXuSUz&T}J!I*iTiOMb!3eh&T(sHIy>{h_;NeBGmbPw-t!lb~{JJ7EJDJFFuM~itCNo+1V_b_-_C7Gv=&8lC|?7HyzImsuXO)Nqtf((^{k|9*i z@}t>`NXjM7uEpsMC$??*IkfjeanjxTomMYjeedmWcu!91YOd(Xd47fM`%W7V7QOlx zioM?gyXq7Pkd zn`7cB)_9w9CSCEB{$PD)I#YGT&E5~q6W_xr=V$4jd8?mn-I+*6+8TIFA?u5t(xOC} zwfLReWnKR4QRBvijB;(;%AyL!?u?|d0jbuUA7pg%@VUrz(P*@`iSRZ!!T_Ib(G zSI+G=nxc#=r&@k#XmGFYzHh$Z)QqzEr8K#7!yO;@V4~DywB{bhFR8b5*yDvVJ)_vX5rxHq9>Qy8!&3}I%|)lC2MEmEefPqbBe)^XY4!6dXCPfn9SIO zesv_0|6@^_=!$fO`RP~adV4x(2VqdzA;CQh9quZTN$rV z^^(`TP3QA-TW6lXij6~IXD-&+UB@K#Bbt^tN|MEbVu8+myURDPYtFtOK@*e^x~owc z^AtL7?-bNVt<|m7vT}S`ON`#1eqx};XCiS-70oXE7?e>-zPybkf0eC`MYtHee3rMm7A|n~jVLjO@LK;pR%dcoe-|ab$7s5gwulit}-!+640e z(IjZp7*NYbH=vBZer$sl9i1{+q1<7{Edaww4%u_rHgWdF{ng4*_sxeTKW?|C8U2xI z(=en#@?c7@`{YHRd5}cX;wgT_^o}gUpoO0Co7lU>%6yf30g&?|(XLo&ochu6x@0rW zk4vtKhe|f*b!;ZFHm1)04gT{;?52>RyHly$M)jP8<)tZS(sj-qb8nyAlr-knn3XoA zC)K2yFNT;Mcf9uFW`tSARK;eATN0w;q8mte?r7YTuD)mg{pFG1Yj$m4L{0v@y?y?^ zsi{xC-K}&wzU}*YQ^rWd_4F^JAoXtFHH+ipr@tE+((RsMCGl5w4GsL-Uoy;%0+);3 zIGw{(bsU;+b+7hV>lSJ6D zT!*c<^(&*|h6cgb+!JlPOBBQSChmR}o_g6jcH!Fc*UA|Ga?OG5-|2Slb%D$(>x}gC z{1T~?i&CKv5s3mE?6&{Av1Mk|3BCHeK6u=$s90UrjtB}GjUGc)lyovtSdo9eai~Ds zgYl@Y?}A%w*Coy!1XKV@QzvlzRlX703M#H&od`eW->40e+&U;msQq`9RPtH|hk`l_nH_tQ$7$* zD9I_D+*R{Q8WWf%3vJ9QkCL3=rXIEAneh)j;JbSBpPImVQIX~4rJmF|i{JXJ(jz`S zG0W_)zIRXD);1gIch7HM)H$2-cP1<(d)E#(OO3^DxxzR_J%~>x>p134n+L|L#|x9X zsvJMA>|O}vJtVd#>$wtRZOU!(jkoiiOrQ2;Ebh6l+dXZq{cEA*u#azo(&-BNgBe?9 zvhq~3*hxB8`buBZX%8GYL|mHz89e^oxe9S|8Q&UB=36=kq(mAP17Ey&c9{Jl87bqz z&!0nK2`ZdpzTE<{S$=~qqW|j!@MFlC?;zza`Z42DPyCdn^|$oioLT=D3q~dnjn!tS zmk(6C(M#Juqs3!*0~yKGVBP+{Jryo>#T)p8sAVV*96n4Yt~+_?(7_F)${*VV$ORUz zwmharsK|H$Cln1?WgsRa#-A#~Bv~*XS+`q>G1HC3=x9?hp-3c2F{u zxKr+QPc&^=)8)qJ-PeLD6@OMr95$xhh0|nL|L70d3X|cExODUS z+c;|m)+KEoF7c~^as` zQC_|YL_>}sDBF9&f&}{2(!NL1k2pBEmPDg)IfJMUN&!x1PIuj{Jb(RG?=lmwx{Y3C z8DdrKX_pN6!BxfY8AdCvF(4jG@S@^?{c8XV~g(bU6+lq(GBx z@e_?ChmxU0O2InLtQyKp_J4ijpl=U@*=n7>+w6rnMeCpUGj_Y5b8qSW z;S%slxT8&~>q7GHG>s48t$pEFQ0kbo82?Hg-rCY!tk=Sd2u$1eaoUxNRF0(E1ukDM zvg!37C7rNZ*hNZH93LF&h+3qTDeUOTuUWD^C6@zOjCpQ;UJKp&*_M-sK*r7cWK?am z^SqoKv&m`PcnZ<`YuKqrJy#02NX zpgrSwtCh%7KHbG<;WAoDC&P{M(vBNbE4(`9jN5`p?%u|~N-sFIedJa~+4ufe-(&Vknu{HwBnNoUcSrmFbW!H0c>{F$xY;&u1gRXUCdgug zL)zVMGSMoCd->bRZtlLHAzt4X>-TE*0y;X>`RzwK?bD8jv*g^9HNB+%*<0riew~{Y z^UL04OUo6Ct0QlcNDh<6!|qFme)O5E5B<$K{=+_5U0r=rjfsxY2HfMXMU3|bC>%s* zh9_v%S*A=NXUoO<8V)N86Hz3G^Qw;jGD4-=ts-ROA7}p?ySg-9zdQ7q=6yw|at)HORI6M9!F zn4jjF(&N_yebK6+BRAjws8LJNq2u1DF6S>o^H0D1nBnPP!_FPqkC)j#j_W5i{ks~s zV7=OH3m_!o{h3f|>36NyTMP3dZr#i?W_|M_*m~;al$C~LSHTtU&W{Gu3Wkw~j))y; z&oH!pvX6t`R)+WQRcgDI&txRHrxw%iK2K!sTQZA~EDH2ANQFtu)+SS>$^ple=|Q=3 zRuyxgMLl`tBnRVsjW-eAD5ocMq|lIR*!@R#gF3DDy*vx4@VQW@UiQl3%yDL=3gM~6 zpSd@Vk_uZiR$q1BOcQ5rYm{@4rvsP?j- zRB0-`DD8@Zw^FF2m(S_#WnQf!egPpwu%kKH#yofTdol*w%)&M#p1<;Bdu)Kh$d-oV z!Aq}b@Bey7fhN=O1EM3mFK7&>TEnk=Sh;5Q!3}*y1k_<8Xtj&*5QsJ9M&mdxzzsq{`%Z)+!` z@+p@jDB++ebFG-$H_gb5r_(lkxXlnYBHnl3lj_-LPZok-ZyP#N=Zu!Q5P(LhzS`Sz z!^L>vPNf{Fa6!Yqn0RV`#eM>XxpQe>u5B~Jld(?Bz|ZXREemA6Qm*t2XsX?kY}+v!xa`!iM|wn z#)R8x3S+VYNLB@nmpUI!{oVT`W3?5|eXwc8I8JA?*5<%j*HWI=5~1Wr4Rr}(mF`%4-cb_g6&q#!MsFHsI=J(i98aJTzs6*=Bl zHkn(pCdyQVw7SPLnr|?QZf^Fk`NzSIod@FsS%cZa}gfEFznwDrL!dl>SOP0ToEV{=>dRyw zid0R%Hj?7laRF&L54Y#;Z{5TF2~R>VPiNtQqM1EBpF5*?Fg3dsnKQyZ{f=zr zpT{-47(Mv8uT=ezTv$L{KR3}jrW~Vdh_TlcJnpYoaQCaL(pn|6tFr#5@1;ljgop-? z=5I0|)EYCEs09p&D#InD_|)@V`$Jm#lH0=tvb0rb|2%FJ6s)-5`luJ5nN@6^hx-^_ zHav`E6x4JQczWy~wJklTZC&aYm%6)uind<=;&1A4Cld4JK+A$x|m^ zNt;(45Sm&PzEi_X#IWL7LmPohAdUfy;das0&QoBs(%ToN(X5SnZo^M`c)E-Ua4km|=lsn6_qUPPyP}?CQwYU;L74Z6kNRP%@=BZ(8n)GMi zSWd|C>=!ofpbk=W0khd#l*=x=!pql9bbBgsT?WVij$%quvz-Z3F~{w*toUBKdl+#S zSJI$%03ZPgv3E4CZd49XGl@Wzn^vX^Ly=_|pn6BV#DP-Wd17F2b^^}=7%x&D*IR`( z5H`E<#S8z5o^o?UnDzAxj7N8R=2^L)wZf#X9Ke0x-lQhqX}<|-;#GeSM6&w*2L%foK1V|x+J(E& zHM|A|(-XknJ7{QV{rT!ga=6fHUozb)a=;tQ727ZVXb@ee6Rx;F4qXhZ^#AG3`32V^ zulb;gpneSr+~=<)t%dhW+Z{8%jug=wq%~i^86rbaZ*L#7`7P85P9) zLsGYxB@o`fGCxj`vT%zbd}SJR*GulI)P!NxUEfXu*NNxmg`pLF}#=-rl-I;Uszqey`o}iKFlqaS3Apl+0#DW3O%l>+v3|n*X_wHun8=@j_zCR zSr0}op_PIfUnlq8VYd{~3~q=+G}hEuf6!KQsxT2Q*eD&T0yMp=zT*w5N= zYP=6=h#w-oo^XhXF@tZs1QH+FJiK(u-9068hm{YZke3Sj99D5N%DA{VB6HS==WY=)cFfHf zJZZ%BN?Z#hDQgkk7L{5?f{Yu>Fv@JM~e7sbJ)m z!+LhuuVU9^pD`@?!$-PBzq{t$+W-8P)=9aM&J%W+!><5%6ojOtx0 z6OEilyVqckrMVs-tjR8k$H!nI2i9)MT~Z--r{Be- z-Th85z3&n>*k#n2?*$R5wtQO_Og^84b0qDX$Xc8 z)b*mGqGxej{gfbxa7IVx0Pf&tpz)W9z8nGZvNc2A1o&5(2D&pUi8qBx*r}kkA!A^0 zO!V9vPJ4J(qUHeV>Bg$!eH{0K-n=OWH9yx&g~3a1ZjW@n`mz%Yr*KyjkdQeUr&0{T zA>v$d`VdRz3#BYV7UHt&DHVNCe36sT%5*8y;D&n?JC6J_Blr%`2|g<*kluOhItfY) z&@?!|_V>3dzN2w~CuMt+_b{i44fe?V4E`HKiCusHy8P!Rp%1$D6D;u)V6c?Xksy(7 z-MZzzpLl$+4EHN-P~@1Ho6F+|L9xgSG%v(*(Dl-z#3rY*4>$mx+`HM-)D&$uQE;>t z^ZyP)%wc?K-gM0^slfxoOKhy_KY&Lrp#!#SXvhOBW&6+(1jVW7IZUQ5$CNBFLvKX=ZKb-IFH z4_(jEG#+klQ@D(XU!|6-geQ*4zl5BQB~)16Vflr;7r?^=)meKp*MP$l~+z@{sA_!zBaGSWR>uLv107I0?!-iMQa0 z`f_&&pE~do(hHi9SR{hK&z1t2hOLJPvkusTBkcCIe_|g_2FHubpB|6!T8wKVSDGGr zINo*2eLwzK{l6!zz2AWh;`l>$$C-A+QPreU%$BX))g8se@-R!T?F@%DFErZB1{=1( zyUam_hIZF3PU8EfHm!f(z5ngZ#3&AuMkpVZ zfB}78{>zn>%K z>9+nb{amwwX9)kuFtd8g6)Lpn5a}p)m#kf$L7!W2)wbfaM3=lP} zU<)?lwa|l|Smt+dI+1@oHVTI| zzY{x-9*L)f5x(Sd-n@NpA%Z3Juo4bhL;@|Qsk zM$^d18?qM1Ze^kfv-WpQ{?Ffq;B?($X(M*~!K>-|Pp|y-qO0?4!sO&OlAujrgkfVX zFc2a<==#EDA3#{Z2G=zUxOu{p*!}19Po6wc3=yRRXaNPva!}xK6TXS!e>>@3h0FL9 z+zsML=5N$&QmnwfDQRdnk-#ayg30$Y?(_#S&hNs1_nT(T+fx=({mMyq^cz1gVzZqg z%z{_gj27Bhl{k7~qc%zs8k{#lRczb&^XCcYE)DJ8+{LlB9LMd_OSIAFU%5CtQw;u0 z^neO^^ig8ApLjb`S9kYQd;^IDD|La&jTXFnN>-LD?l8N>ku1eoGl+kQZ=&8s0=L4B zqt~dXhnl`%KQfwne*Y#?9hD?CW^fm9g3yGJV%{U64#I?}+2+3YcH3;jga2Bxp0J?3 z@J8^dLiDr5#N&990~B}qa*vTVZrVhMTw!9;5w8aN0cYYs-Ospr>rlF0^u?v1_TXQW z4Tr-*rUZDZU6Cw?;_&uBF`R;A(40a>sF{nxUN^|WyD>*V|8ffiW4Z?hju72zQla-rg<`QPFg-C}fn8=ll;vLhNN=PyiA) zNbl(HehWA^o1=J$0q+RDriasmSGBaYqXfd31$6aW`@sgA`v9V1=(i89#jVz82|DvZM9nw!R>1<3+|=Bc zX~+I(rVZNbNAMJ9O#Qi}jpfbWPWrwV3YM>ff(ASzZemzIeAnk-iTRHi*WGJ7~YhE(= zn_ZGtf`|fLp)~`>3_9XCLK<^?yj#|wJ(n?5;z>D_eK4FzXy*{;3aWF3h;ht*4*mW6 zzNy*O*}36hV<#k{rzoEyoOwUPcqjg9vM#6{3sruW4}1k+I$>aoHA&(2ocL(8o=y7*;qO_sE@h}RGC-2PmMzY4k4D>kdkv&7SokrP`3 z>Sq?1L<(FyuwMlHuBhc`BjF_Cf4~L%oK7NB6G+quPlYB=wfS<6r4ih>96k}D!UgaB z6tx~QhpInXkMH6XIN^eL3nRXsX}}y38j1v%0ta@aL!S_rIn`6GVANCbA>7hZ3XxVVEN`FFIZVP3b{sabUv_(nvk#@ zgfk&^;fL=Md-U${`#-IhuUs)@^uV7I64R@YH!ecm_Tc$IIjr=@@CPsa5IzzC5lIuj z2BE9?P-EY#mmtbuZb+FFqQ=@A(y|%=PkDU@fbX{|%X|+91X!Fua*d2T5~yCZ>_b&0Kfp9siVCeV*9n z+;YkE{Glt?CKLGM_oHY`ep?6qhqQ4#v$sSRDARwyuib2nCAoihK6$)Opma%*3 z=+>ix!4c+A5)r;5(Ch*-*!=Mx!-s2$RPY|8d)9khoSZa)eu+W|4f^#?U^I&a9eIl+ zeWz5SxU`rADqW|2_HJW$s**P5rVx?4byz9nYo-CD@=_rYN<0%TWe!SoWba1Uf?g(+ zOYuyzl$e@6d`L#ZeZ*Um0f)(f6AcPh@v$F1bCdKJA3s`La(6p>6G#9OBj|Q+f_?%Y z$QS;Dzx-;o*5c|Hx~Kbs-K*e%N~)@82(2wd-j3|DF8axFDqN(KmoCu~qG52FZMo)K zF`if!c>ETLgs^Dr*a}`NN-0$QdY+Zg-|BE5Y#56Y=KnU;)z@8@tY+xGX0A69MgnS>G8wim$_$XOQGV~k-a9yP$U5b=* zKi2y7{xDf#XQ#9(@#`O;ix4B*Db}+_icqJuHi7#2BClp4)ZTIFXY_X3Tod!s2=5E2 zx0v#4?L*#IE*o))2KbjQIQUCgWj>Eq$Cb5E(Pl`go_zX_ZwpIFC%eX6?b$<$+klJnepe( zp94}7P<22gDBijNNCo%M2V5LERYO$HG8kZFZ%zGbFzig5lO!EeeLTI781o`KxisCR zk;C=Le$`G(SNDDTGMt+Se7@l?k?@xu9_PJ(H%*6_OIC-JMqQNNHnB9&Zap&`G(I)8 z15WjY_zn(!4!E~FIO@ev)wY@WrN;r~a3WpsEc@Rj`(QwvG!k*du;t?^@NInz4-O92 z26;2CXeQ`bZ^7tNc5dz+luAU+`e{jBe=7XPI2Qy_dlh{$%8Ja`6?v2l*1xQb0-`d- z(5tEseb)hwXS0zOMnqiLk){W$RU%O0CW^Q?><(UD+tAht8fHgG8*3u;B@*!*)5Cy@ zJz;sWz-r+7uwkMPe{Y1qSDryD)`sj)uAR1}QK-0iF`P{ez5xOig~i1ih?^Cn(jLH( zN=p9}umyG4Ttnf8;I8hN7+MT5rT3Adp1`5`D%$+(m)6SCJYu;L$PW{_D{f~Zrsk*O zXGbzfi9juD+aB>9T3TAy({#4rtaA}D$(Y*z7w*qIw}*y~If%$+(ZKwx(0sfVz8EGH0H;_14=F+h)BX(CUeyBmS)oFE>= zSmndHaV-pOt!?xHK%9s>LxE+J)G`UQ<{g~Zc273x+tzb!h*XIR$65OI#HO~g|4*7Z zkzOr-G;G}&Zu4&osSCkqF{aH90@?3xU;k!2W+Q1G-1z1WFA`zDx`J}Wr?LDJ&^Zzi zNPp=1&!V}g1}KT+TPVi15KipHr=Z7L^j}eE6Bh|c zRsD&S<;k`I_hDqV~` zw^{K}B5V1-kvD$P_3T|gFp&w#%cC5%-}*#)M&(L>P=UbSGcz-@?N_CM&-nTJ?jwHa zrn0q-WxNnsUsa{_`|R4sUjZg8!pCvzmmmY;Lnx26Tr==p|9t~}D1a6EGcv>r&524# zl)x%+OtZ}f=mU@4*Z}+;=n(V9wM1S;Ft}%O4GoK2QU!{f`B2+Uq z8-tnAL3H}bJd#BsFf)j9$c#8=wrSI*-$=CP>o#2yEq`v9vMp0tumvztMeU2r+9R7= zHCYTwvRh+s=ZQo5nFxtFpty-j4n-Q9fumuH*OFmLGhr_ChlGR}frWkn6FMC|{WG-k zHMh3*iPi=oj>n)Q>&xmRnOY|d^=JA8`S@x-?a>}yd$^_py+9KT+d{7Efpiw& zG!#4lJDI9zKnn%UTT|S|_wQ3d)K8d7lSPIS3a{jt;R65v%Z~3&UWw~pnyKX`3?6%8 zU>l>nMD=sN&gJwewxgLv!p$yPEvOy9YD7){QWJ$PTU!D%bj+N1|Lnvyywi?GVZ^7n zG(SpcEfD*&$-Jk08&YFp#D_#+Z@>pia0kHJ>Cc=g2h%JS`DPj?;NrNmh-8y6mKT-+ zwK?lWnMe+q#A_hk-Oj|V<^V6YlUt3FiHV6Y)%~>S(?ONvE%JOKlTbY5Fa9oTewQIt zpc_>njvxWFAtaH*uLliyE#gL-@uJPd^nriKJEk8iA$SSJN5HD+&Yk?faCRW>j{o88 z%ukU5>_bk7w7Cqgm{Cx`%zz`pSv}fW-~(J^|A7M=Ve6PtJ&Eg3Ip@(`?XWtf(`P5y z6yTuOV0;d)T!?`nP~C<%2l!m{{rgs290aUH9}o6A1pGvO7#wc~o%wsf1@S^W!R+)bgOxjQYqnxU1EbMIIV9sgNi~-+x?N|vSdPH}r?B&ZZ;5q#0i0So~EaUB%pnx7U$$oj736^uv%l*a{ z+-SSsFo&+;!gS0&|M@Dw$%H;Q0%)Bb0$FZnt~Z0zupCgMBLQwwZj!>IA)n(Zly5J7HXFDMU%DSW9tAS*vWU7fE`(=5-*HO;JC;5R z$Hu$`fZ<1jU-YDp(x&Iu0pi_nU?WQB@A4MP{j4B%=o84T*OgbEn4CPZ; zUGIZdF~7#^;2n#4&S)_^_MA_k^t#S9SHApsmpg6`{lE7j?`8j)aD&|5-kx0k^a6qW z5jhWuNR^1n3Y@jhF%;_d-&UKe&{5BB=%!U>H~C$ zqP7CSQh$5~4Nz}{^xLxe0C{P_)1eGn*wn;?Ov%c}PdCR3l$~|9Ne{Lc0_`M9-Hco+ zghvxM>vsT*FpwYuqz)YgrxCfnRpA0S=ZJB-9+arXTP>I7_#hj&{mhv&aLOtP*XhvG z&@glGQ)*4d89k&ZaEosEzLV(8L>31~s{{rrP-;#53Kn5%ZfrKniPSo9q_d!av=6`|e*=(7 zHnNjyc~)0ZCdZ8I(Q649_9t>HvXhEoCvNCj3+Ptz>RWf(OkQ|z3kW0lw)PZipE#;= z$e;z(>ZkgUjR4q`k&$sqPd9bR1iW+{0P!J`8&oa9v z7C6?F91dMk@txISk_^Z*B9Z>KTo>X)3OkUHt`c{d2o_q>pxcbdXlIajjH|2b?ds7B zHzYrWd=3t(IQRdqYeKn9=i0hX&iCnfn%4DI{`s?L{8egpq*YaK572ZwmtBs}o^2$p zP_z3XT1d5-f3ya8J{jYN6e1CPKTpcR!B7tL-Ydy5XQ~`v`dbO4xVQHp zv6RN+T}3T_%0T2Ruc8uwU@-r+fSxycFE=>Mj2YT8_{dPEB2OeciO^n3UF^c|B77aO zj>IS)1O*js=6hciS5_(_E^`AA%A6nxPC8|jcpom!Jp_JAG!>Pi<|yyATWBNcAr{== zi#{4EH#ndK^4(-)NO7w~T83U_3e$ZaA=$t$c$Wd7nscRbMO^=KlUM z@F|GJ_)CaC-_I-qrA5DAvxg2Z*ND^x)i*!lmkO$=nCLMv4js6=dk(Dw^9Zk$)Znk<9RMj?RnDSiyH zZN^6sEg)I^7FT|pbUTk|MH9SNw-L8_m5D7HJxC5v7In;$1oT23K0VgSMna8U-&Yv9 zFr3(S*ia-KT1r})41ian;Q{tPpk9qFCoP5NEz%f&up$>-8R>A8IkLwyjmvs0P2Tz0 zKFU~1njNRmfQuNTR58xuYmj<>jBL&t&KGqPg>Vp+Vlf;2uFn>S(TTXM5Qu&dyi_3;p zTiTt)z5yO|z&2UL=-CxAi9$b`QUpg;jV>xEaPcM%4;v&_siVh7trZ<>4y$2WwjpCa z(kq3vwSL&J7hz$<0VnbdTmWC7x?#V%%yjEkt4;l-6^qL?55JWuBexM6ePU|*{iW`y zFe#<_?V^AGim{b2QprcusX+O6)7M7M;8!SD{=?ZxLq3sM9ev!kJvjIO|ASL_s|%!( zZ`AiaUAmK*o=#4+8bVb9Gq?jCAx{<&Z=?X@jj9?1dI~;&Pq66xW@eWJpOEF{8J9S< zoHW1vbk!SrnHcW%@8GZpym(T{=g0-b7vv{_l{thkx|8t>2$=4PA9>#T#_BI;&!w67 zbzkOzUJ~FD5cw|Pu6Ik_=mZU0cED{R9x4Z?i<;(chJ+}Pq3Fpq`)7X~2kAD_sZ>y1>#(m^iZy#lEI*;d@K-8A_O~*T(uxA3cf$I5dSb>44q;_cz#> zo|RRt>p{Fq+y;Uxcv7+o00jQCVZ#Pu77C`WI&%K(SxqEe9||6z+KRC?L9QGGbU zt%TbecqFc>NIn--;E3IaM^Lb`5 zxVh>ZeS84Cay4gv4VC};#eC&T9G6q~kuk9~tH2IXqxOZ%7G=dEM0S=_{d6cR5_J2x z9?8dNT8+tLcgy6jEIg@S>4e!n|IBZ~% zPlPlUbcwK+Q-h5jDC79QejPjbbVi3q80t3Yp_S3qJp_=3xT_E^zj*WJ8^WCd1j+FK zilO2eg>zxoE_Fx`BxZGRNJs<&{DY_5OE6x#xpfUC7wDpBENImB8kttS%RecLaR*ce zeP6tgv9US9B5d#$rF$o5XEOXrq+FIy!)mG9Eb|Ax$L1;Zl1LjS@dkq|?TK$o3@VgaIWbC4o?&s8BL~_~_3J zA1B&q0vREcI4>hqC~1yx(>n>R$l~hii-g;kx3T%0T0$3DYCSg|fySRRWco`YXw_*< zg;L-(0=E$_FDa3onE1jLpZLrU3*2aj%ef3xD3Cr}xkYg!M`6-`6|1iDWnjn#SWVSr z%LmouBsXtwZeAZCj`6%G9+)8=Y08gLP4o8gQ5D3&Y|4QH*pZw!^X7h|d2cF;r&R*^ z2!f9g^@fQP^JQ<3@cg@JE{VTOR5QK$veMJ9^d=V^s5w*IH3)#yrRM#{Z|6$qQ*Imxgtqec?H}e zf&)I}&S*>|q{Pss`uX3tqZ>DFyi{Ok+lVIEBHk2I~ zhmPMi9R2Zm7niP~zUdyzUT5K^hzs*W`+dxnU-xxhYEttBm_=Gm6z5S0#L@}in^4vi zLv2HPdCI?t=?sHRz%fKxq_VpBRylSdPg)sA>REBIJ6(sS#UE!}8OC@L#+ z#;w63!OC8G1+@{$mARhn#HuGJC#RMi`H1d^31E9jBUz=Er>beqB{1)$`V4@D*&}%9D3uMOq2z&w}d!kLc%fhd=L%PUVC29M!v=>(6X{BB5u0Z z_~9k{MIm`Bkjx3b=sYR+@N!ZB*Q6;G`&1m32<2Tr1d}g9Lk)*bz*R@^Ut!$15YfNq zp68q=3AHrz3Bbh|{7Q3%n`elS=y;xaU5hn+@99BN|F5(KM7AJCr9_$f> zGa8yBz#)H3QZf`lF>!y?p+KPRx}PcnQO}345cqpN+&ZVd&r&M?JS$6($Kf~CqQE^oo#vje~K)av-FdKo+1+yr-2Q>NfoR{I8|$^HXK%ZuY|s0!rD? zh0ab~JCbY94P1LJWz-d6Gx7Dn$LSEd4kW0YBju{9Rd=l}cCG?{$AE7K-d1@q;7^}D zJQ6ChzmcE}UvdiUu;|&gfOc{47$SUNm9%OgPT)66Tp==)T4vuOhU#k8lr$#{EWl`L z+3n2qwW{hb4nr3%zH*v#l**g_&r9THXYWGv1r|O^Wmfp7ITREprl${Lk#NI5T3#^M zc0K6C*-NY?yHo8sf=wF)fqFjZj|jJ&VZM--8J6N$^`;rSme&KRQe1aZmz#k+h2-5(z1kB|T{#9LHbn)S@q?-jN6Vat8gw;7NaRJ50un-w+)o z7!0`A_Uh$JdFZslVgSjoR|}%alW1*^kl7*QcE>LupcFc@6@(<|__!tzD2f%ze58R0 ziB6+m;rS5-b|-GUT^V9zP>K|{wzm4lyx}0sjh%WFUka$^bWGD5jKK8wpT$lyi<-O* z+YdRf9yH_;=UZMu8+%36hA+}#E@1-rKJVk<>nH)oAwI_mD=jKI)sqU`f~aAB)USPp z6pYFEt&H1?`_HN2OewWY;EzztJk+WEZ=wCTfPgf`WpS zfiBzlAXTd{)qqdM3%&`9g8D*FI=mqQHt~Xd&@3)&Jx$53Ktjvk`wPJ?Xy8>%QX_`Y zb#!z{Vppt)R(~8$dGpUOQ=GtZDpgA5;x)5#yHor26nUt|BKnsH`CxjSH_YxOsH})K zU~EA049F?VRpI{MD}_Esbr+!+!OIHqh*f@WeZ7hRgyYUBN=zR9;V>Po>=G=jHdjkF-mp_PSU;XshFn?k2C|XL1;uD+A zI2nqGjTQ!orjQ44|E0uJ{fMUu=AsV}K}Z*tv7AOefX+E6=88YAKjwXF-vGcK0I=83 zTDJUVgQWLGBUm#Q#pgyBV3Z{yQ1Kk<%ok>!-*q;u4u$SUT|jn zsRSkgY`?cRb?@Zo^aN#f7CdwG! z&_+7FARG~G+3D8qA=`ziB7oc{YoY5}QHzNP91SX48+acywS6WdB$j)gdwTwvtd{PB zH8|V-z~!}#$(J@#WzC)8DZwsE=yya2s9UIyo`XM#M+dOD#PTOnwOZUWGUBecqGbP;0LKbY zD;z&6s(vV1HHNlYc+FcW8&FAHj6t6gcJ!Ew8}YT{6B9)xB~gVv0N>tEbB(|)?M5xt z1zusty-)|BYjli^(O+WW9NgEp{+cs$KG7tQ`1b>Yj9XeH?^bG*@hirk!fViFfBFO) z4z^KMpP^wc}#rF?;%5N4(LA(-6&b$u|1l)CX zbtvoYJAV8}SKog-{o$>p_~P`Cs^{7^N8QuUO;hzxfSFhXq5{KfyI=;GT^`~1N2?M1 zXccLCWMq5RZiC3?ULl)21VuOP-5?Q=_8tql+x3CCsAvu1#{F`Tktn~4mP0(C4CXF0 zW7Tv33O6|D%%c44a_9E1IlJfTx3`5N3*h;Cme{L@=H`vflkEVo5$mo!nwG;XQWmdrZME^K(LQlQPMZTYA-vjtZ^;+Z(RJ)baFr+wHcb+HJV&YDR)zIwBDgN`i z43kklA!gHFRP>Z!)8`}h{{}+vhHJY=8{+)SEhU_3f8WqEH2i_id7`Ut?DHfI3hMV4 zfzqBqeaA17L1u>tgYz-jLr$FX#U&*VOyEp`&Z%H^UDjO>u0^GG<_AK8L&-c$bc@?rsS|a6PEW!tO zd;EocMp|0#XL>(aTuXX>oA)|uLUU1ukM=EI2o>_?v35U}I_q3k`Q zqT0HyLDVZ=MZ^Fi`6>dEC1+3(5DZ`xDqM?@Qexd*ZF@qJKp*7Mp|h1YKU`;lx26s3WgZU%B*T1H06%Eg{7 z5O>S2sy`=wUX9NTcoNjhcwaPRs^l#^hGZG}&h-z)3_{F7k#nyNQ)zBH^F?lXxV%7M zHUr}Une6Vgv+w>QV9)A2*G>3TB9)0-HZlp2rQpg3zZx7wziDe}_T{N+lXF{rM2zEW zXuL>!qFH3qY(ma!^)@%Y28U!@y4q$;T2$X5k{@mz-4DNg$2UPQ32O4cq38}xnY2>o z!XQO5fod>Lp4?(SmN)vHQBJEoU9W+DXvAEP<>}vY??77CIZnXmLSz*9KqhQx=@A=# z^7P(E+ObM!vKGzkY7n>8Vh=`LU4`Qhm0_ae&O!!?>RV>!rKyz#`F3Zj?8z*$B-I=; zz9$9>tusA=&d8 z)GvZ5b+={YDMc`tMa##AhqJ}%^kH;iiy#B51_8<|=jB;U$uo2)?W*$2K2q~iP z9;5iwjg1#^sVa09e5~=_yyuediwyRYb*_mL%(2+=SdZ~TJr7A4YmlCqSpuEkfMhTE#@8@lCtL#; zSNJ&XUqe*&1QTwDO!lX8Xbdgf% z%Pc5r03?HyrxNRcFM(X^H^fWeSi&WdZ6;~@(D@0Q{VWuEzr@9Edmc{I_G45#I$3dS z?6Z3kdkrtt*fGf5n6678I&B7)Lm}Gz2scN5xk3m$1_qel^XM@o368FHLSFdSqZ|F- zKe{AOEz%<9!v>K~Y#}574QPEIIj9XE+z6@VIaT&t5!GfUg5n}Qq%dAWyd}o#GJwya znu622YD6P1b7p>#OA>Y~E=s%kOxOdLTM9ZcL3;fqUVH0c3$e7L#11?@4D`)sM8df$ z4Vl)L-gWW^slp(P-MqZEyBxl`djWmK;F9Z&ktfRt?^ zGQ&^Ul&TKzFMY+ul~20P&b%O2n1NcVhUVzwwFvfqV;{Vc`*D!t>BVw(&6gv%MJ;q_m6~L%5|Fc*OGj^8d~YV{2tVH zGq5oNc0f{Tg}o09Z_>#_81CQ8mfecQgSJ8(6%6v1 zbky#DP<;)cdMJY7I=Df-h{`~j^*lJ(7!Rtdx=iDFl75}`cIx#K7u#=oX&DKG!k|y& zzkmO}BfLEozH28K%q}=7T=;W{tXLk(4-CU0>v2H)3Ka%-VCmW~9uUeW@IM9l0eGgp zD-!RMe)jAlbY2R;=BiSlmKfS3AG!P-T<&Su=5@0bYi^ic46!@hpn{|r z(yL?D{x;x;jUI$RVL-T|9fALEfoTn~V>*92S)oSW3zfnC{*fD#+Rm$^rETahgOWHI zoFi(m_Sw;Sb&wS&wFa8sKgnykzq1=h5Ilz~eR=$<1;hPjBpmo+s182;m(}czgAan@ zodKFd)cIT_PdAzKtlk7uMc;fD={jI3(kw)ChLhkpZ;Z8q1RP1F6o$*=QipJF02mKE zy|sZ*$xfMlc8Opyw`)u0Ot`l z)Xw2>Zn_~5mn;G=VF{3E9>6ES7!JodjWw#aiHoj~`99hw=V{!GO6V@fwLZ9PjB(gM zua1D^=Xntj2_5l#p}?41ptBK&qL+gIWtw;_=YwowNC#H@dS3%$2n@1fSNcVpx^ug1 zYH#-UHxVVpYtL|GFCuQ6UqKt+_9gyofzfC{$GL z?dv;+3CstE_Ekns@|| z$1n&ZKJU_vbahR%;L`sy)txOR>^&5~P^dlxQz}kFlWH=&lNBBd8QABLR3VRjVLvho z{O5TrNH>02+IacGdc~#K(6I5}J9u0PpiZqo=js!9aNBH!9^A!)8{0+#&ip1X;j(U+ zKV$-_zm9=uZ)s~wHE(zBx9sm9ng*Er)6uPnAt@?1{PX7@5Y~`X@^3*zAmDQ*938E8 z{obi}QG&zt*l@pNiBZ#>U8B8d`evq=oE)Ffg16XsmL2gx^#F6$vPpBRQK0H5Cl}|D z$uCpx{Pm~#G6(M`s#8WP99AXuHnE09&DnBiYzaaXGzf<3a_g3b{qq%(=+m9dhg=6D zT9Nj#7_6g3_3$>fw9rYkp3R(iK>#iF#?6jt@maUG4c-=TXED{cH2#77#>3XYw!FNYo~phGm7%|{U;o`PMp<8X2?i1Ze`I!S?27CL z^dx*hB6;keA8Xfn3gA&C`FymWmtC~j?v4{b^Y~s;k!c<@m}6DnaU3EBrX7l&wIjB} zEgx(pZ%{pdek5Y_J|`-#n+G}*!QK>X zTi&eMmeS)ACO@S|#5rx-jWHMlYiol!NO0-i+TW+~@j1UO#;qjq8f82hQdwO^fnzKl z*SaI)l=3C5An^k|R$x&>YafuHxE@EfL0;j>G(=IN%TX4``{(o=);qz5LZ;Fo;uOQf z!;~U(2#QJhFWRSvs8_Qi19+#!b2e=6B^3v1@SkLx_fv>kTsJN7?>k4{2Vs!2^=KA) zZ?}ymtUfieJ*&I!!9&iE-SnPDKmH~#30@rQ?+D&NVJ-=Xxf&ZAlL&M|H5`;Ax-10Y zGPsTMD|8ylK~0Ibyqw#nZvr=!WIm9WttwP$Il&qku5khCxK$a92ta9Z<*T^*au}qi z&W>w?RK6g-U7nXMxc_FfoY42K)rb+Z14GQUpQeagq!i+TY~w1Uu=yFdh-Xh}g^^<2 zjjXJUe*f*XxHR{h8SCIKF7!wjoL1@2o+?TRiNGrp3!q9%xnVIu zr0ElPJjO+b_fM&y57Re#T@c{7@the9zVv8F_a$2>Y0Tn>heE|LA~yCTY#y+4ayjz@ zlzBtWW9g6bqXmZr+(A%7({s3r7Vw$A=MxCW{q}@S+?3BK%dPUf|K)z+Nzad(2UZk= zQxOI>cGwl;vRR(a*i0*OnHcuJxGaWlYM~D^LevEWxxGT9^+OhZVn1A*?P3o})aL!} zfianIou?y5=RNncD5xDt;~M45Z|4%RGOY647Ebow;>}<6n4MjI9weyh>hYxT;^I@` zQnAJ6P6i6WzP>&nbep;+o~{oSic$F6M2tM?x_J<1?aZCMKiL9JAV16HEXhuqm`iii zk&Cqn<{Pt(Q{E!ndYJ_rV(Y)EO1KL&Kf=dPPj(M2Fp=ob;R$)FamTI9K2K1!g}PP0 zzyHLVE43#sbgM4R9$s+}6Ta^2;Ua!~g#F3AyZ!C0i^K%=pP+WqZcE{0-s7AEX8Z0C zR@}vK0ibV$I{4VktPUNiPt8Pf;LY;#dUd3%_wK7CVdpPI0oT7CJ49vAs)opSu5!AK zoqq`ayPFG@8^gYH;H$^IRqz5H_~`YJ_IsTgrB{@fI~{T?t1okK@nuHYFnoROqlW%H zHgo|vr2+uhkcC}-GP{sZz_0c9cAj~E9*Jf2c^4GPfF{fEJl<)#!59Oe;o_ff{{03( zG~>2-!Z_~5qx(J(>&*MJ!ivO|aM>h)%z54Mt(=wo7;YP#`nKQfqW2j&Zuv&eJuJ7< z3OY}sI<2#(${)EaEGQk%&6|%2?3N;=W#&>YV-;RwV~$}nY+l$utukSDTH*P#tQF06 z;@`&*nrlTM=oPNm5O9-*rg>GopCYyNY?VCNg|)Xd+5#QE{+)$0@jakr8^Gs^Yd2v2nA>K-3EEI)gk?8BKb_wD&WIaNH` zVZYbyZlOlAZ<6lE5cjMRm$|QEq$kd%heiMBBz+lG8}(EjsWc&}DYsI>uFvVPb!!l` ziT_z2Zehpwif*^r1t<6AI|CW2l`eddB(mN{t0z9kM*EWFJmv)lajWWukH4D6=ev?{of^0num!34oxj#bIu2Ex>6tPQ}Fd^ z$JKY9-;$Ctjm{fsNe2^pe#cL0R-+N&3BSLD2EZClt>7-uM|D&pq&`Q*wtjU9D8iWC zjM-@VzPn|vx3YhYqen5AQt-uMKrFmB<+Ny`_Os!RDvGc*fxGW*G7Fx_Bgz;k%uA8R zD@Pm;=^s9JU9!EASoJH)!!DuPJ#?5L8f5E@n*`qUbaXD=RY#TlT~`v-Ry(hLj*aEw zc~@2CE|zCgm)ss!g2uCTIQ$1fZVB4KeRqAV_$|EFDV;9=%d+ZrCl>eMo^1cwdO(9b zyXdRG(4oQ;xlFzru`yQHs*UxyO-j-5wPJE{w=i>FuxV|Z2gvrh_-Njpwqyn@uMO`& z4*J#l0XCr?`#C+A&%o5CyYurZF1E1EGk>?}v+kPoZpo3WVbuQ_RavRQo3E-*tb}{r zWmb0z3B6;u(7y!irlVJeGD+x(oV<8!d%k`lkum(#n6U0kojk5Ec<(RA<}NpS@-eod9?_19?c;vg@7!WqyCa@)+-YfRwHk zRJJWGEkC*@-hPS_4#sWl#6p{K0#19t-AW_S`b0cle_y?N;p@rlz5SmZ{lUiP)*0e4 z)&M3I+cR(Y7%^wA_zwN<9_>{ZP~AL&?X+3zRwNJby^*c`U`6Ya83m zM^brGDy+YI^|>b&0Lc~|Md3U%n$7HdaNvs0YD5skKnq<{!2r+_8Uvego=9MT@S%yX8ZhlhrC&~dD#R+G@)XK5j(9wVk^ z_0?Y0W+O1L$Y>WoafU;sS_U$^9KrI3-fG=J|99F}FcX?=uKCuO8hiOeozY0R);^$M zyv}R1@~zp|;MU!b(@t>oF$>&g)6brd5cDAtGg`A%A-?9n;*b$Yy}ATY0amPgLv~pi z(&EeEH1;xklKK$$jkWR1pDq6unB!*K^WHB@m?c^&f2BWqBN5<*qE)0l_&#WSdM{s@`__^ib z$qhD6%oON+;ath$TJpQ#7()uP7>;O*EhpsS8N7!04?ENJTBm z3=Z;Mr`+SklXCRzB8=LI7(?aYoqJWV6TS`sX!H{@g9{211NpwR{x`dGc0%jlgWQus zAbc#+>5HuYr^}R3K{i%*Q|G%)-3SM;$$@MgNp%Aca?)?Y)@F>GIrW0o(!0^*6=X;BB`xVX5TktvsBub<<5l1hc3UeiiqeYVXj&nF*P=i-A!i5c&yM%uOB6RZD~kpNSLLqBuxMM z$)gw#m(9$lgB2=OH_+42FM&rlTiD_sC%=q z5q^^bbo>hVeLN7^a@jwGs!!X39d^qx^j-Kr$J=cQ<<3fhP>#{%Ef;8TlT{MQKMghq zI=ZUr>~6Cy(_*S!O|jiptN)u%;gWEuDze!uwX+COTwLDfQg0t3uWPv6ckZ;nb`nHh zX77I8#MtDv?b_o$dQW)_y(mjuvd4>^C|OchTd`+W-d(J#=jq)9b-$aQZ#SGynhzGs zr5Fi(cOkvx?mWMgf_-glJiV;y&o*AoWwGWu_-40zU@2X;17Xo$dKt#%4y9w9s?S-{ zCiI&iraN84UhSUvnD>1Zgrnm8>k1gmBgCAKp)I!0@EV8JuBx;qVlMNsn?|H*+SkwN z(=7kh0^Iwg#U}kxpu4_d)?h1cXjJIp9Hh6KQ(4EZSFRwNNuyJGy}~*5qxP-M|H0`b z@gw5mb{7l`qRy|d!ZzOy9Y#lmb&sSYIe@~)2}IvNrp|3fZOXvV1ve^nCvzz8Y6MGG zegEJpTf$hN4L-4b1C#dS>{P~wM*ZRpTIn0SeadGk1`j9`F81_?5V9dzb+}s$F zbEGM6PC1iWR^4XiN1g7e&2OQOB#u~8kAv0F?hJ)NP?Gi}di3F6FqClh#BG+9 z(NPzx^mkai{g23J4SyJom_KsNORS`)_L)-4xP2(jdN}a0O=EAMGVPnr_XTs(cMc0L zR1WZ2EXYZ8m<>&B5YEo!7H1Mhj20{aM_(N=7-`m73o)>brP`Gwq+qTbt}6*`Y@LZz zdblc<9601yp-yhR!WM}0^9yhtq#g3R(0^B0tc6WyJzgM45evT4RphfXhDIMkgD!JV zR`??|TPu{+DH9W1t2XhpO1I|~J%zdGy<$y0#kvxHi7`jdpfRc;i3B~zSCn;krPL)l z(WkiKIDSK1rc=wL{!I$@ktX2vFI~qSTaBX4coL=9M;tz5uer=k^#u*I5L0Rbj(4J`}jBwF|v?bu+uIsE<$ng zh?GKGC{8R%bgH7N#9^)6(do+Jv<{bwOq4DA%5-8vB=;$`=)?+{e zJkA>F&vf2gw;^0=tSZTbg2CdB|Eme|EzZM@0HsWC@#?yz%nI%RBqb6af+3X3WX{9p zw*C4wD9DTvq8j<`h#GZH`atU47{6I-v(!Gw6wNe{qg=0e=U2nV&bEh*{qc+&D9=9z zAq^VpeM(LW#qjp;uQZ2JKBT49g($`_VAKDX?@dBfU!-_UT3TAA`|$%jV61X7ZXL}; z5LG(O;vy2{iNC(FOOIy2_wr;WLPz9?_SYu3waa#nIDIc- zbe(AX)xz$xCq3#pp!(F!?1QPe{ygVQL^qrxjiz~dV$Tha7akVYRmJu8x?34Tg-w7$ zPyX$~Jsl|a0;RikU`&scET2iZV{=* zPXYRK$_tRD|JSJ2&Zm4l6TU(D?31 zl30U!ZXy|=78EAju2pW;J^Taj1l6x-RTE!x#lc%wE6bDR4LLHd{MF1J%@^SfpEbxI zfKAAvf~MUzqFqwubglgdda6H5k@GdlKC=VViNh!_u9^y)uBeG0e3L0oH8*nViB2|g zOVF9B+Dy1UEsYB=bjHYG-NDxJ_jp4POphZci;sZz7Of{t`-iq)Vl7x8NdC#UGActm z`c$!hT6^PcJ^rF)KeyCuHrJ`+_;_!(aJbrlkWajz$u*lV2bvpl0?Dgyht%ZZ^3N!NQ=5?b^ zULRZ@h>M^m*75=|JC3UpK?CuJf6`pU#C0+xM}E&h>|b@JoSLt@1S#APAQ+_>7_`c) zxSO3TwPvQW-2BZPb`zSubab#FF+QhGS4F`Z8#eoh60WdA+Iu?_ zf7c~fd=cEIFa*qx^1GBjQf_*-$^50l72r_C<*AmpUV@DYG*_ggM9&3HHTK^LlvGl< z%a*Zoaoxcqa0q&qss8!hj#P4Kr@HQZOyJjZQ}N_^&eI4uO}^d__VrnvuFJBr%sKAp zsqyKwq2f*cPVKVJ{WyGNT(Ke68BnHQSs5WbEa$5~qi-fW@?Pwkku#0=c0Io@CPHxk z`fKR>2A5|fktmfEN>VN;a**Aj&Zw&+yW`yJGGt_14SUjmCGo$DjTNvOahb_w}%j0Ru-TWiN-irQg^b@0MAjEc8$7uN7)*f%$*;T4JrtK1pAA@tw zz=HMhNr_^fyKtUF>(3rYkZHdFBfN07#ahp%E!R@U%yMRY123`0$@GBT=ezC38}Bco zVJWndz=2*LSoQOhFPTHTB?!6&F9j0W<<7}hY@qH5m{7`hbyXRmT$3gjx)y%-_<=Tk zYH8^?3Lm+I8F{6*A@uddac;XnUGf1>eJ3VN(WsegrKHe4eE_zPUmN2FlOJEf;Nk7* zhXho!11mYoS7?M4W3IB@VAO{~pz1xZpQ$HqB2R_@4pXDpBCW}R zad-Jgwk)JgT3PnJ3llx*FR!4uf*H9M=LBu+l^(FJs?pMN`S}u5Tq`?y^E5S$`tf{8 zD6W|V#KjWwh-9#d{Q(UgOA|F>mEH7SNRI`jn>qa z9rg!LftOnws!f_yFUaiMWnajV-N}tf^5lKl*`ZPHdLgFkN?25^KVtl(7W1=3xc(t= zx!zynE9v1XF)(*z{PT&fMMU$uS+=aI^_ElfJx{d!z%6S*RZu%PTz~p>eQ|};XotTm z=>|v?-`>slA`@7UwK(o?-LD4bVry|mzsh<3L3z3Jo~JRMQ4?aO(OGVwYrN2d0T~p9 zn=b78?sStwJ0-7whW>S#+7voipkYCI(Q%KFn>!L`)aP+UsxA>D^^^UPZ}A(^qazXlV>OD?B>zwryO^6+vXQF z`@Zmb;5UeowGVA8U#B!^C9&VX?O-aO3J#2?;EDv_ew@+r$Lap_Z2SKogjk!pf~;j< zwa<}f|Knon^IunPQai^0(1z63{YW;^hkkt03gZg_fs2A(VTIO=ZlG_8X=c z4SeJ8O8M4p-|ylMCE=ciNrTlU9*a?cUjp89|KRq<2g1QH`?J^4DsXOKZJUd7{R}{Q zfcl_Cs2#C{N{j3eN(jiA)IZ3Ckujl>X!F9lOUG6dp=%T6AEhU|)L8Mpmf9RTL$}1z z(Eeo){+U|34}x#=b?wR&$2b<< zJ%UUGwfg8?$CHO?0fhHOMMHolP#w*bLuJV(Dvqph>aJdr+ruX9miFodu5xvT>jhpK ztMrcgsPFkq$*`*|;~_(lRiD=B;o8(;)T-G)IcjBSS>NYpuf+L2x2(*|*UL7$m9C;6 zI`TSn%n209HMO)1Osw_3xzWge`u#gWaHvhIB5II_QTEt~1(@aD+3AgwI7?Mx8fYG|EM|v9qs*Ld(MIg`K*o4v;gpPy4om!C zfs*VZ5VOG;$GgKf3sZAm`owZ6=NDb%j`5)*Lc~>AOR1UA3so#^5PnOb%~@nFyvsCM zigGDs?OR+K%rseFbUpefyzfG@eWDL0BY`>wrPqTftTFYs-}e8JxwEJ8gJU2e1i|O1 zhw=;qtgcmN84B!5lOusLd!ndY0(qII#zD79*Z7Cm-9`VX8m*3PX6AM>W?TI`1$tMC z-|Z;GpZrr_TH2{CuJ59_Tyq0$8b?=kLtS_E6euT0lhz!hPBleChj3KxQIPFts76xn&^v5Q^&q6V-%n`lL|K!D-58-|FhMB7$Uot7# z$lw1IR8zyCNJR79WpVJqfPWJ)DQSE4K>LNQ936>5^B8{!5?1ZFJOwr%O0ilRJfiM4 zd?aEi`Cm5b|0?rE0`~DaMZIn6)PkN$Sxi@V-{N+Tv-HQ0ID(&5tD*r2!MpjR9KZp? zxl~A*poQ}n?(XE`6BIuqV;!>eO6~$3*Pn-_+`Y?=D=Fg2p%z1o!8;^6vBI-bkT_*c zX>oLu$j7k8AtWUQQF>%Q_J((}bp|MaLIV!3)utW&%EY>>9BJ9wc49-F1eKOck9U0Z zXQnmD90&*!65D%fCZ7NgJ3W)Q^x%9%gK|-!S6zVo0ZMN5?82+l$k^IHv&IPtiO}rw zD_4d`(nsJH`jfTrr%*6Zdb#f87FBOwxim<)f(rU)F{liNUBrE#Ri)0{V{=I<14Z3j zGn>u64+<+vs76FZ@r+j=GO5%)km_5=2mRFC*z%Jyc7{x9KF<*$epnh3OZgNzUJ#t{ zz8c+E^X&`;4w9K~?KfnJYisM<1&Tq*ci9>`I)Z^8t8!T^U(v8ymC)TN>{W|d)}j&a zytye5P`mMN>3>ZUPA(p!;rjUHKj{>>q1?%CpvmP`NY`@0k%~?YT6nNF?%*{@A&sFP z$}bet&^jFJB6{?t+t{r1kr5DJSA3^!2){w;t?cl3?<*){b02OSuIJ3dX~nZ>Og+t$ za%&xNNbmB`MTQ$trHi3ObLwnvWc`P9amTK>yadPnp;H_3=+n-n8U zfS(I@FC%QdY$vVpCft3V7kE)8MOBgCMf_+SwaA@QwLiprwYOEVJy{~Y?iX4|+$<2e zry3IQ!e%=8{O0Z0=fHu1-fO|o_GUc#yd4+%qew@0x1K)4%VuP>C~VI?tvPhR3^N1* z`TWy4uYrU3`{*h=EK{deS>w9h1nKip52+Z=^?ey1Jizc-gQGT^1?%6^(|eEoH-?5s z7x(Q`-U20OHR?jo${Mx%)Kp~@k;oqaQvjv!MRjD8{@Qmokm%x{}a^#&<@-kS{wGNBz2peTP7 zHC%ZNy|XO!w@!OEtt?yHS2=xn*WaLI}_GX zJg7JbPSpn}q$J+ZtuC@0rH2=p1|dX%@G)suPpYbRT)9~%T|n3;oq+>y#! z(G@TI98KdLh;gF;jw`%K^sd;;Ki-E^{ zLTUD(nvnd0EyEr?Py;XtlmI|9tyUBjrAu1Tu;E`Z_wo)Kl}f8`acE1Kc{!_TVr<+Q zx#?}#<|0NWM4Wh?hKC-V`c6g!vzKX_oa*Jxv<@5caa0gJxH>}tuFdz>$C-bbz4T8z z?N#)M1K$pW;jgr3$%uF(t2YGHx4&#XN&=F%qocE^nCbNOkk?6l*O-KlVw#%#W;E=a z7elB_M&5UuPyP;+*mWrUoS%=|2}Rp_6qc!mYJx6^)Y$L+@y2m=dpXZytjEcCoP{mR zj~9=P`RjK!k3ZQj&1wyl>yGl_sx`7O@=R>H8&taOrKXe3D8Y)k>-q?pmvdWZs39SK z;;O*u5*dNNuqsdFiSobpy?t%+`cH-zgI2lul~X>%qW@G0I0Ao*sV)DPku63Suwmaq%CB4o%OvsE{5Y$OFkM0*dm6ms7S;*+{`~Che>c(cViD(xhcLoO2{BBlg zc#3qw^++q`EE#15Ag#JcuY$I!YrL}Xq=x^IN9Jo|6R1lCq#K_hZz5Lyzd6jz2aTES z0>o~6f27+?hrZPXjxBMa%=LZbVM5nY)jXcf)VDj_QW{OpYxoqy?zo975IxOA*h5YK z@-?VZ{;V*H5niVcjZ$Yuf%T-RS205RqH2vYw`REI#=1*`UcQ} z-)n~-WG6Yt0@@X;zBPfQP2KxOd6Y2bhbmO~jW@uz76+`Nd zH&t(XMSF0O#bP?M@o`4#gwV(sAH?{cVkA36iAu*aN+I5Mi`5exbqw3;BCaGqRsmHu zA#z@8{3kUx5?kBa49_R1-e{f^0q0O^fvy`eWVQf&gi=vjDXv}06@A>~A#YGvlQ%F6 zz{p2Q&*{j2Dgl!wnLkolZUld+{+p#7R1m7eX_yYjWgl&$=iLcv(Y zO82D0dV5F9SKV1H_107pS>y_2c&?2P~SH<<|l{K@2Zm;TB|61n}{gl z(fM62UDe@CA|i78=fT0jIv5}Qt%#L&J4tu<0_?F{mA*V}%K656p6))w-Mi5^^pfBE zXJLYI#cS8T{_T^loL^Em)*8}1{U00_dki`)^%AxmKf47P2=E$;tlqr`cEjF$#uwGu zbibC_1U}nO8c;`l`_Epav|3!dqY!iQL|Lo4ZqZB}UCU~@?K?<%n^@C~e_pme$ zF0R?RqK074O}S?Ux}b(gtVyZBsps;JG3RSOkw@CAp2%&g1@2_|F4iA0-# zu`A1s=JJ|}86zU16%j!KDBPVED!fB>OMX|)Dq9Uj|G9+!C00`IZ=#y^=1o-Ve9)q_ ztSlWHZvf%>S?5aq8J@j+(6UU71Gn|yMc+G8MBAWe!j+ldyyTW8?OcRcEi-@9w7X5! z&Rf`Jgf!Xn2W0rTOU#Xs(0d8ssQ26uxgPr?BOp3#?VT5<>c+>eSIJ}Kbydah5Xi=R zWM&<926=mZ|Ni~;a;iRHl?+U~7YA1ina!tE<9KIijj%tNv%?!Xa#mWmoE^nQzJZu< zd0hfDfw=zuZ8LTD6?>nk{%V#&;r?{d7G_r635V-m;|d2W+mJ;vH#RnH4f^UO2zfv+ znEL)_@v`-`>t`F4Dj0TZ{-A2ZNxWY=mP0KeMQ!cn&&682# z=(CIo<+N07x;l(o%1HU9_Zd>%!_nmMpmS}aPCz~G(xhkL;JTn0m8@b{Po6P_r;zhB zPznnj32h0n%Bq-Z90{6ES3_MWqpbxXiUaC>8Qwex=WF9`I;u`*&D)3m1Rp+JWcu(= ze1;iYWy;mXMJEUOT$5e<2yM7vFK2BWrBS*b{gS81st3%X;h{cRbvIy!w7O zh6Ou~=V8U2Z_po?}M%tb3nR3Aqex4BeAfI$vd0TgxrL zgsU853{@xe%eM8Lv(bNATdR5x{bb8jqb3E)>#Vak#+xlMBDET>&-K^G$|ceQ4%qiA zLv3U`Pp3-44PRt_w|3tdj@jEO>D$|`;gYp_G>~pd6q}eJQ^$Yd%`!43NmaZ;lH8+v zzCL)k&bVZQ(#o${YlNTfkd%ubi#smgtcF4=`*pOsx zAIkL*S$ZqKvRzeLtg!USuhXwnqqV8g6|G~PH{;Eh5@i+=kplgT|-lDjKL!{}!><5SV5KB)&(LhZxD63DUf0p`@g ziNxJ96Q7+Z_q~wM53O`C4(X%RcG^AtQlv5X zGG*KpwYKra;Sx?(nY|KUKAcJPp~XjZ($wDmB(W~3-jG$A-w0B5^BEZQ7=}iooG~J{ z0X0?c#i5hx^lBHH!u3O2>6H;wW}RlKj)=QN*3(Ps$H!O9e(9g(H0JW=$c_F{T9o&E zl_MHYlP?;Eou8;I0v92kg^lf#wNbj@VkzX%!6<#>8>}kdglBwbkj!SMr>d3bSm{il zgdlN+enc};5pC%?KwBDhb`WECBC=ijka4>IDt4WHwjY(4EO^;tH3^#joU!(~13BqE z1U$| z|813iaF#^=C(SP@{va#7>tVD&1yfwa5 zE{5cRJqBt9m5zr0Y5^jHf`ZPln?4j&nUNsq)(6k5f$#CnW<#uRe*e|)qV7TDW3!MO zCot%T|rtN^Cq2J>3sG?^{f+EzIi(R%*u0DMa0wUd5y%3hs^BsiUDm zIhVJ&E!Vt6DO5XOO+v)B&|9Z=DjX~Cu|Xmi$62Nlurck$CSZ8tk2b}vSQ+m#+rfDE z*>oR^-pK5cx_R~Wk)r$#2i(LD2&q&JC&pqcPU^EGR6i%{F9;WAbnZ32qZv3Gkq`fN z!UYCh|{#O;`~+i+9|yZImmdrUQ-YPr7j ziP?cR?&qR~oF?FIle_fu*mON2h1H_aT1;AJw6)@2wJzhYE>1e@?^G*@+@KjN)Z|K1 zee%Rt^k~YDsRG)M??O9+Hi+gxy^YG@Fd*wA>$!^egiy2eRnkNA4&7No`HOg2EF05V zHhF5fgVa-ZpL+WcTvSO|3pdTIL7{6J|e zF4iQ?E^@QGN5%147L=B%NU$fUXXKNJvGj%pk+wAS$!_}aA~>!fEYGuv^T*2(M3N*{ zd3zHbWX7LDIJA9KS1m`DcWq+K&|wd;r2^%z{l04!);qR3J+6ls6&P_=gTM8&kJOQ$ zvE*9|na`GQh6ty`<>O;=kImQlBgDDbOr=so@nGEGGcHE#|M^zb*3LZgc>Oq&T3OP) zx=GIc$*Rk5Mz;HMqeS#eorjBU5Uw01e58hlHR-UZtrHfjF}D{=J@)qkBRJW$=V~L{ z4r=Vi$x&xBT*X@X|NhK)ojwMUZZ=)4-=D6H0Cxq8HW3#l>`Wu4m+C>;!PcGwHe@qS1(M;gs zl~Ekp_M6&a4j!?xs^%A(=i(FUSmx=Qnv!~*6iQrMrDjt{2doI$ezzubsCdZ8MJuk) za-=BYL29*yE;)1}=UcuY&E%q7x3pOw^-zSzE8vXV^IM#&YYRN(*@dT{+;@^KHrCUE zNZFP;V*ApQ7FR|TCRwynS}vg5^A%|?SJX+`_#D9jkW@|^Z^CcOQy2D@d7W-(w{@18 zoq8#Ado{iEFRha7>SD9#<5ejUJC+%DKN$RIBvr~%Z5A!N9eEl^?WM$u)oNXvdq_lt z7#Ci-u`U*s>c!CAK2T;5Rjea#jKNN=qaRAUAd|l>bD6Z&tA0&L)FmDi(b4y*SGn-? zz90q@8}1ZxuCA)LIy3K6{zgxK9eo4S{EH2*jRZ`&K7Y=!`GjY5TIu?hy6QYhM$G20 z=OK@y5r>Vo>uCz6bUJ2$PE720P1fFa%0qI>eq|SxC#vsZ^;?8;f4CrVlujC5x~j0T z?Xg)~)qhUYsCHUe*)JFsUTQxpUsji4<$tn1yeP&UzK5r?j&GznV_#Og?In&Bt@BxF)M)XA)+w`kYBRC&NGKAG zrFm@lyg<>O47Q-^II$+C#!M!oHsRkenWAb7m6qas<3h(z<$P8+@^w+EuVZ};Ro(JO z%h}x#8xVcQ(6{#~{N?fR-eg&Z>fx>r9_Y(IrOpc|0+5ddZ{vM^R~eWl%FK3=_gvhd zXRvMbbKzUdo)1c!ca8Z5?d;CFLl$Db$k$mm^P=|K9I@r*C^!MEO(YZ0yJLXwSNm=U z2WFv_%{QryUxMbtW@ZW@D0o=tvl{LxHV{5O?+1atm6Luu!pcg zYAHjzDU$J~T1?wsyzR=jo!y-|2pOl^m!n7Z7Of@F9y3EdoXt5qS{@#xD29mc#Qc7z z6z{KpuHreh$W;XQz#oSo{#kQ>b8;{IaSGzUQvdJERY^SDx{`LLS4{uUE22+9_5dH+ zMb3s#mz2cx*G--`ej?DBhx$UEingVCVLx`ZIg*$kdwQwA09&>)l%5N^&AdPcgu=|S zvT@R4?~nQ7bv0weV(?z^s#PH~5P=;p*2qrqga3>L_eOIz91Z!x?|uCA^i05!Yn4E+q|(vn64b(3vpDLGm*Z zGtStY{Cv{nV=)q9;zz~j|DbjbuIyMX>#w+~sV&eYX<~IK?vmJ2QBsz!wrtXg>9=mh zOPL>ara8Wvq|VnyDa8*K_8hR*GqJO)7oxU^oYs0(Z+Tr&@tF8@usWEPbu-C(`xo7) zO#K&n>7Biuoai@={;Q|&+CuXr=85YE4@%zzVIN+8BX$VPw3y( zV@rf-#HLI(dl&HfNr%rF{~E}^IBuJEfJFckGEcm*l8=qtKL!qIvJKBU2EY_TBf=u> zj!9#0BNpgWQg*!0s;V>~X{M%DvM^z9+5f`d-+!dW(lkAJ681{>sp;nZfy4H_(Fr45 z2$a@3+-+M4(aQG5YF}>Yd85{L{F*L7P|=#ANT^&|DfS#+RaJOoXdGo%RvIXqi>$v? z@ctPCUOY5*nEhPo=y3Flz{|?#<_Lfu?3TY_)&1rgkX+^6G;ny6iQx)o@KGRmzXr!``|S)!9<6G}66N zB@--tdd|+#bxD|_huP;-_f^~Wq_P<$Hd}WF9VGVKPP$F^XD|gKuoQlnczOlhB|yjs zTiM>mD;t2m1UhMG4CdyS)@+RJdPsjS;V-;kMGcLt64Tps(%#vsGvsm1nw>5raQ3VVDw&n6UD0M^~kLYuUvH zN)VDbFg-s%Z-EZ6A{RTMrxJ75fcWdU`lTj!uNvEPhJt|!No>eT7ttBn%naHnQpaeH zkB(}CBjV0Mc_+Tsc@U4_g`t8CrNiXcnlz)`vq@6a~FHI%rq&Q12=eTEE ztEAl3-j};<+`ZYu{46lS!n(7a86rzRE-LLJ#0vhC-lhi`Hb_p;TNYLua<2dVU3uA7E?wOpQo6DP^@{97^$kX#)6&V*j zNRnO1ds82FsQ^8TkV8|ji{;vgVl*Z_5A3pcMNn}TgADpn#7Tu}9fOk(rBSq3RAfCW zOMkylt)DMEC~=3wE?Z44wT+(RRt$I#JzVbpn3mUf^{Wn0r(t`O7zr!rwq}JEX?RB~ zLt)~Q%@=&hU2G@Wq?N$5G1tlO%L<&au4uPAbtZz(kX-{{Vn1`5mQGrm*XA+5+(2-> z%_qHN?8gz8H5LFiJoYZ?g&*qNUAoY?Okt%*=vjEF-Fi1qwD}q;qU_Z&IU$u@ZXC~H zblNZrqA;V{VCe5o{hf$_N#UHiG3WI{hzw|pIjf5H=8AO6P7@x;`cV%~R`Q8N`($nE zw5@4uydDEC*JlQ%oo9PXp?A}iDGKJC=+@Tk$;o6p&r*QF&emQyNX9SvXUq zuFsJw{LqF{h}rU(F;5h(^>xcbE|qjWc%OI z+dqqEXR_X810!+iigD-o9y=KUuqy#;-u@p0s)Zd%%u-U4t!WufL4{N@&zamtFcA0YPq*guFCZ+?oBveqoJQ zISRadeBmow&FAQxqsr8M?59otvQ{3n4lVe;%)d6Z?Cx5J1g}KU-%0uP`O_2Q{cDK` zQBz0Cp(iFLJ@%Ih3J3}1l{g3%eduhG?~*$$CL=?X7OgO4OEyO%M?zEfj~p+4&jzbD zI9R}Za)U}R*>ey#ejnex^RybSR8#8lN-c-;(15`RWc!w3wpnm5Z5Hnd^4JO6&D7G3 zE<@{S=kh*aGU1GMY#`)&5zdP(ZkytE8ubem&3$MCWalJ6xO;q*)nOFX)~{MFZg^D$}0;H3tot178TXQd4Rs` z7{D5Ag4+}LqiKSLvTj*vTMQrhk6l50J;1Fd2en~xc zO#aUpeC?r4ml4jtBO@bIA0JF~nP8M2IcjH0f1GnC3%ua$%XR`c>7?Pjq-a0!^)r4> z`?(iY)zvFQ(|*A#e<`r%Va;PbG)mHw$)yBkET-l~3-?Ne_A`4d$bH`#w)d zDMZ{$!Ld5haiLU=?RWwv^DC}NFCM93P0eeBgsSIfSlp9~U0*=%7;?xE_ zx-wo<0Zt^y5EbYsw~pg9{`Am%f5(hU$n($Cb36sC_vluT^X3p!$#5RnH@3m?BUIVX z&Q2G_+RIA4Ne0WGf;F0&XOvL_oSMx?37aY3U9{ML|I64r%G` zQV?m7cxa@%yU$$4-tRf*_l@uS^E>1G)^O~>`##Tl)>`+v?|IL8%{i~DQFWD#WEyo( zQO_XJWMl|WuTYf@R1$#twry5K@d3oIzNHIU&+sAqzPaaRk)9J*Gb@5J?)aQ7 zp!oT%?)vI#hbJEYQ=2F;5O41Q8Z6{BB52K~58*q%GpHWwu{vhP-A}0^oWO8+788>= zDs>$AyEebw^#9h`DXYej*)?sO?h9YFOUlWS(Q?@>l9LaZT3N{k5;VBoCS>%P+uGWk z=F?IpLBRAT*3Lp?=}I$bV3A_q!+l)Ipjw@!g)|QGZTIP}udlOk%LN}US-8t zx3wO~&YqE(aN1r>0~Wz>3F=IhHP5RqN3=J2o^u;dL!$41Z0Ey}DAUS=eQY?jdG;|%h8_$eqt;=x5`)Su(Fh=?$tqvwj@;DGeH;`MbK9q0 zCvf|O5CYTJM8JXxdYBY}jfuDd*&Q?FRhjD8<$9-W1%XuC?eVT})q$}efKNadq{5Pv zbNlIFQD%(mwhCgOR%?$Ujs~p7_+m|M7*MG>FYy~Tr?I7Jna2{L;(H5Nt=o z+ZNFUwi`|`^u8c{2h628gK6(W$6?0>j?g)f5A5n6ZGU=@O)^PN^wFTz^_?FkDs*e` zHQWB?Fh_8BcouN;w1zQ11DIr)% zav(rs1N;clq7vQPTIhub#@+)31gI=ExGnE$@)Vhe#JYP_L^zU=;-QSAU`xqfaPN)k&!*pU z$s-2r(=f(S2(-1>S0W*B`+{s`930C0{QT@EudCtx62lAv*5|CQ6nvX!C+182rIIn4 zt&_u_(aEkDsbn;Mq`e&0BR%u_od8_j;^ce`jQADY_RFR=cO!w4unR;wgFk$T2m0w~ zZZ`!rj%;~P%q-(M@*7xKN*7LRGl)wSDKYu<62i=cAOP|mqVP65fM$3S@!@zQnZqUK zJK{YN3>+b^04&dJxIzlT`$nc4B3Zag_Iz@v)ORJWA;!jG?x^5 z~@n@4E9_%1zttiMo&FX8Fo_A){o* z;d^Wa6S(}SJ^*R-j>?t|%?|a0VWx@&02y8!>;ph#tAv@7jyMSsp!9-B!=(4X9wExN zzo|G`BS3qjq_A@h6*oX|Wzs~s<*M1PW0K!>?i*>d*!#eKVGOz$Y}}q!%^{2fmCnU5 zz!{(r*`Jp_hZsk0U|vxL#bUSJF|PoP{-Gh_H0Zl7D-!28`&vCAS%T{{&Ih-(7gE=G zGBC>Rjindul9HlADY-Z~XQ40~4Z9zs#mfHCiJ_ZY6==9*0Y8}%5Y7hxL7`z`c~L(1 zYg^@oBGT@ml(RSaPAQaww-_zTne-5A+t^|e$a4JU!JJSwj*e=9#qF|1Yba-jVj(Pm zhyv5nP-*@ovsSgzd|#m#w+)Vbv(kMlv%Zd+mna!q*bliaDK!u5Rys!di1ZDv;NGyZ zv+D)IYTE`<`&8JD*kXKOVv2}~i+2H;ULjEC0ivGO&5VAH3ov*o@m-t1{ttpvr_|^e z7!)9~|1Q@S`tBUv^zw2R$OoG2u8tY^=Ox`gZ;}!fbshM^lwb(EfVxeL6x0lqSS`p) z*=+*xO&m{`h+Xv&R0+Q(#vhva`NfNX21)8bP%=lPPST`f`K=~d<=+Ci%n>jp0m*Kl zm9@1v>Bgop^$xY<`2iKz)MAhi z?BiFiUQGtQYTMb4cqNxcLAaIMHw6T=fQC{`R8$s-_Mt(jJ`*R06jZfg14@JK6!;Jc zUbUu!hY}nVln5NDbgZoMFoEfIV=uujbPc54fTiyPFz0sX>5D%UUhF;_9v%(?S3$r- z19Elsgz~BEqN1Yv%jGNGK&U4fcBgihGxZ^Wb&23%HG*CYpeBV@(T5QXHoaBpSRVAZ zjzj2f+^Ctog_1FXtrz?@83+d6`JG1XV!(}oY6&qZhAsG~UYjs$8^KQiYBx6Si!z3} z7x+=Xj!2-&RC@aKsjsPO(;Gl%e;(|hE13Zral~^zkmmh$ERrGM?a#x`A12waqa1Pw zzy9(V)@7wYO?mtKqk)%-06YdD-y$K!`uXXPJfcpB)S=T5(l$ykex0(v&dPf8U;mWv z?`x5)Z*BDy7ivDKoWHfSv_OSG5@bk(gip=i{d4Q=lVE0G$4$^h*^>Roqy0N( zZ-FDczP=thZ4{vWjD6|f*=PD$Cl}dvU{zem;%Ig(v?^ng3}r&vXC!92bDBTYvw3AN)3OxBwQJXZYuVY}O}51qB5g zAsPin{+}!BKjz|}xr(Zx1|N`sn3&GO!Xk2~zT5WK0HgkUnf+@#{(rU1bY_oI{?r1X zP;7djKL>H-t&Lq_a@R!ghS^N}WB_wW&ECO1w zp8g4hJODkSWoIv%%>85_Jmu)@jBG@pmcYK^leX|{B48p$6TCRsJE%mVI12*qPrx{t z1479riA;ZH8=D@8tLc*s<{SD$5CRz7W)ppOYEDjvhhDJqNMRqgbDN*|pE;);H#DR! zDhEnJQ(!rt5!blo3jMN-_i*u1kP1YvLp$}PTYfSt8As}ASf*C@$?>O-gdk^kBEgvvdALc>RLxE1cce=Sf(D4Q&0;AS^u(D<+=*AU*LliENe4&^Pv~U>J zOL8V>;89(H03UHufGzfBe!u$n8A*;|g-@wG79+ffW@C5q{oq`k`U}W)_V)Gy5`o`o z*zI76m_<+I)u0Fvs{`j<5J;K}3k$=Rt3-?at`>IEpF814K_3WebZxF6FFG|JXgiTG zP&5D(Un!vo2MB_!+I`=?l?8GTAW=>MO2teNF9*`=UU1(CNJ%xOaDc+3sj2BZxJP0G zxaZGzB7U7-W59)C>*XOXcm^5JCy{^f-~~iOFv6+e{?dW`Q#JUKjitarjktoq#3{aS z-udwr@Tn((A|9PaLwA~daBOTgp{B!qkor*6(fN41=I{O#bZrRCfoh#}9@Hj)*bjiL zY{;+D=|5)%qkl^o?h{m}Ax3%f>ybPt0o4G%Lj)Q@;vxeKmKo?RWLNEfLgb-T7_K6(0dRn_vDYMG(E>2| zTXS8)2m}DI4XA1{i7G}^Re{W7JxD=Azwds7z;Qf083Gi6oEjSR)9HFZQXwCvR-l-n z$J(D=s_!|7FR;j`0sUnrf^`72rD%?@r~@i#o7Mn|!;_PfiSW>hCD9=KHd~8Dt{^7n zZO|T*>AX^@jBKme*jTvVTCh8GSfVn8924>%+tTpaZD=|gs;q@>MeXyd{VqaCpk z#~D(fwJ`zR4J&*5P|Br&LUDlZxH$g$SdmwG|{Jmc+O(GjmJK}0#COST)3^TBo-oaX_DTb)Ib}8QdHpN&^lZf zWF~S{s#7)o8RYk%g$Y1k4>-Q*#xxKW1B9z&YMKF#av|sq48u?cB=RhmBurKSR6$2W zBP`9omv~#fBo5A)4>yeIggCLujDv|u8aRkM!RZeI&x3$o=_$w_1tfx~l``ijs6BwK zx(hfR*e?mCbVV3xV_;{eOaoyH#mY^31jPi=qXNCg%ZLX8^lh?MR(Xg5jqA}Cntg$= zP<-ku`7dXz<}XNV$f~MFl)=-_uYq6#65lcHIRcxoYL51EgmJ%et5+?+TgZ`re>TBs zsSw>%FqkAx2&FY?;7Ny??TGDfEu>Vr9?WZ(;Xh62ZRHfO*dHq>2w99 zCF9YmN&qCqgt&qDwfN_h*#cc<&`iE>+!6O&wb(opt?4WY9!RDFKY&-j$c{)O8H3gb zV1iR)WAS5Oyi8z)&ci4MXICO3+XZe%PKb#I^1+bU*jWC(N&KTZY4<#%>XHg@!dgL# zIX5?#eWh8C(IQw0wKW3r0!S2A2-0>Sr~ruo#G-YZ!2Z_(?tXAolqtC9az&>7AA#R2 z9qt9vG@*>;5p91S6ZNluZ5#~m6}Cmi*;qK2gp{!G%%7zK-$MNA=HX!t*f!RIi-h~a z&nr0t*)AJ55S(+{r1mLc2188tU$}C=oi-Hezg@Zij+p*CV){QVV)_%k*C?u1IOIad ze=}c5Kmdg(l%1nt1GZ~q2V4CKa-5Nq81Eq4p9vyA;871&{Np4m&kdDY!^vy-p&35t5`T z1+cvkXxl;V_IDJ$_yPn7!EytxshT#!9D$!o2EP(SPfst7IDFr}-T7tr`h1(NGD=bd zNFEmf!d!1Tfg_uX@H-lg8^3N zwW#qpRfLDpee<7Z?hj7R=HcN_$PpKT_;Rqpl}v(S z*)u@QdbBBOhyX$aEH>{k1H?kVhaGAYVM_xY8Yai6;oj%-f8eDW%-f1BrVwg?lB4Vw zBagB_5R?Zl_<{%+q)%48T(*D!;rA%w%>T=9vgUSps{ts$202C`x>~}87*fW3p^&B-q2*wGnc*x-pBtf{ixrxKh zrI!CjmhPRUq^6#q?M&cyJ1l|8i$M7;EfOl3y#dycGfD#tTCt=R{-4Uj_2%HF`{dg_ zUX6NkdU}Z;6B4LI$HvB-zy(e(^cToOqrLgXc0idt9ReJfIS;@Kh5cNz)TC;>`nS*Z z>^$I>Xy8IG1hA8eib`Yc(C-N!!4>1QX9shH`B$L3FHK_eiMy-K@}+q2383mTQ%Le? zahrQK!QkL={xqS!UScROR3~(2Y4AX7DGu^mav+C5*JI$Sop-#VER75a>Rv!lNJuic zN4Ee04FF#`BP8?m6q-&(vo!*WXXCH8CN1lxL=zHuW z2lMnHFFcY7(pAdFl%@m{DZgfS4f*6G0UaZ~GzOBVE`z;=WO0XSXz#JsfWTEW5_I&f7sf95~aD|TRrnE}P z>4bzhzs44kP_>)A%8#~0xUE%9ln&z;A9t44W{gl1}*eQNfp625YNL>W)Bp3Mzi2}ATxtms~4aG-MPDAQvFnQufl zP}|L&xv6#bfAeUSo>eM*I?jM4e?)K3P4pWDy{R{Ibyk8{Pzq%$ZfE{nDn&I&3vb2j z_ln&Tg7m{hGTzjTq0`Z&+OmJAm!3-XwA#Q94pTj;`UVCrP95~t5ji>tq!BO~g;IzF zXZ?{h@7hbZ$H(}Q7f+>jtzWnh9HvxV)$#L${{`6fFwQj}<_00tM^G?nWo5?I`QoCTX_f> zAkiP^y}te~`n8$OiDGa-Kt?s+W*m}KO-fECCT%eIy{6_fDk^wgH(EKMbhvovcDBI? zWXN3&DvtvOc1z~A2xHS$tu@`(t|8Tw8ECBlN>|PWN#uXfw@a3PVt|gE!tW}d)SxvK zDY87DA|+C`Z*t&vj5eN_NY0GpP}nbpYBL!Uat`DqCV|Y7#nMjfnRH+O$mPlngGjwn zVt1Eng&uEm!iB6zF9mtJgM|}g2M2b=n>*A93;@|$6PxRie+DogNkDblnGJWy?@>}!bhupw_GUQ{=|o5R@um;)!2GE=osthAIZeVJnTFIaD)B*YYzjp1x>ayU!UL))izDOzn|tP%f;`|7>$8FeGRx^cE! z(${(9ru(2A6zN{-hA;6AV$H&rY<6Xa2&^@YNt-bawr4*atjMnT)f&&BA0eMZm(|n8 z>gsZx$P}sTU0(uekoGFu3){WWjZ)~+Se?CF;k|FUcG|bp@_kvswrs4_-%fL}(hwAv zKR_#0WX(x8yLVu%RAiZ(seMu#3~^el6-|mMKMw5)NQ42owYRdq7$`e6-PqJ3(@L&%Pg>ykJ-`<9u9YW|NkuuT(9BUO6S!QZSeb<@ms;)RRIq_A zk^NH0uzwJ+5#y-yiK4tHSmExg3YdvyniFctmB_%%Pp-`mGe2HB;_Jy)gGNXeF~nYP z4~bQ9&<|Ae&32SV@>WF5kJtMQlv+b|*(cIJQz+=YOhJg|;~h;*no=j>alHAqNQ;hX zT$AAF;Iw?vY7?W)2gB?I)kO+jUs=|$CTtEQJqw_nMITxU*rqB%gqXTpJ+ss0$k(v& zX0zZ_Xwtd0H>3jjYI^vbJxeV?IAth3oHeF;(|Y3KY>0rMG5jP1=5##t^0~}vTgBDy zx;SE8UD!ftMV;ots~6xMb+#?31%-czM`{fEh4b0gvebgL-fNB7U1bVO;m~rEB4kqU zsK+MkuKSYTy#@uda?ch_CP(RHZkm@r3@9)*vMGJl6sycAFC|q(aex=#m7*LzQT(n{ z9KA)7%9qQtIh8`Lu1^P}Sa|?(p5zVklFVw_5J0;8=?G^z`Jm#}1{0 zbY<(0yFI_XpExO}qt@_>__D$zLG`oSmbWUigm7U4I&MXdHF0` zeV&0$q;DUqKnGl)a@X{})tKB8kE3RDubDiQxk5GBr|yOgX};uln>N0Yn;yL@Z{*b< zL==%rbVRPSLuTJ+y5htS_=}s`*SoA7o!Gi&MsIh<=o%Ip%V%UQaL5GQUGNRfHmd zQRQUctAe{rZ+&U6-M=;vpD<8>+f$k^>fN7LDxuc=(MKrVuViame0%56WaV3Z(sP40 z2hD1ln5<`Q0tRWgN{2Zj0=G49Ku=5MNk4D>b>2**_hfsSC+pL`|HLl#ZS^9L3%9(z$D7`cw*(tdnKL%O){oE+ z*H=l#Wi8B;=wcB5($u7~Y_@Dhm36(u@&`)xTAsUm+#%&{hthZSyKeksd}NMoJ1zpT z&TV|WWG=fGOYK3`P&QfO8j#Je3_X4X$K>rRv0)G9Sls%EaeAew&^SeyZc+JffZIvu zF1Jjnv29f>SF9BQ-DS6B2Jz|kW?LMnNN|fO4@+)U8~gD?B-nUo?WkpMH+YBUa7ZPD zDIzxE*xWacWc4f0Zphk~wh=PujN-O;k8=M)!S#&$G@KoX=H}*J+o+(rYtn;BV@ODc zsbH{(9@Pcb-nuFAC|n->wVA`^NTyQ`MnAMQiFc{V$YOw}x8tB*+3}$A1J``|BT**) zL%r+uL&LOlQ<853H0Ng6EgnC#vSMK*+jY0h)h#Zdm|myCX4ypKCB7D>TRwZ1Xjbm0 z@|Au?>#gz#(E|yayW|&Lzbx`fPU)CoyT{_z>R z4bze_8mJ(s(m!OmzOlX?Utpnv%6cReQML#v;qm@LGi;Rf&T{D7VPN}KeP?Im;_(Y& zwpJ^w4atRG?YrC^v&SbEFMDzqbWs_P^KN`e?PjA7y+rQriA84Y<&$nw#C%Z6@}@~K zJ>Q`IvJ#V)sZva`Rio4j4LAOeiGbr#{OEz?8P|g~6gi>aD>CNv_LGS9E>l0vH}QiL zplCzb{JipShJD?Oy8KR|S$G^X-|MkaLvPAAM8>yFlujpB;}s)%${PvNsGPZJdrpqg z){ix{otN4#8wu{or!br()DC}Z8#oVI;K(uQRd=O1g=5t0v?p-{ubWmTH!6zXmKV)z zjLx)Jci4(B@+6lcYp-yWwcD>cMf2 zPeQU)DaqoVA>jo7wc2e)5>Y8B_Wo%p5+*_Qs=DBB=THNM=bDO^GFP6NS49fG3N6O_ z5Xxk=x2|uJ5|DEDqdc-wA3gF;45Cn&wc*I+!u0@2S5zlDdxraUx>_!SU-_v?2j8~a z{#$zE1+i%gTxP}dlZljero(lqJ0r=C+KS1N#IaL_Qc`>J@5}AB=uo$Bs}N!i5A(Gw zw>S20ZcaZuS)S3^bqqAov75ECR+t$tm|I}(>@-y^##H9uQ03drQL$X$F#S9sy+aKP zy=r&zi|L?2sJK#cyXv=2Z05O>R@DMx4`FVb#%G7KufjHGBIOs*8HvJP?56!Cevs{N z3StyqC|V-obRe=0&1x-r<5E7mfNt?~OgBRwT5yhKDpfs+u1&?o3!C0|_6n55UApvv zB5gK^YfR{B!oaRRmxP0Xgwd(o*QD!$C!?xB z(46`~oBw*_YCNn14|`$1v35nHuHHfWIX_R&gns4`H}@;PD8aA*-}YK7oO(va-sY*86(N8=$dz zozLnH+LAp}P10yI?~FoP$XryhG%Yc^(N`cD-?bkl3t=kTFkeW%Vr@TDDI5DCAmDSk z#l;)6w6qPSkHvRO^P_m(u6Qt|90#R+yJkxjkS?q2k%7Hk9JIOqwY~=mlCEs8H16FV zw+9xs_171of0{H33j0#3jhV}~R1=uIKq>_E@T4BScxO|{?RQqs+)>$7**wT#K=Y~> zt?o!Lf6UebN?Rjl)gos1M?dBQ8_us<#tT*&hxcmQ?5y6nlX?k-=~=tTzwx57@`FL< z0BZFpD4Qi(>yCxe{y8OagbM{4r|!D{sAU+mnLp-4feew}#=Zcp=Ep?A1gt@Ox1bO+ zvhg3sm4}B%dOlXUuV230-8_+&kZ7?vF1M(XV0uPPO^GtP$$m=iWaFr?urNslsIj<@ z^JS?ga7nvk!Lad@@pq4}7$~hxEq{EF^2-u^)3`k^M%lhS`z>bJ9W(`NYLA>9i-c*~ zUPga(Y8@zWEOB)jEZZUhGP7o(Dl&oB(1>Y8#qwgVi+XKsHD1Nb#8~N1Er67N)#Yoo z*8cW77T-oGDeLmx+s$KKGcX9-ViSzFh_C4kiI!dJCZ*BO+p>U-Ond4xw9%;PQ zJB}I++uGRiuuYb)KHMMd8yH}jIG}Z5U+256RVD(QHr8%OAI2VPHhld`t$buLSn<5B z?xiWZ&ZWeaaB<030;qq1zo%fK8`m>hU;d5oyS>ZE22FA+JkW3{)@p0d^h)B)9^4i7 zzfQr-{5(gVhG=g@t>tLR@SPA=%<|ET`q9?Kaq(>^UP)8RBpw;rt8SF&+TmNu!?wU|+k44{014gxcXte$6zJF;#;ON$oI+&9j4q}y3 zny zS8wSzlrzh=91M4#!W#}U3jAu==a4L={TFc0A68mN)*o3sE+>YKW2g9Ov_6SF9$tjN z>J1ZRMvRt0Gd_C#qbAnXO#5pao#j`jbo}CkB)ObwE7(|DpWGorOdNS7PnfTXa#xYj zFwV@N?Yxk&*ai9!h96I7pMV1BYqeaRpl!xxScp$YKm84kgtMQQu7yRSR3)a%9VZ6x zO*{#FX7Y8`4snk9;jP+zFAhqVxsaD04x_OkeYn3CsPB&!RDQ)k$W zy%Q4f^_xN_`Uy1+YCpA7qcx)ryG%8&-F*p;oQ{P>U3P^)HlAZ_qd;4eRh>>I_tx%| zzkatx-x*e5I^|<qE;uCFi4+YzcnuE0<_rwh7kbhrV~2d2OGY-QfS^Fok?=Jl$fo z*ZF20QJQRC!1*U7|(N*+3-$93mg` zA>r$Ho$JA#SdRj1UZujR9kdkGP`i-_X@4ydt57HXj=$h-)(sd9*>c-B zzPqSFmTyN)VHGMH|Jzdlzn*M;W?k?)PeJI#+NGzE&Mxd~WQ{1h{d<``qN1jL%yX9Z z-p>9;6SZjp?#T!rqWCwEHc3nHXNzjFVWaa-wmrRTQ+LNC#Y!0FaGc?c*9KqP zOu`6FO~b3VvHn%MgFVfJ;1NDp5GrKwEk$i87v>#DGbLP)D=Mn@8oPN4bkl>at_4>2?eFl{~=z%m=$DBt`xy^2&Z5I_#_E?^=7D-z1LY@u3&k3P8_|Wx3Y%4$oVW_ z#XS-z#&JQ;Va)fq9WhoWDW*lCm{pD7BH-d7TwoO|qO7TuA(yC{N z8)kRBp0Uw2=^c0;?6Bl$F%hFTQ;?6k)4#6TV_GsFBI0}gzK;2Lq_j2TxV4m=%TcNr@$edW_(kA)XB0OkGL^M{tS9;4 zkZ!WTl^6TU&VO^lZS=(2UV{bNS&}TE*j&;T=foAW(D^~oy`}L=^Y@vfVGjIrtCnMX zF&cS?*BKX-k(I#uB&DS+wjW6G@R_#f@5``NZ|+QdiMMz*c36tDWbTS4Y>TW1M`3R3 zsk8B(b60^KmFB_@L%bJW#L-^9S^{gzUf9O+@a^o81Tty9=AP8l&7FIBhK5ULemaYf z9<4_N2I9Bf&LYBj%{yn&+e-1U+;3PSCiY-Zx?-od2IuDa_LtE*`vsz=BdavkOj`c= z%_rE25Z$&8VjSo{bze3ms+NlVx-RIx+BHO_kp02&OUztXRhvQ`_MS5h#=+M3e9tYX z?fJktV)?5rz}}dI-F33GJa#^jz!0;to2N@P6#UD+B1eotc8ec}rTLq40v#EgrG+-N z>T+^&w;;AITH-bs?1lY>l`!jbwVXb$Pv|$GnNe;AE69 zbn@kyzU;JngO8_}YJY!gQ&*YdW?#h<#z>BgDQLFFahmZ|cdr8Trr#pG0zN%AgtmHR zh6g)~(~IF0@p>0=N51iCmbizGA`Y>l2HDi(Gckd(<92?fr!Np52KFF8d)?T5n_dCJMIWso7UV zc3uY8T}yz@KgMC&@tDiXZ|OjKwJENkHCTxuraj=S_1Y>!8%LMn`p#JXA<5OoeGRPd zgD!l>U8f_7k9V?2QFo1czh`W35#MdI{D3bpiYFe^1p#U}%Nn++si}kOw}VA)hlt_$ znZ4COOZ75`=Y>E{#~kYax>9s=Yk{|GhKl{z`prYbkGl)2*Q$1>v0(SEIxsj5rgv_M z(RC!rrTMS!UAKV1fXBId_mg?S4?Ax2F0=*|&Oee1e zSzz4eyG`yXbQA0g?3@A1-u!I1TXf=?-SvsxP8rta-AYLWe`#!O^@UQlA9Hg?OA0@iRK|SA zbJs_L8u4;dbe!DW`rIcI1)`8|?Z`F36LM!hU%h_J$;l~K?DjCZEzGC3%`XXi`a@I{ zPECR61f+LUlt+)>?b`4XV9@E_<^D@1iCD{($I}Uy*cwbu2;<>Rc?R&N-J+{sKzcu#k$=N48};;&93kwYk{ibt%>_B}2;*UhsUc#W$vErX=-O-s(64 z?NM6pEzhEBdu#lyuXcX4RfBKPDQC9ru+GS5JrMld9+}X!RaH0>xZueluxZLVjr+Fko>;cXmdYbdJ93fAizs|-@%k@7wXtwTAK zsj~7w!p=$>`~Lx)@?oWyGS?`ND5^@!_Ytr9%4{oOwI2o2Uwyf@tnBRUyy%r+3AD}9 znqIP@p;+4YDin^>92XcADN~7Mj`+rT$Kr)P0v=~)JG0Tx_1I%`&^?4Z^?3HlJcqG& zd#eir2t9upEib}Y9k1bFjkdS4i{EK=E_7@4#+e~AALZ(b$nf)%nrqkXDr;F0cAE|i zv}ej5g)-DGw{1X>LRFSl^i>mn`+1Zk#om`S`IA_p#igXC4@+553fYb`_fKC6WwNAV zIqd;1Se}{l`fjd7!(o%+o&Me_gTFh5Xr}-#%;PPdiPd;_pm%1l*i?f2)xH)4{s#4p zjO~}Ho}Uo%ZZx~EfYqj5+@ys@yIb-_QH^sOn4Zr;=Q^42YyS70DfBhoI40<1HUyB3 zXmRnJao@|LTGF|WDdv3dq?6#Djcs@bK+F2L*y(KZ)uT3RrbL20rD65<{D+}Kl4=6@ zb`jurd57K#pW==!Tu2%om8bDDQ{LP%u{RCWP{R`TGR-b^jjmbb zbFz8*CB%0@L`o`IqTi~(e9-T?rx&yR+<+-|;$Hk_Yv;=+DN+$V{ItxS zA30Ad$wPQkQX@z436;?dgJoq`%XQr~$QBywj0EJ;IyeDWLGp%w%--dzEr>V9q>$R19civPyHW%led_*g;6^Gk$jVerR`1|PaoPp(A7MpQVpP(?^QxKYZb<4yk!Isa*kim4M#OzDbyQQw>c;TzBIu4s?Xx8`n%GfCG(sHe^ zpE5xwVO7RBM(9Whrr%Ay^c!K4qwF7;loT+p|1RX9QF=LiE(+q^kt8vUlhH@iJh1vL zEp4H6<<*h1-HhZ5M@oPzrN{&>)P%~rHz`EDExX*Iqb%+z#hEX=U-j{SIT6yjlw3w&p%HXfbIRo~I*&AtxvlH%rVYwE?IM)tos z(9wr0JB;bhh3*yu8zpSxg>mc5%5D|H#X`a5rNfSoo=No$Om^ZmmcRLftjSSM^)RjJ zTV%BoUbF0<+&fU?Mb)&#RyEt)re*$cu%JW@PH13*zmAECcz%c(is+>OR^ZWpqF7O8 zW?gQ>9BdcZ=+B>Dn>&eps&fH=K$mdMFi{DS9M0mXTL7E{wpXHRgjX(3Kc2l5xCFS* zs#t7_h?Lmp;&IVuL|C8VLk_oO)O? z7iO2o?rY23^w{*xG;b%g+ODubr#TO7ZUKoiv&FP@-yn(Ot&%`N*WtFt{#S8;p@(s= z`AE!w($C(Mml3MKWpxZ^CZr`+yxaY_9OY*-WYce>ye=-XIUes3Y5{=JP!Y~bbX3dpGL}ai z)(jTj;>6SlU+J^s>-T*luF ze79pVkE@yFP0ymK+7IkNvZa3bizoQ3C|pntl2>~7`UNevle4pYrYc)SrFGQ@B`Jc;x4XZh zYzmHROY762y9dTS@nPjW4gRkIBcuM%Ma z$1B`h#H+Qlxh|ZWxQLJIUBAIvfZ*+mi59fV@gM8C$_eO&0P>C!uz$UsNep?+oEZm) zA~dXY%!_1SVP+curFnr_L~iQ$EO`z&c{y-RPYFN7>xvU)_1P$Avtp))cYN|X$#

FtN0B6QePWI1fWfYH*_vtlr+U0iC*8K2GVi>-$k%kQ;IlDdS2Il5xTQIF?Pu9N(@n4$XEryToQ+NLdk3# zB+&6z?3%#;>^M#vISMPcVMM|hQ6>uS5ObUb#a#jGXe}v+ zgCL#bPm=GBbuPD(JKQ;n0n%p(9dR{Xws4iqMxqxqKA$uP%xoT~w(psa-!<;Z^nyf4 zy?D@MI6u=`SCnAikh)ZDXD4lQm|{Bm$IjHkTz4iGf08S6U%zb2a5MxGtmf-B;O{q1 z+J^A14B9S+u)7Y|)_%aUAYlwAL@a&vVQy5f+2Gii%JqcK=j+G84#!E)8IG+jlBTu7+;^r@`W=I!hfEOU1Cr#X z=g-%t6l4RYr@iz^f75xIdBAr5tMX{uw>*vf8oGC6JZ~ZI(Ud1??!xDW17P_ZLM>O% z;t`T?Cgux)wD@0HU?#mgbfMsL-fGNus~mbKXx?C}{iu^+^p^wvjc)~*H6)@}uZ_BH zIc6PvQY0?Rf@vdCa|&|J(A(cal4;n+aAebf;qyUG<#gv<=C5o`(yF_*82HZ;s0)N{ey!W z5SA|D?fJZECbS+NrMIYfXeD>KI=oLq<4l}E^gR@STE1qH9Izie><$(QVF+smk|?}; z{wXnq2Hw};_8x&<$DVG}>-qjFXeawozLJ?lk)){Tllt+!cd;ydUEGBM0+1H_0rM4Q!Sw5xP@4-qL|Y9xUw=*7|MSgWZf+3KoeRZxwhpm-%c_dN-? zBrZaKc7|4A(6aP)o1!}|@o0MjU+maHBfdkKy#RqTW#B1`iZA>cl*GxE&p_!Q@@|Hd(=S%WxYJ4> z*fBZD_jVy(Ul>&1HcvH&{;VmW`#E8*F7)REbsajBf(1{=_96*0CZRG%)wL59;C}y@ zQsGXeWV#s zv@Ng`v+4q%jdOFw?W+sdSx=1-0%~Xtvt+s2YP1m7`sNWOw)6JllbNA+fA!_iRvzHx*>O`)oV`uG&s}}8kKuZ-`L(?`2&OF4z&nv&TeNo( zzw5sC9iqNUYkH`v{dL0f&S>%mdb`zSYl;)n-`NP7jvOB-GNFLNW@UubqhCOB!nm7& z-jI?)?wQ@nP?OdmJLFwv(b=)^`^x`<^5}d+Y2bx{WU4oLii@b2RbHX|OU=R!;NIISRR3*L;OgKnai$_4a4F zVFb#&aiR6;%o&C0!yX57%H+p9RF^*RS}wO3B})=LoSgEx!lW4`n;<0nNr5(f`U*KY z-eGmy`&i`R0)f-;c|mo>&&~`pXUG}D+xu>T`iK_RRIkEa1iTJ3Y%wIN7PgE>~K@=dz z=QzxS$lS=iif729ejP`>Easm23n6=NkukZJMnC>aC%G(z^U(5~TPl8Rg^!Q_5bmj{ zsK~*=VbYlwzz0fk;It{UyYO>0o&7qZ?V0U`fwrKuq@w;%H>qHfLP-(8XqfQ)a@ID{ z(sydJVvEs{rfd2@<$>uD8cJwqx2N@5;ZN3OlHJ_Tu7rEwlJ(8E>6W7E{*|?hvB{QN zOZS?TGbG*yjPy#$K3aal&i-RwTbP-K=lashGRV|KbD#Byjn#TyC;!h637DeLk!Rb@ zgtzmon=$Ro`%xYsn_DmLFgCmRBDz+4eLtps2MG%ECrJ5hJc51P+hzT{>gt|BQ7j4;1vX{!AG1r*ze+#R3=~LB zF?`OPmg8qEb91A7`GS|&%(rQGOCqbktgadRYT%}Nv-Q5a|FkOEhIAr|KW}VT z4F&pOFeYyi*3013-zsBXTw1cUwM~N0Vfjto$}Yd};=&8foYegMG3;goGSD;q;vCNd zfb;$wjjX_NF$s;pbzi=C0{0X__wCg`1^}k4$Vi#f7Tx*t=N%lwtNn~_`*sljDO8}K z2uJ~CG{72KRBGDW{Sy-@l`mqZOur1I{ao_!;QXQ@A|)m}i#eh_y^RzB;#hcOdcauoUV3?9{8(O=0)ijNE#+DnTti0Akp_bCO6MIgNDp*$Kqj{c#BcF| zrq?8e`uEkjpqsh|y39EpR&M}j*Xf$JHb2!u<4Z(DZ)&x3tX_w|%gMLHq>!_L3I{nb zzQ}V!vdq%b(w@PJnuaDYC@3g_6KoBaaW)aM4*s`){XSqLp{5uf<=OF}BlHO0gMoWr zBrP0Fr+5wr2Xx=5gpaOLA~vBNdT_o2TTcD}8NW*y5DrhKo&ZS~CbXC557NW@|KI-! zZ37d)S7@aL}jZskSaKb|a578~n3Qp*l4q{Zh3XD1?DZw`p zKriD{D$bfR4zdvbmw&ffn7vVELX^J;3*3;Ab804r@H&1U0c!s_yw*UYD diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_gating.dot b/applications/dynaclr/docs/DAGs/visuals/witness_gating.dot deleted file mode 100644 index 1bda77ce0..000000000 --- a/applications/dynaclr/docs/DAGs/visuals/witness_gating.dot +++ /dev/null @@ -1,30 +0,0 @@ -digraph witness_gating { - rankdir=TB; - bgcolor="white"; - node [fontname="Helvetica", fontsize=11, style="filled", color="#4A4A4A"]; - edge [fontname="Helvetica", fontsize=10, color="#4A4A4A"]; - labelloc="t"; - label=<Witness gating — score band → pseudo-label
t = quantile(|w|, dead_zone)>; - fontname="Helvetica"; - fontsize=14; - - score [label="witness score w(z)", shape=box, style="rounded,filled", fillcolor="#EAF3E1"]; - - hi [label="w(z) > +t", shape=diamond, fillcolor="#FBEFD6"]; - lo [label="w(z) < −t", shape=diamond, fillcolor="#FBEFD6"]; - - control [label="control", shape=box, style="rounded,filled", fillcolor="#CFE8CF"]; - perturbed [label="perturbed", shape=box, style="rounded,filled", fillcolor="#F5D6D6"]; - unknown [label="unknown\n(|w| ≤ t → dropped,\nlike annotation != 'unknown')", shape=box, style="rounded,filled,dashed", fillcolor="#EDEDED"]; - - score -> hi; - hi -> control [label="yes"]; - hi -> lo [label="no"]; - lo -> perturbed [label="yes"]; - lo -> unknown [label="no"]; - - note [shape=note, fillcolor="#FFFDF0", fontsize=9, - label="dead_zone = 0.0 disables the band\n(plain sign: every cell labeled)"]; - unknown -> note [style=invis]; - { rank=same; unknown; note; } -} diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_gating.pdf b/applications/dynaclr/docs/DAGs/visuals/witness_gating.pdf deleted file mode 100644 index 77e246f2cb266cc1c02d2fb11d7d1494e26e3d81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39358 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%0Dm*X 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 PF$9FHwzj$LZrMKoE~*Tg diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_gating.png b/applications/dynaclr/docs/DAGs/visuals/witness_gating.png deleted file mode 100644 index 4d1b2749c6637ab110b2449ba4d0a9013b0a5c66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66884 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
)M+q!|trCtaO{sbgTq@S2`zr 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 LC — pipeline dependency (Nextflow eval)>; - fontname="Helvetica"; - fontsize=14; - - predict [label="predict\n(GPU)"]; - split [label="split-embeddings"]; - lc [label="run-linear-classifiers\nlabel_source: witness", fillcolor="#FBEFD6"]; - append [label="append-predictions"]; - plot [label="plot"]; - - predict -> split -> lc -> append -> plot; - - note [shape=note, fillcolor="#FFFDF0", fontsize=9, - label="witness mode has no annotations →\nLINEAR_CLASSIFIERS stages empty CSV set;\nresume cache keys on YAML, not CSV hashes"]; - lc -> note [style=invis]; - { rank=same; lc; note; } -} diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.pdf b/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.pdf deleted file mode 100644 index cb616e92004f3567f1f03df9357688bf56dc885e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35886 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 diff --git a/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.png b/applications/dynaclr/docs/DAGs/visuals/witness_pipeline.png deleted file mode 100644 index bda1ce4fc7f8c4b4c0459319f017c7fcbdb11b3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47573 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
-m>y@@;)FxA*kaao)^Ncd=}8dU)^qJEi&`0^e*% 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: experiment, marker, fov_name, id, track_id, t,
perturbation, hours_post_perturbation
one microscope / marker per config — no LOT, no pooling"] + + subgraph A["STAGE A · dynaclr witness-gmm-labels (NEW)"] + direction TB + A1["references from wells/filters:
X = control cells, Y = perturbed cells"] + A2["bandwidth = median_heuristic(X, Y)
viscy_utils.evaluation.mmd"] + A3["score every cell: w(z) = witness_function(z, X, Y, bw)
mmd"] + A4["per perturbed CONDITION: 2-component GMM on w[cond]
witness_gmm.fit_gmm_labels
remod = argmin(means); posterior ≥ gmm_pos_threshold → positive
negatives = ALL control-well cells; ambiguous → dropped
unimodal GMM (separated=False) → marker skipped"] + A5["map GMM ±1 → class_map vocabulary
(e.g. infected / uninfected)"] + A1 --> A2 --> A3 --> A4 --> A5 + end + + LBL["ANNOTATION FILE .csv|parquet
key: fov_name + id (or fov_name + t + track_id) + experiment
named state column, e.g. infection_state ∈ {infected, uninfected}
hand-annotation format — producer-agnostic"] + + subgraph B["STAGE B · dynaclr run-linear-classifiers (EXISTING, label_source: annotations)"] + direction TB + B1["_annotation_run_specs → load_annotation_anndata (join by key)"] + B2["train_linear_classifier → save joblib →
metrics_summary.csv → publish → PDF"] + B1 --> B2 + end + + OUT["output_dir/
metrics_summary.csv · {task}_summary.pdf
pipelines/{task}_{marker}.joblib
[publish_dir/vN + latest] → append-predictions"] + + Z --> A --> LBL --> B --> OUT + LBL -. "teacher/student: SEC61 labels,
train on a different modality's zarr" .-> B +``` + + + + + +## What's parallel vs sequential + +```mermaid +flowchart TD + E["experiments
(Stage A pools them per marker)"] + W["witness → per-condition GMM
(one pass per marker)"] + L["labels.parquet
(single annotation file)"] + R["run-linear-classifiers
(one LC per (task, marker), existing loop)"] + O["metrics_summary.csv + pipelines/"] + E --> W --> L --> R --> O +``` + + + +## Recipe / config + +**Stage A** — `labels_config.yml`: + +```yaml +witness_gmm_labels: + experiments: + - experiment: "2026_04_28_A549_SEC61B_DENV" + embeddings_zarr: ".../2-phenotyping/predictions/embeddings" + control_filter: {perturbation: uninfected} + perturbed_filter: {perturbation: [DENV], hours_post_perturbation: {ge: 18, lt: 24}} + marker_filters: [viral_sensor] # from viral_sensor → infection_state + label_column: infection_state + class_map: {positive: infected, negative: uninfected} + gmm_pos_threshold: 0.8 + bandwidth: null # median heuristic + max_reference_cells: 5000 + condition_column: perturbation + output_path: ".../infection_state_witness.csv" +``` + +For an organelle marker: `marker_filters: [SEC61B]`, +`label_column: organelle_remodeling_state`, +`class_map: {positive: remodel, negative: noremodel}`. + +**Stage B** — `train_config.yml` (the existing annotation path): + +```yaml +linear_classifiers: + label_source: annotations + embeddings_path: ".../embeddings" # same modality, or a phase zarr for SEC61→phase + annotations: + - experiment: "2026_04_28_A549_SEC61B_DENV" + path: ".../infection_state_witness.csv" + tasks: [{task: infection_state}] + use_scaling: true + split_train_data: 0.8 + split_groups_by: [experiment, fov_name, track_id] +``` + +Invoke: + +```sh +dynaclr witness-gmm-labels -c labels_config.yml +dynaclr run-linear-classifiers -c train_config.yml +``` + +## Related + +- Annotation training path: [evaluation.md](evaluation.md) diff --git a/applications/dynaclr/docs/DAGs/witness_score_classifiers.md b/applications/dynaclr/docs/DAGs/witness_score_classifiers.md deleted file mode 100644 index bdbf4805c..000000000 --- a/applications/dynaclr/docs/DAGs/witness_score_classifiers.md +++ /dev/null @@ -1,236 +0,0 @@ -# Witness-score linear classifiers DAG - -A second label source for the DynaCLR linear-classifier evaluation. Instead of -loading per-cell labels from annotation CSVs, it derives **weak labels from the -MMD witness score** using per-experiment control/perturbed wells — no -annotations required. Everything downstream (classifier training, publishing to -the LC registry, `append-predictions`, plots) is identical to the annotation -path in [evaluation.md](evaluation.md); only the label source changes. - -Use this when you want infection/perturbation classifiers but do **not** have -(or do not trust) hand-annotated CSVs — the witness score gives a principled, -distribution-level proxy for "how perturbed does this cell look" that is then -gated into discrete labels. - -## Visuals - -Rendered from the Graphviz sources in [`visuals/`](visuals/) (edit the `.dot` -files and re-run `dot -Tpng -Gdpi=150 .dot -o .png` + -`dot -Tpdf .dot -o .pdf` to regenerate; PDFs alongside for print). - -**Data flow (per marker)** — pool → build control/perturbed references → fit → -score → gate → train → outputs: - -![Witness data flow](visuals/witness_dataflow.png) - -**Gating** — how a continuous score becomes a discrete label: - -![Witness gating](visuals/witness_gating.png) - -**Pipeline dependency** — where the step sits in the Nextflow eval: - -![Witness pipeline](visuals/witness_pipeline.png) - -## Mock example outputs - -> ⚠️ **Illustrative synthetic data — not real results.** These mock plots show -> the *shape* of what the step produces so you know what to expect. Regenerate -> with `uv run --package dynaclr python visuals/mock_witness_plots.py`. - -**Witness score distribution & gating** — the two well-defined reference groups -separate along the witness axis; the dead-zone band (gray) is dropped as -ambiguous. This is the plot to sanity-check first: if the two humps overlap -heavily, the wells are not separable and the pseudo-labels will be noisy. - -![Mock witness score histogram](visuals/mock_witness_score_hist.png) - -The remaining three mirror the panels in `witness_state_summary.pdf` (same Wong -palette and layout as the annotation path): - -**Per-marker metrics**  ·  **ROC**  ·  **F1 over time** - -![Mock metrics bar](visuals/mock_metrics_bar.png) - -![Mock ROC curves](visuals/mock_roc_curves.png) - -![Mock F1 over time](visuals/mock_f1_over_time.png) - -## Why the witness score - -The empirical MMD witness function is the RKHS direction along which the control -distribution (X) and the perturbed distribution (Y) differ most. For a cell -embedding `z` with the Gaussian RBF kernel `k`: - -``` -w(z) = (1/n) Σ_i k(z, x_i) − (1/m) Σ_j k(z, y_j) -``` - -`w(z) > 0` → looks more like control; `w(z) < 0` → looks more like perturbed. -The magnitude is the per-cell contribution to MMD². It needs only two reference -groups (control vs perturbed wells), not per-cell labels. - -Implementation: `viscy_utils.evaluation.mmd.witness_function` (shared with the -MMD eval), wrapped for pooling/gating in -`dynaclr.evaluation.linear_classifiers.witness_labels`. - -## Step-by-step detail - -``` -embeddings/{experiment}.zarr (per-experiment AnnData; obs has experiment, marker, fov_name) - │ (produced by predict + split-embeddings — see inference_triplet.md / evaluation.md) - ▼ -dynaclr run-linear-classifiers -c linear_classifiers_witness_infectomics.yml - │ - │ for each marker (witness.marker_filters, or every unique obs["marker"]): - │ 1. POOL cells across all listed experiments for this marker - │ 2. BUILD references from wells (obs["fov_name"] path-prefix match): - │ X = control_wells cells, Y = perturbed_wells cells - │ (cells in neither well set are dropped — no leakage from dead wells) - │ 3. FIT witness bandwidth (median heuristic on pooled X,Y unless set) - │ 4. SCORE every cell: w(z) via witness_function - │ 5. GATE scores → pseudo-labels: - │ t = quantile(|w|, dead_zone) - │ w > +t → control (obs["witness_state"]) - │ w < −t → perturbed - │ |w| ≤ t → unknown (dropped, like annotation `!= "unknown"`) - │ 6. TRAIN logistic regression on the labeled subset - │ (same train_linear_classifier, same group-aware split) - │ 7. EVALUATE on the val split vs GROUND-TRUTH annotations - │ (witness.eval_against, e.g. infection_state), mapped via - │ eval_class_map {control: uninfected, perturbed: infected}. - │ NOT the witness label — that would be circular (see Gating rule). - │ Falls back to the witness label, flagged, if no annotation column. - ▼ -output_dir/ - metrics_summary.csv (one row per marker + eval_source column; - accuracy, F1, AUROC vs ground truth) - witness_state_summary.pdf (bar chart + ROC + F1-over-time per marker) - pipelines/{task}_{marker}.joblib (+ manifest.json) → append-predictions - [publish_dir/vN/ + latest] (if publish_dir set — central LC registry) -``` - -## Pipeline DAG (process dependency) - -``` -predict → split-embeddings → run-linear-classifiers (label_source: witness) - │ - ▼ - append-predictions → plot -``` - -Same shape as the annotation path — the witness label source is a drop-in swap -inside `run-linear-classifiers`. In the Nextflow eval -(`nextflow/workflows/evaluation.nf`) no module changes are needed: witness mode -has no `annotations`, so the `LINEAR_CLASSIFIERS` process stages an empty CSV -set and the recipe YAML drives everything. Resume-cache invalidation for witness -mode therefore keys on the YAML content, not on annotation-CSV hashes. - -## Gating rule - -Default is **sign with a symmetric dead-zone**: - -| Score band | Label | -| ------------------------------ | ----------- | -| `w(z) > +t` | control | -| `w(z) < −t` | perturbed | -| `|w(z)| ≤ t` | dropped | - -where `t = quantile(|w|, dead_zone)`. `dead_zone: 0.0` disables the band and -labels every cell by sign. The dead-zone is the honest analog of the annotation -path's `label != "unknown"` filter: cells too close to the decision boundary are -ambiguous and excluded from training rather than forced into a class. - -## Evaluation: avoid the circularity trap - -The witness label is a **deterministic function of the embedding** -(`sign(w(z))`, and `w` is smooth in `z`). If you train logistic regression on -`z` and then score it against that same witness label, it trivially recovers the -witness function and reports **~1.000 accuracy/AUROC** — a meaningless artifact, -not biology. On real 2D-MIP infectomics embeddings this reads `1.000` vs the -witness label but only `0.71` vs true `infection_state`. - -So witness mode **evaluates the trained classifier against ground-truth -annotations** on the val split (`witness.eval_against`, default -`infection_state`), mapping witness classes to the annotation vocabulary via -`eval_class_map`. `metrics_summary.csv` records `eval_source` — either the -annotation column name (honest) or `witness_label` (fallback when no annotation -column is present, flagged as circular). Representative real numbers, scored vs -`infection_state`: - -| marker | val accuracy | val AUROC | reading | -| ------------- | ------------ | --------- | ------------------------------------- | -| viral_sensor | 0.865 | 0.815 | strong — sensor reports infection | -| SEC61B | 0.764 | 0.838 | good — ER remodeling is a real signal | -| Phase3D | 0.580 | 0.536 | weak — label-free barely separates | -| G3BP1 | 0.491 | 0.554 | ~chance — witness axis ≠ infection here | - -This spread is the useful output: the weak-label proxy works where the marker -carries infection signal and not where it doesn't. - -## Config structure - -A ready-to-edit recipe lives at -[`configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml`](../../configs/evaluation/recipes/linear_classifiers_witness_infectomics.yml). -The load-bearing fields: - -```yaml -linear_classifiers: - label_source: witness # "annotations" (default) | "witness" - witness_labels: # per-experiment control/perturbed wells - - experiment: "2025_07_24_A549_G3BP1_ZIKV" - control_wells: ["C/1"] # matched against obs["fov_name"] by path prefix - perturbed_wells: ["C/2", "C/3"] - witness: - marker_filters: [G3BP1, SEC61B, Phase3D, viral_sensor] # null = all markers - label_column: witness_state # obs column + the "task" the classifier trains on - control_label: control - perturbed_label: perturbed - dead_zone: 0.1 # drop lowest-|score| 10% as unknown; 0.0 = label all - bandwidth: null # null = median heuristic on pooled (control, perturbed) - max_reference_cells: 5000 # subsample each reference group to bound kernel cost - eval_against: infection_state # ground-truth obs col to SCORE against (avoids - # circular ~1.0); null → score vs the witness label - eval_class_map: # witness class → annotation class for scoring - control: uninfected - perturbed: infected - use_scaling: true - split_train_data: 0.8 - split_groups_by: [experiment, fov_name, track_id] # track-level, leakage-free split -``` - -## What lives where - -| Data | Location | When written | -| --------------------------------- | ------------------------------------------- | ------------------------- | -| Per-experiment embeddings | `embeddings/{experiment}.zarr` | predict + split | -| Control/perturbed well spec | recipe YAML `witness_labels` | authored per benchmark | -| Witness pseudo-labels | in-memory `obs["witness_state"]` per run | `run-linear-classifiers` | -| Metrics + plots | `output_dir/metrics_summary.csv`, `*.pdf` | `run-linear-classifiers` | -| Trained pipelines | `output_dir/pipelines/` (+ optional registry) | `run-linear-classifiers` | - -## Notes - -- **Wells match `obs["fov_name"]` by path component**, not string prefix: `C/1` - matches `C/1/000000` but not `C/10/000000`. Give wells as `C/1`, `A/2`, etc. -- **Pooling is per marker across experiments.** The witness axis is fit once per - marker on the union of all listed experiments' control/perturbed cells, so the - learned "perturbation direction" is shared — this is what lets it generalize - across datasets without per-experiment annotations. -- **`center_per_experiment` is not applied here.** Unlike the batch-QC MMD mode, - the witness is fit on the raw embeddings so the control↔perturbed contrast is - preserved. If cross-experiment batch offset dominates the witness axis, apply a - LOT correction upstream (see [lot_correction.md](lot_correction.md)) before - this step. -- **Labels are weak; metrics are honest.** The witness labels are a weak proxy, - but reported val metrics are scored against ground-truth `eval_against` - (see *Evaluation* above), so `metrics_summary.csv` measures agreement with - biology — not the circular witness-label reproduction. Always check the - `eval_source` column: `witness_label` there means no annotation was available - and the number is self-referential (~1.0), not a real score. -- **The witness score is unsupervised in labels but supervised in wells** — the - quality of the pseudo-labels is only as good as the control/perturbed well - assignment. Mislabeling a well flips the sign for every cell in it. -- **Class balance.** Gating is often lopsided (e.g. G3BP1 real run: ~53k control - / 2.8k perturbed). `class_weight: balanced` (the default) compensates, but a - near-empty perturbed class makes the val metrics high-variance. -``` diff --git a/applications/dynaclr/docs/linear_classifiers/README.md b/applications/dynaclr/docs/linear_classifiers/README.md index fc46bedfd..db5b8fff2 100644 --- a/applications/dynaclr/docs/linear_classifiers/README.md +++ b/applications/dynaclr/docs/linear_classifiers/README.md @@ -191,6 +191,7 @@ Examples: `linear-classifier-cell_death_state-phase`, `linear-classifier-infecti See `annotations_and_linear_classifiers.md` for the full specification of the annotations schema and naming conventions. For an **annotation-free** label source — weak labels derived from the MMD -witness score using per-experiment control/perturbed wells — see the -[witness-score classifiers DAG](../DAGs/witness_score_classifiers.md) -(`label_source: witness`). +witness + a per-condition GMM, written as an annotation file and trained through +this same path — see the +[witness-GMM classifiers DAG](../DAGs/witness_gmm_classifiers.md) +(`dynaclr witness-gmm-labels` → `run-linear-classifiers`). diff --git a/applications/dynaclr/src/dynaclr/cli.py b/applications/dynaclr/src/dynaclr/cli.py index 30f28b322..98ef5748b 100644 --- a/applications/dynaclr/src/dynaclr/cli.py +++ b/applications/dynaclr/src/dynaclr/cli.py @@ -135,6 +135,14 @@ def dynaclr(): ) ) +dynaclr.add_command( + LazyCommand( + name="witness-gmm-labels", + import_path="dynaclr.evaluation.linear_classifiers.witness_gmm_labels.main", + short_help="Generate an annotation file from the MMD witness + GMM (Stage A)", + ) +) + dynaclr.add_command( LazyCommand( name="run-linear-classifiers", diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index 626fb83e5..d63207c8f 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -240,92 +240,93 @@ def _validate_refs(self) -> "WitnessLabelSource": return self -class WitnessSettings(BaseModel): - """Settings for MMD-witness weak labeling. +class WitnessGmmExperiment(WitnessLabelSource): + """One experiment for witness-GMM labeling: refs plus its embeddings zarr. + + Extends :class:`WitnessLabelSource` (which carries ``experiment`` and the + control/perturbed reference specs) with the path to the embeddings zarr the + witness is scored on. + + Parameters + ---------- + embeddings_zarr : str + Path to the embeddings zarr (AnnData) whose ``.obs`` carries + ``experiment``, ``marker``, ``fov_name``, ``id`` (or ``t``/``track_id``), + and the ``condition_column``. The witness references and the scored cells + both come from here. + """ + + embeddings_zarr: str + + +class WitnessGmmLabelsConfig(BaseModel): + """Stage-A config: generate an annotation file from the MMD-witness + GMM. + + For each marker, the witness scores every cell against per-experiment + control/perturbed references, a two-component GMM is fit on the perturbed + cells' scores per condition, and confident cells are written out as an + **annotation file** — a named biological-state column (``label_column``) with + the real class vocabulary (``class_map``), keyed by cell exactly like a hand + annotation. The Stage-B training path (``run-linear-classifiers`` with + ``label_source="annotations"``) then consumes it unchanged. + + The label's *meaning* is named by the modality it is computed from: a witness + over ``viral_sensor`` produces ``infection_state`` (infected/uninfected); over + an organelle marker it produces ``organelle_remodeling_state`` + (remodel/noremodel). One config = one microscope/marker (no pooling, no LOT). Parameters ---------- + experiments : list[WitnessGmmExperiment] + Per-experiment embeddings zarr + control/perturbed reference specs. label_column : str - Name of the pseudo-label obs column produced by gating (this is the - ``task`` the classifier trains on). Default: ``"witness_state"``. - control_label : str - Class name assigned to control-like cells (witness score above the - dead-zone). Default: ``"control"``. - perturbed_label : str - Class name assigned to perturbed-like cells (score below the - negative dead-zone). Default: ``"perturbed"``. - dead_zone : float - Fraction in [0, 1). Cells whose ``|witness score|`` falls at or below - the ``dead_zone`` quantile of all ``|witness score|`` are left unlabeled - ("unknown") and dropped from training — the analog of the annotation - path's ``!= "unknown"`` filter. 0.0 disables the dead-zone (plain sign - gating; every cell is labeled). Default: 0.1. + Name of the biological-state column written to the annotation file (e.g. + ``"infection_state"``). This is the ``task`` Stage B trains on. + class_map : dict[str, str] + Maps the GMM gate outcome to the class vocabulary: + ``{"positive": , "negative": }`` — e.g. + ``{"positive": "infected", "negative": "uninfected"}``. + output_path : str + Path to write the annotation file (``.csv`` or ``.parquet`` by extension). + marker_filters : list[str] or None + Markers to label (one annotation column per config; usually one). None = + all unique ``obs["marker"]``. Default: None. + condition_column : str + obs column whose distinct values define per-condition GMM fits and carry + the biological condition (e.g. ``"perturbation"``). Default: ``"perturbation"``. + gmm_pos_threshold : float + GMM remodeled-component posterior at/above which a perturbed cell is a + confident positive. Default: 0.8. bandwidth : float or None - Gaussian RBF bandwidth for the witness kernel. None = median heuristic - on the pooled (control, perturbed) reference. Default: None. + Gaussian RBF bandwidth for the witness kernel. None = median heuristic on + the pooled (control, perturbed) reference. Default: None. max_reference_cells : int or None - Subsample each reference group (control, perturbed) to at most this - many cells before fitting the witness (bounds kernel cost). None = - use all. Default: 5000. - marker_filters : list[str] or None - If set, fit/score one witness classifier per listed marker. None - (default) runs one per marker discovered in the data (all unique - obs["marker"] values), matching the annotation path's behavior. - eval_against : str or None - obs column of *ground-truth* labels to score the trained classifier - against, instead of the (self-referential) witness label. The witness - label is a deterministic function of the embedding, so evaluating the - classifier against it yields a trivial ~1.0 — meaningless as a measure - of biology. When ``eval_against`` names a column present on the cells - (e.g. ``"infection_state"``), the reported val metrics are recomputed - on the val split against that column via ``eval_class_map``, and the - summary marks ``eval_source="infection_state"``. When None, or when the - column is absent, metrics fall back to the witness label and the - summary marks ``eval_source="witness_label"`` (flagged as circular). - Default: ``"infection_state"``. - eval_class_map : dict[str, str] or None - Maps witness class names to ``eval_against`` class names for scoring, - e.g. ``{"control": "uninfected", "perturbed": "infected"}``. Required - when ``eval_against`` is set and the class vocabularies differ. Cells - whose ``eval_against`` value is missing/``unknown`` or not in the map - are dropped from the evaluation. Default: - ``{"control": "uninfected", "perturbed": "infected"}``. - eval_annotations : list[AnnotationSource] - Optional per-experiment annotation CSVs to join onto the cells before - scoring, supplying the ``eval_against`` column when the embeddings obs - does not already carry it. This is how a witness-labeled run (weak - labels, no annotation for *training*) is still scored against - *ground-truth* infection labels. Joined via the same - ``load_annotation_anndata`` (fov_name/id or fov_name/t/track_id) as the - annotation path. Empty = rely on an existing obs column. Default: ``[]``. - marker_eval : dict[str, dict] or None - Per-marker override of the eval target. The witness classifier measures - how much a *marker's* embedding changes between the references, so its - biological meaning is marker-dependent: viral_sensor → infection, - organelle markers (SEC61B/TOMM20/G3BP1) → remodeling. This maps a marker - to ``{"eval_against": , "eval_class_map": {...}}`` so, e.g., - viral_sensor is scored against ``infection_state`` while SEC61B is scored - against ``organelle_state`` in the SAME run. A marker absent from the map - falls back to the top-level ``eval_against`` / ``eval_class_map``. - Default: None (single target for all markers). + Subsample each reference group to at most this many cells before fitting + the witness (bounds kernel cost). None = use all. Default: 5000. + random_seed : int + Seed for reference subsampling and the GMM. Default: 42. """ - label_column: str = "witness_state" - control_label: str = "control" - perturbed_label: str = "perturbed" - dead_zone: float = 0.1 + experiments: list[WitnessGmmExperiment] + label_column: str + class_map: dict[str, str] + output_path: str + marker_filters: list[str] | None = None + condition_column: str = "perturbation" + gmm_pos_threshold: float = 0.8 bandwidth: float | None = None max_reference_cells: int | None = 5000 - marker_filters: list[str] | None = None - eval_against: str | None = "infection_state" - eval_class_map: dict[str, str] | None = {"control": "uninfected", "perturbed": "infected"} - eval_annotations: list[AnnotationSource] = [] - marker_eval: dict[str, dict] | None = None + random_seed: int = 42 @model_validator(mode="after") - def _validate(self) -> "WitnessSettings": - if not 0.0 <= self.dead_zone < 1.0: - raise ValueError(f"dead_zone must be in [0, 1), got {self.dead_zone}") + def _validate(self) -> "WitnessGmmLabelsConfig": + if not self.experiments: + raise ValueError("witness_gmm_labels requires non-empty experiments") + missing = {"positive", "negative"} - set(self.class_map) + if missing: + raise ValueError(f"class_map must define {sorted(missing)} (got keys {sorted(self.class_map)})") + if not 0.0 < self.gmm_pos_threshold <= 1.0: + raise ValueError(f"gmm_pos_threshold must be in (0, 1], got {self.gmm_pos_threshold}") return self @@ -388,28 +389,18 @@ class LinearClassifiersStepConfig(BaseModel): Parameters ---------- - label_source : {"annotations", "witness"} - Where per-cell labels come from. ``"annotations"`` (default) loads - labels from per-experiment annotation CSVs (``annotations`` + ``tasks``). - ``"witness"`` derives weak labels from the MMD witness score using - per-experiment control/perturbed wells (``witness_labels`` + ``witness``), - requiring no annotation CSVs. Everything downstream (classifier training, - publishing, append-predictions, plots) is identical for both. + label_source : {"annotations"} + Where per-cell labels come from. ``"annotations"`` loads labels from + per-experiment annotation files (``annotations`` + ``tasks``). Witness → + GMM pseudo-labels are produced upstream by the ``witness-gmm-labels`` + (Stage A) command as an annotation file and consumed here unchanged — + there is no separate witness label source. annotations : list[AnnotationSource] - Per-experiment annotation CSVs. Each entry maps an experiment name - (matching obs["experiment"] in embeddings.zarr) to a CSV path. - Required (with ``tasks``) when ``label_source="annotations"``. + Per-experiment annotation files (CSV or parquet). Each entry maps an + experiment name (matching obs["experiment"] in embeddings.zarr) to a + path. May be hand annotations or a Stage-A witness-GMM annotation file. tasks : list[TaskSpec] Tasks to evaluate. Each task can optionally filter by marker. - Required (with ``annotations``) when ``label_source="annotations"``. - witness_labels : list[WitnessLabelSource] - Per-experiment control/perturbed well specs. Required when - ``label_source="witness"``. One classifier is trained per marker - (all markers, or the markers named in the witness settings) on the - gated witness pseudo-labels pooled across these experiments. - witness : WitnessSettings - Witness kernel + gating settings. Only used when - ``label_source="witness"``. publish_dir : str or None Central LC registry root for this model (e.g., ``/hpc/projects/.../linear_classifiers/DynaCLR-2D-MIP-BagOfChannels/``). @@ -443,11 +434,9 @@ class LinearClassifiersStepConfig(BaseModel): cell-level stratified ``train_test_split``. Default: None. """ - label_source: Literal["annotations", "witness"] = "annotations" + label_source: Literal["annotations"] = "annotations" annotations: list[AnnotationSource] = [] tasks: list[TaskSpec] = [] - witness_labels: list[WitnessLabelSource] = [] - witness: WitnessSettings = WitnessSettings() publish_dir: str | None = None use_scaling: bool = True use_pca: bool = False @@ -461,12 +450,8 @@ class LinearClassifiersStepConfig(BaseModel): @model_validator(mode="after") def _validate_label_source(self) -> "LinearClassifiersStepConfig": - if self.label_source == "annotations": - if not self.annotations or not self.tasks: - raise ValueError("label_source='annotations' requires non-empty annotations and tasks") - else: # witness - if not self.witness_labels: - raise ValueError("label_source='witness' requires non-empty witness_labels") + if not self.annotations or not self.tasks: + raise ValueError("label_source='annotations' requires non-empty annotations and tasks") return self diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py index 83f9bbc00..8c909dc2a 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/orchestrated.py @@ -39,7 +39,7 @@ if TYPE_CHECKING: import anndata as ad - from dynaclr.evaluation.evaluate_config import LinearClassifiersStepConfig, WitnessSettings + from dynaclr.evaluation.evaluate_config import LinearClassifiersStepConfig def _annotation_run_specs( @@ -62,24 +62,6 @@ def _annotation_run_specs( return specs -def _witness_run_specs( - config: LinearClassifiersStepConfig, - adata: ad.AnnData, -) -> list[tuple[str, str | None]]: - """Expand (task, marker_filter) runs for the witness label source. - - The single synthetic task is ``witness.label_column``; markers come from - ``witness.marker_filters`` (or every unique ``obs["marker"]`` when None). - """ - task = config.witness.label_column - runs = ( - config.witness.marker_filters - if config.witness.marker_filters is not None - else sorted(adata.obs["marker"].unique().tolist()) - ) - return [(task, m) for m in runs] - - def _build_labeled_adata( config: LinearClassifiersStepConfig, adata: ad.AnnData, @@ -88,9 +70,10 @@ def _build_labeled_adata( ) -> ad.AnnData | None: """Return the marker-filtered, labeled AnnData for one run, or None if empty. - Annotation mode joins per-experiment CSVs and keeps rows with a valid - (non-``unknown``) label. Witness mode derives weak labels from the MMD - witness score using per-experiment control/perturbed wells. + Joins per-experiment annotation files (CSV or parquet) and keeps rows with a + valid (non-``unknown``) label. Witness→GMM pseudo-labels are just annotation + files produced upstream by the ``witness-gmm-labels`` command, so they flow + through this same path. """ import anndata as ad @@ -102,18 +85,7 @@ def _build_labeled_adata( if adata_task.n_obs == 0: return None - if config.label_source == "witness": - from dynaclr.evaluation.linear_classifiers.witness_labels import build_witness_labels - - combined = build_witness_labels( - adata_task, - config.witness_labels, - config.witness, - random_seed=config.random_seed, - ) - return combined if combined.n_obs > 0 else None - - # Annotation mode: join CSVs per experiment and collect valid-labeled subsets. + # Join annotation files per experiment and collect valid-labeled subsets. annotated_parts: list[ad.AnnData] = [] for ann_src in config.annotations: exp_mask = adata_task.obs["experiment"] == ann_src.experiment @@ -147,170 +119,6 @@ def _build_labeled_adata( return annotated_parts[0] if len(annotated_parts) == 1 else ad.concat(annotated_parts, join="outer") -def _join_eval_annotations(combined: ad.AnnData, annotations: list, col: str) -> ad.AnnData: - """Join per-experiment annotation CSVs onto ``combined`` to supply ``obs[col]``. - - For each ``AnnotationSource`` whose experiment is present, loads the CSV and - maps ``col`` onto the matching cells (via ``load_annotation_anndata``). Cells - with no annotation keep NaN. Used to score a witness-labeled run against - ground-truth infection labels the embeddings obs does not already carry. - - Parameters - ---------- - combined : ad.AnnData - The labeled AnnData (obs must carry ``experiment``). - annotations : list of AnnotationSource - Per-experiment CSV specs. - col : str - Task/column name to pull from each CSV into ``obs[col]``. - - Returns - ------- - ad.AnnData - ``combined`` with ``obs[col]`` populated where annotations matched. - """ - values = pd.Series(np.full(combined.n_obs, np.nan, dtype=object), index=combined.obs.index) - for src in annotations: - exp_mask = (combined.obs["experiment"] == src.experiment).to_numpy(dtype=bool) - if not exp_mask.any(): - continue - ann_path = Path(src.path) - if not ann_path.exists(): - raise FileNotFoundError(f"eval annotation CSV not found: {src.path}") - sub = combined[exp_mask].copy() - try: - sub = load_annotation_anndata(sub, str(ann_path), col) - except KeyError: - click.echo(f" eval_against {col!r} not in {ann_path.name} for {src.experiment!r}, skipping join.") - continue - values.loc[sub.obs.index] = sub.obs[col].to_numpy(dtype=object) - combined.obs[col] = values - return combined - - -def _resolve_witness_eval(witness: WitnessSettings, marker: str | None) -> WitnessSettings: - """Apply a per-marker eval override from ``witness.marker_eval``, if any. - - The witness axis means infection for viral_sensor but remodeling for - organelle markers, so a marker may be scored against a different obs column. - Returns a copy of ``witness`` with ``eval_against`` / ``eval_class_map`` - replaced by ``marker_eval[marker]`` when present; otherwise returns - ``witness`` unchanged. - """ - if not witness.marker_eval or marker not in witness.marker_eval: - return witness - override = witness.marker_eval[marker] - return witness.model_copy( - update={ - "eval_against": override.get("eval_against", witness.eval_against), - "eval_class_map": override.get("eval_class_map", witness.eval_class_map), - } - ) - - -def _evaluate_witness_against_annotations( - pipeline: Any, - combined: ad.AnnData, - idx_val: np.ndarray, - witness: WitnessSettings, -) -> dict[str, float] | None: - """Score a witness-trained pipeline against ground-truth annotations on the val cells. - - The witness label is a deterministic function of the embedding, so val - metrics computed against it are self-referential (~1.0). This recomputes - ``val_*`` metrics on the val split against ``witness.eval_against`` (e.g. - ``infection_state``), mapping the classifier's witness classes to the - annotation vocabulary via ``witness.eval_class_map``. - - Parameters - ---------- - pipeline : LinearClassifierPipeline - The trained pipeline (predicts witness class names). - combined : ad.AnnData - The labeled AnnData the classifier was trained on (obs may carry the - ground-truth column). - idx_val : np.ndarray - Row indices of the validation split (into ``combined``). - witness : WitnessSettings - Provides ``eval_against`` and ``eval_class_map``. - - Returns - ------- - tuple[dict[str, float], dict] or None - ``(metrics, val_outputs)`` where ``metrics`` are the ``val_*`` scores - (accuracy, weighted_f1, auroc, per-class f1) computed against the mapped - ground truth, and ``val_outputs`` holds the annotation-scored - ``y_val`` / ``y_val_proba`` / ``classes`` so the ROC + F1-over-time - plots match the metrics (not the self-referential witness label). - None when evaluation is not possible (no ``eval_against`` column, no - class map, or no val cell has a usable ground-truth label) — the caller - then keeps the witness-label metrics and plots. - """ - from sklearn.metrics import f1_score, roc_auc_score - - col = witness.eval_against - class_map = witness.eval_class_map - if col is None or class_map is None: - return None - - # If the ground-truth column is not already in obs, join it from the - # configured annotation CSVs (per experiment) — this is how a witness run - # trained on weak labels is scored against real infection annotations. - if col not in combined.obs.columns and witness.eval_annotations: - combined = _join_eval_annotations(combined, witness.eval_annotations, col) - if col not in combined.obs.columns: - return None - - truth_raw = combined.obs[col].to_numpy(dtype=object)[idx_val] - # Map witness classes → annotation vocabulary; keep only mapped truth values. - expected = {class_map.get(witness.control_label), class_map.get(witness.perturbed_label)} - keep = np.array([t in expected for t in truth_raw], dtype=bool) - if keep.sum() == 0: - return None - - X_full = combined.X if isinstance(combined.X, np.ndarray) else combined.X.toarray() - X_val = X_full[idx_val][keep] - y_true = truth_raw[keep] - # hours-post-perturbation for the kept val subset (F1-over-time plot), aligned - # to y_true so lengths match. - val_hours = None - if "hours_post_perturbation" in combined.obs.columns: - val_hours = combined.obs["hours_post_perturbation"].to_numpy()[idx_val][keep] - # Pipeline predicts witness class names; translate to annotation vocabulary. - y_pred_witness = pipeline.predict(X_val) - y_pred = np.array([class_map.get(p, p) for p in y_pred_witness], dtype=object) - - classes = sorted(expected) - metrics: dict[str, float] = { - "val_accuracy": float((y_pred == y_true).mean()), - "val_weighted_f1": float(f1_score(y_true, y_pred, average="weighted", labels=classes, zero_division=0)), - } - per_class = f1_score(y_true, y_pred, average=None, labels=classes, zero_division=0) - for cls, f1 in zip(classes, per_class): - metrics[f"val_{cls}_f1"] = float(f1) - - # Probability of the annotation-positive class, ordered to match `classes`, - # so the ROC/F1 plots are drawn against the ground truth (not the witness - # label). Column order of y_val_proba follows sorted(classes). - pos_witness = witness.perturbed_label - pos_truth = class_map.get(pos_witness) - pipe_classes = list(pipeline.classifier.classes_) - val_outputs: dict[str, Any] = {"y_val": None, "y_val_proba": None, "classes": classes, "val_hours": val_hours} - if hasattr(pipeline, "predict_proba") and pos_witness in pipe_classes: - pos_idx = pipe_classes.index(pos_witness) - proba_pos = pipeline.predict_proba(X_val)[:, pos_idx] - y_bin = (y_true == pos_truth).astype(int) - if len(np.unique(y_bin)) == 2: - metrics["val_auroc"] = float(roc_auc_score(y_bin, proba_pos)) - # Two-column proba aligned to `classes` (neg, pos) for the plotters. - neg_col = 1.0 - proba_pos - proba_2col = np.column_stack([neg_col, proba_pos]) - if classes[1] != pos_truth: # sorted order put pos first — swap columns - proba_2col = proba_2col[:, ::-1] - val_outputs = {"y_val": y_true, "y_val_proba": proba_2col, "classes": classes, "val_hours": val_hours} - return metrics, val_outputs - - def run_linear_classifiers( embeddings_path: Path, config: LinearClassifiersStepConfig, @@ -369,12 +177,8 @@ def run_linear_classifiers( trained_pipelines: list[tuple[str, str, Any]] = [] # Build the list of (task, marker_filter) runs and, per run, resolve the - # labeled AnnData. Annotation and witness modes differ only here — the - # training/publish/plot path below is shared. - if config.label_source == "witness": - run_specs = _witness_run_specs(config, adata) - else: - run_specs = _annotation_run_specs(config, adata) + # labeled AnnData from the annotation files. + run_specs = _annotation_run_specs(config, adata) for task in {t for t, _ in run_specs}: val_outputs_by_task[task] = [] @@ -443,9 +247,8 @@ def run_linear_classifiers( # Replay the same split to recover the val indices. Must mirror # train_linear_classifier exactly — same seed, same splitter - # (Group-aware when groups is set, cell-level otherwise). Used both for - # val_hours (F1-over-time plot) and, in witness mode, for scoring the - # classifier against ground-truth annotations on the val cells. + # (Group-aware when groups is set, cell-level otherwise). Used for + # val_hours (the F1-over-time plot). y_full = combined.obs[task].to_numpy(dtype=object) idx_val: np.ndarray | None = None val_hours: np.ndarray | None = None @@ -470,33 +273,13 @@ def run_linear_classifiers( if "hours_post_perturbation" in combined.obs.columns: val_hours = combined.obs["hours_post_perturbation"].to_numpy()[idx_val] except ValueError: - click.echo(" Could not replay split for val evaluation; falling back to witness metrics.") - - # Witness mode: the witness label is a deterministic function of the - # embedding, so val metrics against it are trivially ~1.0. Re-score the - # trained pipeline against ground-truth annotations on the val cells. - eval_source = "annotation" if config.label_source == "annotations" else "witness_label" - if config.label_source == "witness" and idx_val is not None: - # Resolve the per-marker eval target: the witness axis means infection - # for viral_sensor but remodeling for organelle markers, so a marker - # may score against a different obs column (e.g. organelle_state). - witness_eff = _resolve_witness_eval(config.witness, marker_filter) - anno = _evaluate_witness_against_annotations(pipeline, combined, idx_val, witness_eff) - if anno is not None: - # Swap in the annotation-scored metrics AND plotting arrays so the - # ROC / F1-over-time pages match the CSV (not the circular ~1.0 - # witness-label score). val_hours comes back aligned to the kept - # (has-ground-truth) subset. - metrics, anno_val_outputs = anno - eval_source = witness_eff.eval_against - val_hours = anno_val_outputs.pop("val_hours", val_hours) - val_outputs = anno_val_outputs + click.echo(" Could not replay split for val_hours; F1-over-time plot skipped.") row = { "task": task, "marker_filter": marker_filter, "n_samples": combined.n_obs, - "eval_source": eval_source, + "eval_source": "annotation", **metrics, } all_metrics.append(row) @@ -504,7 +287,6 @@ def run_linear_classifiers( { "marker_filter": marker_filter, "val_hours": val_hours, - "gating": combined.uns.get("witness_gating"), **val_outputs, } ) @@ -655,10 +437,6 @@ def _save_task_plots( pdf_path = output_dir / f"{task}_summary.pdf" with PdfPages(pdf_path) as pdf: - # Witness mode: lead with the gating diagnostic (how labels were chosen). - for vo in task_val_outputs: - if vo.get("gating") is not None: - _plot_witness_gating(pdf, task, vo["marker_filter"], vo["gating"]) _plot_metrics_bar(pdf, task, task_df) for vo in task_val_outputs: if vo["y_val"] is None or vo["y_val_proba"] is None: @@ -672,61 +450,6 @@ def _save_task_plots( click.echo(f"Plots written to {pdf_path}") -def _plot_witness_gating(pdf: PdfPages, task: str, marker_filter: str | None, gating: dict[str, Any]) -> None: - """Witness-score histogram showing how pseudo-labels were gated. - - Shows the pre-gating score distribution split by well-of-origin - (control-well vs perturbed-well vs other), the dead-zone band that is - dropped, the sign cut at 0, and the resulting labeled/dropped counts — - the "how were the labels chosen" diagnostic for one (task, marker). - - Parameters - ---------- - pdf : PdfPages - Open multipage PDF to append the figure to. - task : str - Task name (the witness label column). - marker_filter : str or None - Marker for this classifier. - gating : dict - The ``uns["witness_gating"]`` payload from ``build_witness_labels``: - ``scores_all``, ``ref_all``, ``threshold``, ``dead_zone``, counts, and - class names. - """ - scores = np.asarray(gating["scores_all"], dtype=float) - ref = np.asarray(gating["ref_all"], dtype=object) - t = float(gating["threshold"]) - ctrl_label = gating["control_label"] - pert_label = gating["perturbed_label"] - - fig, ax = plt.subplots(figsize=(8, 4.5)) - lo, hi = np.percentile(scores, [0.5, 99.5]) if len(scores) else (-1, 1) - bins = np.linspace(lo, hi, 60) - palette = {"control_well": "#009E73", "perturbed_well": "#D55E00", "other": "#999999"} - for grp, color in palette.items(): - vals = scores[ref == grp] - if len(vals): - ax.hist(vals, bins=bins, color=color, alpha=0.6, label=f"{grp} (n={len(vals)})") - - if t > 0: - ax.axvspan(-t, t, color="gray", alpha=0.25, label=f"dead-zone |w|≤{t:.3g} → dropped") - ax.axvline(0.0, color="k", linewidth=0.8, linestyle="--") - - marker_txt = marker_filter if marker_filter else "all markers" - ax.set_title( - f"Witness gating — {task} ({marker_txt})\n" - f"labeled {gating['n_labeled']}/{gating['n_total']} " - f"(dropped {gating['n_dropped']}); w>0 → {ctrl_label}, w<0 → {pert_label}", - fontsize=10, - ) - ax.set_xlabel("witness score w(z) = mean k(z, control) − mean k(z, perturbed)") - ax.set_ylabel("cell count") - ax.legend(fontsize=8) - fig.tight_layout() - pdf.savefig(fig, bbox_inches="tight") - plt.close(fig) - - def _plot_metrics_bar(pdf: PdfPages, task: str, task_df: pd.DataFrame) -> None: """Bar chart of AUROC, accuracy, and weighted F1 per marker for one task.""" metric_cols = ["val_auroc", "val_accuracy", "val_weighted_f1"] diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py new file mode 100644 index 000000000..4cd1c2c95 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py @@ -0,0 +1,337 @@ +"""Stage A: turn MMD-witness + GMM into an annotation file. + +A witness→GMM label *is an annotation*: its meaning is named by the modality it +is computed from (a witness over ``viral_sensor`` produces ``infection_state``; +over an organelle marker, ``organelle_remodeling_state``). This module scores +cells with the MMD witness, gates the perturbed cells per condition with a +two-component GMM, and writes a **named biological-state column** with the real +class vocabulary — a file indistinguishable from a hand annotation. + +The downstream classifier is then trained by the unchanged annotation path +(``run-linear-classifiers`` with ``label_source="annotations"``), which joins the +file onto an embeddings zarr by cell key. Same-modality trains on the labeled +zarr; teacher/student points the training at a different modality's zarr. + +Pipeline per marker: + +1. Build a control reference (X) and a perturbed reference (Y) from + per-experiment control/perturbed wells or filters. +2. Fit the empirical MMD witness on (X, Y) and score every cell. +3. Per perturbed condition, fit a 2-component GMM on the scores; cells above the + posterior threshold are confident positives. Negatives = all control-well + cells (well identity). Near-noise markers (unimodal GMM) are skipped. +4. Map positive/negative to the config's class vocabulary and write the + annotation file (CSV or parquet by extension). +""" + +from __future__ import annotations + +import logging +from pathlib import Path +from typing import TYPE_CHECKING + +import anndata as ad +import click +import numpy as np +import pandas as pd + +from viscy_utils.cli_utils import load_config +from viscy_utils.evaluation.mmd import median_heuristic, witness_function +from viscy_utils.evaluation.witness_gmm import fit_gmm_labels + +if TYPE_CHECKING: + from dynaclr.evaluation.evaluate_config import WitnessGmmExperiment, WitnessGmmLabelsConfig + +_logger = logging.getLogger(__name__) + + +def _well_prefix_mask(fov_name: pd.Series, wells: list[str]) -> np.ndarray: + """Boolean mask of fov_name entries whose path prefix matches any well id. + + ``fov_name`` values look like ``"C/1/000000"``; a well id ``"C/1"`` matches + any fov whose leading path components equal it. Matching on the ``/``-joined + prefix (rather than ``startswith``) avoids ``"C/1"`` spuriously matching + ``"C/10/..."``. + + Parameters + ---------- + fov_name : pd.Series + obs["fov_name"] values, e.g. ``"C/1/000000"``. + wells : list[str] + Well ids, e.g. ``["C/1", "C/2"]``. + + Returns + ------- + np.ndarray + Boolean mask, shape (len(fov_name),). + """ + stripped = fov_name.astype(object).str.strip("/") + well_set = {w.strip("/") for w in wells} + n_parts = {w.count("/") + 1 for w in well_set} + + def _matches(fov: str) -> bool: + parts = fov.split("/") + return any("/".join(parts[:k]) in well_set for k in n_parts) + + return stripped.map(_matches).to_numpy(dtype=bool) + + +_RANGE_OPS = { + "lt": lambda s, v: s < v, + "le": lambda s, v: s <= v, + "gt": lambda s, v: s > v, + "ge": lambda s, v: s >= v, +} + + +def obs_filter_mask(obs: pd.DataFrame, filter_dict: dict) -> np.ndarray: + """Boolean mask of rows matching an obs filter (AND across keys). + + Each ``col -> spec`` entry contributes a condition; a row is kept only if it + matches every entry. Spec forms: + + - scalar → equality (``obs[col] == spec``); + - list/tuple → membership (``obs[col].isin(spec)``); + - range dict → any of ``{lt, le, gt, ge}`` combined (one bound = half-line, + two = window), e.g. ``{"ge": 24, "le": 36}`` → ``24 <= col <= 36``. + + A ``well`` or ``fov_name`` key routes to :func:`_well_prefix_mask` so wells + are just another filterable column. + + Parameters + ---------- + obs : pd.DataFrame + The AnnData ``obs`` table. + filter_dict : dict + Mapping of obs column name to a scalar / list / range-dict spec. + + Returns + ------- + np.ndarray + Boolean mask, shape (len(obs),). + """ + mask = np.ones(len(obs), dtype=bool) + for col, spec in filter_dict.items(): + if col in ("well", "fov_name"): + wells = spec if isinstance(spec, (list, tuple)) else [spec] + mask &= _well_prefix_mask(obs["fov_name"], list(wells)) + continue + if col not in obs.columns: + raise KeyError(f"obs_filter column '{col}' not found. Available: {list(obs.columns)}") + series = obs[col] + if isinstance(spec, dict): + unknown = set(spec) - set(_RANGE_OPS) + if unknown: + raise ValueError( + f"range filter for '{col}' has unknown ops {sorted(unknown)}; use {sorted(_RANGE_OPS)}" + ) + for op, val in spec.items(): + mask &= _RANGE_OPS[op](series, val).to_numpy(dtype=bool) + elif isinstance(spec, (list, tuple)): + mask &= series.isin(list(spec)).to_numpy(dtype=bool) + else: + mask &= (series == spec).to_numpy(dtype=bool) + return mask + + +def _subsample(X: np.ndarray, max_n: int | None, rng: np.random.Generator) -> np.ndarray: + """Randomly subsample rows of ``X`` to at most ``max_n`` (no-op if None/small).""" + if max_n is None or len(X) <= max_n: + return X + idx = rng.choice(len(X), max_n, replace=False) + return X[idx] + + +_KEY_PRIMARY = ["experiment", "fov_name", "id"] +_KEY_FALLBACK = ["experiment", "fov_name", "t", "track_id"] + + +def _annotation_key_columns(obs: pd.DataFrame) -> list[str]: + """Pick the annotation join key present in ``obs`` (``id`` primary, else t+track_id).""" + if all(c in obs.columns for c in _KEY_PRIMARY): + return _KEY_PRIMARY + if all(c in obs.columns for c in _KEY_FALLBACK): + return _KEY_FALLBACK + raise KeyError( + "embeddings obs lacks a usable annotation key: need (experiment, fov_name, id) " + f"or (experiment, fov_name, t, track_id). Available: {list(obs.columns)}" + ) + + +def build_marker_annotation( + adata: ad.AnnData, + experiments: list[WitnessGmmExperiment], + config: WitnessGmmLabelsConfig, +) -> pd.DataFrame | None: + """Label one marker's cells via witness → per-condition GMM. + + Builds control/perturbed references from each experiment's wells/filters, + scores every cell with the MMD witness, fits a 2-component GMM on each + perturbed condition's scores, and assembles a per-cell annotation frame with + the config's ``label_column`` set to the mapped class vocabulary. Negatives + are all control-well cells (well identity); positives are perturbed cells + clearing ``gmm_pos_threshold``; ambiguous perturbed cells are dropped. + + Parameters + ---------- + adata : ad.AnnData + Embeddings for a single marker, pooled across experiments. ``obs`` must + carry ``experiment``, ``fov_name``, the annotation key, and + ``config.condition_column``. + experiments : list[WitnessGmmExperiment] + Per-experiment reference specs (control/perturbed wells or filters). + config : WitnessGmmLabelsConfig + Labeling settings (threshold, bandwidth, class map, condition column). + + Returns + ------- + pd.DataFrame or None + Annotation frame with the join-key columns plus ``t`` and the + ``label_column``. ``None`` when references are missing or the GMM is + unimodal for every condition (near-noise marker → skipped). + """ + obs = adata.obs + rng = np.random.default_rng(config.random_seed) + key_cols = _annotation_key_columns(obs) + + control_mask = np.zeros(len(obs), dtype=bool) + perturbed_mask = np.zeros(len(obs), dtype=bool) + for src in experiments: + exp_mask = (obs["experiment"] == src.experiment).to_numpy(dtype=bool) + if not exp_mask.any(): + continue + if src.control_wells is not None: + ctrl = _well_prefix_mask(obs["fov_name"], src.control_wells) + pert = _well_prefix_mask(obs["fov_name"], src.perturbed_wells) + else: + ctrl = obs_filter_mask(obs, src.control_filter) + pert = obs_filter_mask(obs, src.perturbed_filter) + control_mask |= exp_mask & ctrl + perturbed_mask |= exp_mask & pert + + X_all = adata.X if isinstance(adata.X, np.ndarray) else adata.X.toarray() + X_ctrl = X_all[control_mask] + Y_pert = X_all[perturbed_mask] + if len(X_ctrl) == 0 or len(Y_pert) == 0: + _logger.warning("No control/perturbed reference cells found; skipping marker.") + return None + + X_ref = _subsample(X_ctrl, config.max_reference_cells, rng) + Y_ref = _subsample(Y_pert, config.max_reference_cells, rng) + bandwidth = config.bandwidth if config.bandwidth is not None else median_heuristic(X_ref, Y_ref) + scores = witness_function(X_all, X_ref, Y_ref, bandwidth=bandwidth) + + pos_label = config.class_map["positive"] + neg_label = config.class_map["negative"] + + # Negatives: all control-well cells (well identity, no gate). + labels = np.full(len(obs), None, dtype=object) + labels[control_mask] = neg_label + + # Positives: per perturbed condition, GMM-gate the scores. + conditions = obs[config.condition_column].to_numpy() + any_separated = False + for cond in pd.unique(conditions[perturbed_mask]): + cond_mask = perturbed_mask & (conditions == cond) + if cond_mask.sum() < 5: + continue + res = fit_gmm_labels(scores[cond_mask], pos_threshold=config.gmm_pos_threshold, random_state=config.random_seed) + if not res.separated: + _logger.warning("GMM unimodal for condition %r; no positives labeled.", cond) + continue + any_separated = True + idx = np.flatnonzero(cond_mask)[res.hard_label == 1] + labels[idx] = pos_label + + if not any_separated: + return None + + keep = labels != None # noqa: E711 — object-array null test + frame = obs.loc[keep, key_cols].copy() + if "t" in obs.columns and "t" not in frame.columns: + frame["t"] = obs.loc[keep, "t"].to_numpy() + frame[config.label_column] = labels[keep] + # Normalize fov_name to match the annotation-loader convention. + frame["fov_name"] = frame["fov_name"].astype(object).str.strip("/") + return frame.reset_index(drop=True) + + +def generate_witness_gmm_annotation(config: WitnessGmmLabelsConfig) -> Path: + """Run Stage A end to end and write the annotation file. + + Loads each experiment's embeddings zarr, pools per marker, labels via + :func:`build_marker_annotation`, concatenates, and writes to + ``config.output_path`` (CSV or parquet by extension). + + Parameters + ---------- + config : WitnessGmmLabelsConfig + Stage-A configuration. + + Returns + ------- + Path + The written annotation-file path. + """ + parts: list[ad.AnnData] = [] + for exp in config.experiments: + _logger.info("Loading embeddings for %s: %s", exp.experiment, exp.embeddings_zarr) + a = ad.read_zarr(exp.embeddings_zarr) + a.obs_names_make_unique() + if "experiment" not in a.obs.columns: + a.obs["experiment"] = exp.experiment + parts.append(a) + adata = ad.concat(parts, join="outer") if len(parts) > 1 else parts[0] + adata.obs_names_make_unique() + + markers = config.marker_filters or list(pd.unique(adata.obs["marker"])) + frames: list[pd.DataFrame] = [] + for marker in markers: + sub = adata[adata.obs["marker"] == marker] + if sub.n_obs == 0: + _logger.warning("No cells for marker %r; skipping.", marker) + continue + frame = build_marker_annotation(sub.copy(), config.experiments, config) + if frame is None: + _logger.warning("Marker %r produced no labels (missing refs or unimodal GMM); skipping.", marker) + continue + counts = frame[config.label_column].value_counts().to_dict() + _logger.info("Marker %r: %d labeled cells %s", marker, len(frame), counts) + frames.append(frame) + + if not frames: + raise RuntimeError("No markers produced labels — check references, threshold, and condition_column.") + + out = pd.concat(frames, ignore_index=True) + output_path = Path(config.output_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + if output_path.suffix == ".parquet": + out.to_parquet(output_path, index=False) + else: + out.to_csv(output_path, index=False) + _logger.info("Wrote %d annotations (%s) to %s", len(out), config.label_column, output_path) + return output_path + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "-c", + "--config", + "config_path", + type=click.Path(exists=True, path_type=Path), + required=True, + help="Path to the witness-GMM labels YAML config (top-level 'witness_gmm_labels' key).", +) +def main(config_path: Path) -> None: + """Generate an annotation file from the MMD witness + per-condition GMM (Stage A).""" + from dynaclr.evaluation.evaluate_config import WitnessGmmLabelsConfig + + logging.basicConfig(level=logging.INFO, format="%(message)s") + raw = load_config(config_path) + config = WitnessGmmLabelsConfig(**raw["witness_gmm_labels"]) + out = generate_witness_gmm_annotation(config) + click.echo(f"Wrote witness-GMM annotation to {out}") + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py new file mode 100644 index 000000000..823d4d43d --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py @@ -0,0 +1,181 @@ +"""Tests for Stage A: MMD-witness + GMM annotation generation.""" + +import anndata as ad +import numpy as np +import pandas as pd + +from dynaclr.evaluation.evaluate_config import WitnessGmmExperiment, WitnessGmmLabelsConfig +from dynaclr.evaluation.linear_classifiers.witness_gmm_labels import ( + _well_prefix_mask, + build_marker_annotation, + generate_witness_gmm_annotation, + obs_filter_mask, +) +from viscy_utils.evaluation.annotation import load_annotation_anndata + + +def _make_separable_embeddings( + n_per_well: int = 80, + n_features: int = 16, + experiment: str = "exp_A", + marker: str = "viral_sensor", +) -> ad.AnnData: + """Embeddings: control well A/1, perturbed well B/2 as a *mixture*. + + Control cells cluster near +2 on feature 0. The perturbed well is a mix: half + the cells are remodeled (near -2), half still resemble control (near +2) — + the realistic case the per-condition GMM is designed to gate. The witness + scores of the perturbed cells are therefore bimodal and the GMM can split + them into confident-remodeled vs unaffected. obs carries the annotation key + columns (``id``, ``fov_name``) and a ``perturbation`` condition column. + """ + rng = np.random.default_rng(0) + wells = ["A/1"] * n_per_well + ["B/2"] * n_per_well + total = len(wells) + X = rng.standard_normal((total, n_features)).astype(np.float32) * 0.3 + X[:n_per_well, 0] += 2.0 # control — all near +2 + half = n_per_well // 2 + X[n_per_well : n_per_well + half, 0] -= 2.0 # perturbed & remodeled — near -2 + X[n_per_well + half :, 0] += 2.0 # perturbed but still control-like — near +2 + + well_to_pert = {"A/1": "uninfected", "B/2": "DENV"} + obs = pd.DataFrame( + { + "fov_name": [f"{w}/000000" for w in wells], + "id": list(range(total)), + "t": [i % 5 for i in range(total)], + "track_id": list(range(total)), + "experiment": [experiment] * total, + "marker": [marker] * total, + "perturbation": [well_to_pert[w] for w in wells], + "hours_post_perturbation": [float(i % 5) * 24.0 for i in range(total)], + } + ) + for col in obs.select_dtypes("string").columns: + obs[col] = obs[col].astype(object) + obs.index = pd.Index([str(i) for i in range(total)], dtype=object) + var = pd.DataFrame(index=pd.Index([str(i) for i in range(n_features)], dtype=object)) + return ad.AnnData(X=X, obs=obs, var=var) + + +def _config(output_path, experiment="exp_A", embeddings_zarr="unused.zarr"): + return WitnessGmmLabelsConfig( + experiments=[ + WitnessGmmExperiment( + experiment=experiment, + embeddings_zarr=embeddings_zarr, + control_filter={"perturbation": "uninfected"}, + perturbed_filter={"perturbation": "DENV"}, + ) + ], + marker_filters=["viral_sensor"], + label_column="infection_state", + class_map={"positive": "infected", "negative": "uninfected"}, + condition_column="perturbation", + output_path=str(output_path), + ) + + +def test_well_prefix_mask_no_spurious_prefix_match(): + """'A/1' must not match 'A/10/...' — matching is on path components.""" + fov = pd.Series(["A/1/000000", "A/10/000000", "B/2/000000"]) + assert _well_prefix_mask(fov, ["A/1"]).tolist() == [True, False, False] + + +def test_obs_filter_mask_forms(): + """obs_filter_mask supports scalar, list, range window, and well-prefix routing.""" + obs = pd.DataFrame( + { + "fov_name": ["A/1/0", "A/2/0", "A/10/0", "A/2/1"], + "perturbation": ["uninfected", "DENV", "DENV", "DENV"], + "hours_post_perturbation": [3.0, 26.0, 8.0, 30.0], + } + ) + assert obs_filter_mask(obs, {"perturbation": "DENV"}).tolist() == [False, True, True, True] + assert obs_filter_mask(obs, {"perturbation": ["uninfected"]}).tolist() == [True, False, False, False] + assert obs_filter_mask(obs, {"hours_post_perturbation": {"ge": 24, "le": 36}}).tolist() == [ + False, + True, + False, + True, + ] + assert obs_filter_mask(obs, {"well": "A/2", "hours_post_perturbation": {"ge": 24}}).tolist() == [ + False, + True, + False, + True, + ] + + +def test_build_marker_annotation_maps_class_vocabulary(tmp_path): + """The annotation frame carries the named state column with the real vocabulary.""" + adata = _make_separable_embeddings() + frame = build_marker_annotation( + adata, _config(tmp_path / "labels.csv").experiments, _config(tmp_path / "labels.csv") + ) + assert frame is not None + assert "infection_state" in frame.columns + assert set(frame["infection_state"].unique()) == {"infected", "uninfected"} + # All control-well cells labeled uninfected. + ctrl = frame[frame["fov_name"].str.startswith("A/1")] + assert (ctrl["infection_state"] == "uninfected").all() + # Every *labeled* perturbed-well cell is a confident positive (infected); the + # unaffected half of the perturbed well is dropped (not in the frame). + pert = frame[frame["fov_name"].str.startswith("B/2")] + assert len(pert) > 0 + assert (pert["infection_state"] == "infected").all() + # Key columns present for the annotation join. + assert {"experiment", "fov_name", "id"}.issubset(frame.columns) + + +def test_generate_writes_annotation_file(tmp_path): + """generate_witness_gmm_annotation writes a parquet/csv annotation file.""" + zarr_path = tmp_path / "embeddings.zarr" + _make_separable_embeddings().write_zarr(zarr_path) + out = generate_witness_gmm_annotation(_config(tmp_path / "labels.parquet", embeddings_zarr=str(zarr_path))) + assert out.exists() + df = pd.read_parquet(out) + assert "infection_state" in df.columns + assert set(df["infection_state"].unique()) == {"infected", "uninfected"} + + +def test_annotation_joins_by_key_under_shuffle(tmp_path): + """The Stage-A file is a valid annotation: labels land on the right cells even + when the embeddings rows are shuffled (join by key, not row order).""" + adata = _make_separable_embeddings() + zarr_path = tmp_path / "embeddings.zarr" + adata.write_zarr(zarr_path) + out = generate_witness_gmm_annotation(_config(tmp_path / "labels.csv", embeddings_zarr=str(zarr_path))) + + # Shuffle the embedding rows, then join the annotation back by key. + rng = np.random.default_rng(3) + perm = rng.permutation(adata.n_obs) + shuffled = adata[perm].copy() + joined = load_annotation_anndata(shuffled, str(out), "infection_state") + + # Every control-well cell that received a label reads "uninfected"; perturbed "infected". + labeled = joined.obs["infection_state"].notna() + ctrl = joined.obs["fov_name"].astype(object).str.strip("/").str.startswith("A/1") + pert = joined.obs["fov_name"].astype(object).str.strip("/").str.startswith("B/2") + assert (joined.obs.loc[labeled & ctrl, "infection_state"] == "uninfected").all() + assert (joined.obs.loc[labeled & pert, "infection_state"] == "infected").all() + + +def test_build_marker_annotation_none_when_reference_missing(tmp_path): + """No control or no perturbed reference → None (marker skipped).""" + adata = _make_separable_embeddings() + cfg = WitnessGmmLabelsConfig( + experiments=[ + WitnessGmmExperiment( + experiment="exp_A", + embeddings_zarr="unused.zarr", + control_filter={"perturbation": "nonexistent"}, + perturbed_filter={"perturbation": "DENV"}, + ) + ], + marker_filters=["viral_sensor"], + label_column="infection_state", + class_map={"positive": "infected", "negative": "uninfected"}, + output_path=str(tmp_path / "labels.csv"), + ) + assert build_marker_annotation(adata, cfg.experiments, cfg) is None diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py deleted file mode 100644 index 4f33a15cf..000000000 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels.py +++ /dev/null @@ -1,267 +0,0 @@ -"""MMD-witness weak labeling for linear classifiers. - -Derives discrete per-cell pseudo-labels from the MMD witness score instead of -annotation CSVs. For a marker-filtered, cross-experiment pool of embeddings: - -1. Build a control reference (X) and a perturbed reference (Y) from - per-experiment control/perturbed wells. -2. Fit the empirical MMD witness on (X, Y) and score every cell — a signed - scalar measuring how much the cell looks like control (positive) vs - perturbed (negative). -3. Gate the scores into ``control`` / ``perturbed`` labels, dropping an - ambiguous middle band as unlabeled (the analog of the annotation path's - ``!= "unknown"`` filter). - -The output is an AnnData whose ``obs[label_column]`` holds the pseudo-labels, -consumed by the same ``train_linear_classifier`` path as annotation labels. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import numpy as np -import pandas as pd - -from viscy_utils.evaluation.mmd import median_heuristic, witness_function - -if TYPE_CHECKING: - import anndata as ad - - from dynaclr.evaluation.evaluate_config import WitnessLabelSource, WitnessSettings - - -def _well_prefix_mask(fov_name: pd.Series, wells: list[str]) -> np.ndarray: - """Boolean mask of fov_name entries whose path prefix matches any well id. - - ``fov_name`` values look like ``"C/1/000000"``; a well id ``"C/1"`` matches - any fov whose leading path components equal it. Matching on the ``/``-joined - prefix (rather than ``startswith``) avoids ``"C/1"`` spuriously matching - ``"C/10/..."``. - - Parameters - ---------- - fov_name : pd.Series - obs["fov_name"] values, e.g. ``"C/1/000000"``. - wells : list[str] - Well ids, e.g. ``["C/1", "C/2"]``. - - Returns - ------- - np.ndarray - Boolean mask, shape (len(fov_name),). - """ - stripped = fov_name.astype(object).str.strip("/") - well_set = {w.strip("/") for w in wells} - n_parts = {w.count("/") + 1 for w in well_set} - - def _matches(fov: str) -> bool: - parts = fov.split("/") - return any("/".join(parts[:k]) in well_set for k in n_parts) - - return stripped.map(_matches).to_numpy(dtype=bool) - - -_RANGE_OPS = { - "lt": lambda s, v: s < v, - "le": lambda s, v: s <= v, - "gt": lambda s, v: s > v, - "ge": lambda s, v: s >= v, -} - - -def obs_filter_mask(obs: pd.DataFrame, filter_dict: dict) -> np.ndarray: - """Boolean mask of rows matching an obs filter (AND across keys). - - Each ``col -> spec`` entry contributes a condition; a row is kept only if it - matches every entry. Spec forms: - - - scalar → equality (``obs[col] == spec``); - - list/tuple → membership (``obs[col].isin(spec)``); - - range dict → any of ``{lt, le, gt, ge}`` combined (one bound = half-line, - two = window), e.g. ``{"ge": 24, "le": 36}`` → ``24 <= col <= 36``. - - A ``well`` or ``fov_name`` key routes to :func:`_well_prefix_mask` so wells - are just another filterable column. - - Parameters - ---------- - obs : pd.DataFrame - The AnnData ``obs`` table. - filter_dict : dict - Mapping of obs column name to a scalar / list / range-dict spec. - - Returns - ------- - np.ndarray - Boolean mask, shape (len(obs),). - """ - mask = np.ones(len(obs), dtype=bool) - for col, spec in filter_dict.items(): - if col in ("well", "fov_name"): - wells = spec if isinstance(spec, (list, tuple)) else [spec] - mask &= _well_prefix_mask(obs["fov_name"], list(wells)) - continue - if col not in obs.columns: - raise KeyError(f"obs_filter column '{col}' not found. Available: {list(obs.columns)}") - series = obs[col] - if isinstance(spec, dict): - unknown = set(spec) - set(_RANGE_OPS) - if unknown: - raise ValueError( - f"range filter for '{col}' has unknown ops {sorted(unknown)}; use {sorted(_RANGE_OPS)}" - ) - for op, val in spec.items(): - mask &= _RANGE_OPS[op](series, val).to_numpy(dtype=bool) - elif isinstance(spec, (list, tuple)): - mask &= series.isin(list(spec)).to_numpy(dtype=bool) - else: - mask &= (series == spec).to_numpy(dtype=bool) - return mask - - -def build_witness_labels( - adata: ad.AnnData, - witness_labels: list[WitnessLabelSource], - settings: WitnessSettings, - random_seed: int = 42, -) -> ad.AnnData: - """Weak-label a marker-filtered embedding pool via the MMD witness score. - - Parameters - ---------- - adata : ad.AnnData - Embeddings already filtered to a single marker. ``obs`` must carry - ``experiment`` and ``fov_name``. - witness_labels : list[WitnessLabelSource] - Per-experiment control/perturbed well specs. - settings : WitnessSettings - Kernel + gating settings. - random_seed : int - Seed for reference subsampling. Default: 42. - - Returns - ------- - ad.AnnData - Subset of ``adata`` containing only the labeled (non-dead-zone) cells, - with the gated pseudo-label written to ``obs[settings.label_column]``. - Empty AnnData if no reference cells were found in either group. - """ - obs = adata.obs - rng = np.random.default_rng(random_seed) - - control_mask = np.zeros(len(obs), dtype=bool) - perturbed_mask = np.zeros(len(obs), dtype=bool) - for src in witness_labels: - exp_mask = (obs["experiment"] == src.experiment).to_numpy(dtype=bool) - if not exp_mask.any(): - continue - if src.control_wells is not None: - ctrl = _well_prefix_mask(obs["fov_name"], src.control_wells) - pert = _well_prefix_mask(obs["fov_name"], src.perturbed_wells) - else: - ctrl = obs_filter_mask(obs, src.control_filter) - pert = obs_filter_mask(obs, src.perturbed_filter) - control_mask |= exp_mask & ctrl - perturbed_mask |= exp_mask & pert - - X_all = adata.X if isinstance(adata.X, np.ndarray) else adata.X.toarray() - X_ctrl = X_all[control_mask] - Y_pert = X_all[perturbed_mask] - if len(X_ctrl) == 0 or len(Y_pert) == 0: - import anndata as ad_ - - return ad_.AnnData( - X=np.empty((0, adata.n_vars), dtype=X_all.dtype), - obs=obs.iloc[:0].copy(), - var=adata.var.copy(), - ) - - X_ref = _subsample(X_ctrl, settings.max_reference_cells, rng) - Y_ref = _subsample(Y_pert, settings.max_reference_cells, rng) - - bandwidth = settings.bandwidth if settings.bandwidth is not None else median_heuristic(X_ref, Y_ref) - scores = witness_function(X_all, X_ref, Y_ref, bandwidth=bandwidth) - - # Well-of-origin tag for the gating diagnostic plot (control ref / perturbed - # ref / other), before subsetting to the labeled cells. - ref = np.full(len(obs), "other", dtype=object) - ref[control_mask] = "control_well" - ref[perturbed_mask] = "perturbed_well" - - return _gate_scores(adata, scores, ref, bandwidth, settings) - - -def _subsample(X: np.ndarray, max_n: int | None, rng: np.random.Generator) -> np.ndarray: - """Randomly subsample rows of ``X`` to at most ``max_n`` (no-op if None/small).""" - if max_n is None or len(X) <= max_n: - return X - idx = rng.choice(len(X), max_n, replace=False) - return X[idx] - - -def _gate_scores( - adata: ad.AnnData, - scores: np.ndarray, - ref: np.ndarray, - bandwidth: float, - settings: WitnessSettings, -) -> ad.AnnData: - """Gate witness scores into pseudo-labels and return only the labeled subset. - - Cells with ``|score|`` at or below the ``dead_zone`` quantile of ``|score|`` - are dropped (ambiguous). Above the dead-zone, sign decides the class: - positive → control, negative → perturbed. - - The returned AnnData carries the gating diagnostic used by the summary - plot: ``obs["witness_score"]`` (the kept cells' scores), - ``obs["witness_ref"]`` (control_well / perturbed_well / other), and - ``uns["witness_gating"]`` (threshold, bandwidth, dropped count, and the - full pre-gating score/ref arrays for the histogram). - - Parameters - ---------- - adata : ad.AnnData - Marker-filtered embeddings (same order as ``scores``). - scores : np.ndarray - Witness scores, shape (adata.n_obs,). - ref : np.ndarray - Well-of-origin tag per cell (control_well / perturbed_well / other), - shape (adata.n_obs,). - bandwidth : float - Kernel bandwidth used (recorded for the diagnostic). - settings : WitnessSettings - Gating settings. - - Returns - ------- - ad.AnnData - Labeled subset with ``obs[settings.label_column]`` set and the gating - diagnostic attached (see above). - """ - if settings.dead_zone > 0.0: - threshold = float(np.quantile(np.abs(scores), settings.dead_zone)) - else: - threshold = 0.0 - - labeled_mask = np.abs(scores) > threshold if threshold > 0.0 else np.ones(len(scores), dtype=bool) - labels = np.where(scores > 0, settings.control_label, settings.perturbed_label) - - out = adata[labeled_mask].copy() - out.obs[settings.label_column] = pd.Categorical(labels[labeled_mask]) - out.obs["witness_score"] = scores[labeled_mask] - out.obs["witness_ref"] = pd.Categorical(ref[labeled_mask]) - out.uns["witness_gating"] = { - "threshold": threshold, - "bandwidth": float(bandwidth), - "dead_zone": settings.dead_zone, - "n_total": int(len(scores)), - "n_labeled": int(labeled_mask.sum()), - "n_dropped": int((~labeled_mask).sum()), - "control_label": settings.control_label, - "perturbed_label": settings.perturbed_label, - # Full pre-gating arrays so the plot can show the dropped dead-zone band. - "scores_all": scores.astype(np.float64), - "ref_all": ref.astype(str), - } - return out diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py deleted file mode 100644 index 773c579d8..000000000 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_labels_test.py +++ /dev/null @@ -1,230 +0,0 @@ -"""Tests for MMD-witness weak labeling of linear classifiers.""" - -from pathlib import Path - -import anndata as ad -import numpy as np -import pandas as pd - -from dynaclr.evaluation.evaluate_config import ( - LinearClassifiersStepConfig, - WitnessLabelSource, - WitnessSettings, -) -from dynaclr.evaluation.linear_classifiers.orchestrated import run_linear_classifiers -from dynaclr.evaluation.linear_classifiers.witness_labels import ( - _well_prefix_mask, - build_witness_labels, - obs_filter_mask, -) - - -def _make_separable_embeddings( - path: Path | None, - n_per_well: int = 60, - n_features: int = 16, - experiment: str = "exp_A", - marker: str = "Phase3D", -) -> ad.AnnData: - """Embeddings with control well A/1 and perturbed well B/2 well-separated in feature space. - - Control cells cluster near +2 on feature 0, perturbed near -2, so the - witness cleanly assigns positive scores to control and negative to - perturbed. An unrelated well C/3 sits at the origin (ambiguous). - """ - rng = np.random.default_rng(0) - wells = ["A/1"] * n_per_well + ["B/2"] * n_per_well + ["C/3"] * n_per_well - total = len(wells) - X = rng.standard_normal((total, n_features)).astype(np.float32) * 0.3 - X[:n_per_well, 0] += 2.0 # control - X[n_per_well : 2 * n_per_well, 0] -= 2.0 # perturbed - # C/3 stays near origin - - # Ground-truth infection_state keyed to wells (A/1 control, B/2 perturbed, - # C/3 unlabeled) so eval-against-annotations has something to score. - well_to_infection = {"A/1": "uninfected", "B/2": "infected", "C/3": "unknown"} - obs = pd.DataFrame( - { - "fov_name": [f"{w}/000000" for w in wells], - "t": [i % 5 for i in range(total)], - "track_id": list(range(total)), - "experiment": [experiment] * total, - "marker": [marker] * total, - "hours_post_perturbation": [float(i % 5) * 24.0 for i in range(total)], - "infection_state": [well_to_infection[w] for w in wells], - } - ) - # pandas 3 defaults string columns to ArrowStringArray, which anndata's - # zarr writer cannot serialize — cast to object (matches orchestrated_test). - for col in obs.select_dtypes("string").columns: - obs[col] = obs[col].astype(object) - obs.index = pd.Index([str(i) for i in range(total)], dtype=object) - var = pd.DataFrame(index=pd.Index([str(i) for i in range(n_features)], dtype=object)) - adata = ad.AnnData(X=X, obs=obs, var=var) - if path is not None: - adata.write_zarr(path) - return adata - - -def test_well_prefix_mask_no_spurious_prefix_match(): - """'A/1' must not match 'A/10/...' — matching is on path components, not string prefix.""" - fov = pd.Series(["A/1/000000", "A/10/000000", "B/2/000000"]) - mask = _well_prefix_mask(fov, ["A/1"]) - assert mask.tolist() == [True, False, False] - - -def test_build_witness_labels_separates_control_and_perturbed(): - """Witness scores gate the two separated clusters into control/perturbed.""" - adata = _make_separable_embeddings(None) - labels = build_witness_labels( - adata, - [WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"])], - WitnessSettings(dead_zone=0.0), - ) - # Every cell labeled (dead_zone=0), two classes present. - assert labels.n_obs == adata.n_obs - col = labels.obs["witness_state"] - assert set(col.unique()) == {"control", "perturbed"} - - # Control well cells score as control; perturbed well cells as perturbed. - is_ctrl_well = labels.obs["fov_name"].str.startswith("A/1") - is_pert_well = labels.obs["fov_name"].str.startswith("B/2") - assert (col[is_ctrl_well] == "control").mean() > 0.95 - assert (col[is_pert_well] == "perturbed").mean() > 0.95 - - # Gating diagnostic is attached for the summary-PDF plot. - assert "witness_score" in labels.obs.columns - assert set(labels.obs["witness_ref"].unique()) <= {"control_well", "perturbed_well", "other"} - gating = labels.uns["witness_gating"] - assert gating["n_labeled"] == labels.n_obs - assert len(gating["scores_all"]) == gating["n_total"] == adata.n_obs - - -def test_build_witness_labels_dead_zone_drops_ambiguous(): - """A positive dead-zone drops the lowest-|score| cells (the ambiguous C/3 cluster).""" - adata = _make_separable_embeddings(None) - labels = build_witness_labels( - adata, - [WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"])], - WitnessSettings(dead_zone=0.3), - ) - assert labels.n_obs < adata.n_obs - # Dropped cells should be disproportionately the near-origin C/3 well. - kept_wells = labels.obs["fov_name"].str.split("/").str[0] - assert (kept_wells == "C").mean() < (1.0 / 3.0) - - -def test_build_witness_labels_empty_when_reference_missing(): - """No control or no perturbed cells → empty AnnData (skipped downstream).""" - adata = _make_separable_embeddings(None) - labels = build_witness_labels( - adata, - [WitnessLabelSource(experiment="exp_A", control_wells=["Z/9"], perturbed_wells=["B/2"])], - WitnessSettings(), - ) - assert labels.n_obs == 0 - - -def test_run_linear_classifiers_witness_mode(tmp_path): - """End-to-end witness path: config → weak labels → trained classifier + metrics.""" - zarr_path = tmp_path / "embeddings.zarr" - _make_separable_embeddings(zarr_path) - - config = LinearClassifiersStepConfig( - label_source="witness", - witness_labels=[ - WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"]), - ], - witness=WitnessSettings(marker_filters=["Phase3D"], dead_zone=0.1), - split_train_data=0.8, - ) - - results = run_linear_classifiers(zarr_path, config, tmp_path / "out") - - assert len(results) == 1 - assert results.iloc[0]["task"] == "witness_state" - assert results.iloc[0]["marker_filter"] == "Phase3D" - # obs carries infection_state → metrics are scored against it, not the - # self-referential witness label. - assert results.iloc[0]["eval_source"] == "infection_state" - # Wells align 1:1 with infection_state here, so agreement is high. - assert results.iloc[0]["val_accuracy"] > 0.8 - assert (tmp_path / "out" / "metrics_summary.csv").exists() - assert (tmp_path / "out" / "witness_state_summary.pdf").exists() - - -def test_run_linear_classifiers_witness_falls_back_without_annotations(tmp_path): - """No eval_against column → metrics fall back to the witness label, flagged as such.""" - zarr_path = tmp_path / "embeddings.zarr" - adata = _make_separable_embeddings(None) - # Drop the ground-truth column so eval-against has nothing to score. - del adata.obs["infection_state"] - adata.write_zarr(zarr_path) - - config = LinearClassifiersStepConfig( - label_source="witness", - witness_labels=[ - WitnessLabelSource(experiment="exp_A", control_wells=["A/1"], perturbed_wells=["B/2"]), - ], - witness=WitnessSettings(marker_filters=["Phase3D"], dead_zone=0.1), - split_train_data=0.8, - ) - - results = run_linear_classifiers(zarr_path, config, tmp_path / "out") - assert results.iloc[0]["eval_source"] == "witness_label" - - -def test_obs_filter_mask_forms(): - """obs_filter_mask supports scalar, list, range window, and well-prefix routing.""" - obs = pd.DataFrame( - { - "fov_name": ["A/1/0", "A/2/0", "A/10/0", "A/2/1"], - "perturbation": ["uninfected", "DENV", "DENV", "DENV"], - "hours_post_perturbation": [3.0, 26.0, 8.0, 30.0], - } - ) - # scalar equality - assert obs_filter_mask(obs, {"perturbation": "DENV"}).tolist() == [False, True, True, True] - # list membership - assert obs_filter_mask(obs, {"perturbation": ["uninfected"]}).tolist() == [True, False, False, False] - # two-bound range window - assert obs_filter_mask(obs, {"hours_post_perturbation": {"ge": 24, "le": 36}}).tolist() == [ - False, - True, - False, - True, - ] - # well key routes to prefix match (no A/10 leak) and AND-combines with others - assert obs_filter_mask(obs, {"well": "A/2", "hours_post_perturbation": {"ge": 24}}).tolist() == [ - False, - True, - False, - True, - ] - - -def test_build_witness_labels_filter_based_late_window(): - """Filter-based refs: control well vs perturbed well at late timepoints only.""" - # control well A/1 (early t), perturbed well B/2 spanning early→late hours. - adata = _make_separable_embeddings(None) - # Give the perturbed well a real hours gradient so a late window selects a subset. - is_pert = adata.obs["fov_name"].str.startswith("B/2").to_numpy() - hours = adata.obs["hours_post_perturbation"].to_numpy().astype(float) - hours[is_pert] = np.linspace(3.0, 36.0, is_pert.sum()) - adata.obs["hours_post_perturbation"] = hours - - labels = build_witness_labels( - adata, - [ - WitnessLabelSource( - experiment="exp_A", - control_filter={"well": "A/1"}, - perturbed_filter={"well": "B/2", "hours_post_perturbation": {"ge": 24}}, - ) - ], - WitnessSettings(dead_zone=0.0), - ) - # Only late (>=24h) B/2 cells are eligible for the perturbed reference; the - # early B/2 cells are not in either reference, but still get scored+labeled. - assert labels.n_obs == adata.n_obs - assert set(labels.obs["witness_state"].unique()) == {"control", "perturbed"} diff --git a/packages/viscy-utils/src/viscy_utils/evaluation/witness_gmm.py b/packages/viscy-utils/src/viscy_utils/evaluation/witness_gmm.py new file mode 100644 index 000000000..2421a006f --- /dev/null +++ b/packages/viscy-utils/src/viscy_utils/evaluation/witness_gmm.py @@ -0,0 +1,129 @@ +"""Gaussian-mixture gating of MMD-witness scores into confident pseudo-labels. + +The MMD witness score (:func:`viscy_utils.evaluation.mmd.witness_function`) is a +scalar per cell measuring how far its embedding leans toward the perturbed +reference distribution. This module fits a two-component Gaussian mixture to +those 1-D scores and derives a confidence-gated label: the lower-mean component +is the remodeled/perturbed mode, and a cell is called positive when its +posterior for that mode clears a threshold. + +This replaces the earlier hard sign + dead-zone gate. The GMM crossover is a +calibrated boundary (it adapts to the two modes' locations) rather than a +hardcoded sign-at-zero cut, which matters for the heavily imbalanced gated +classes the witness produces. + +The single public function :func:`fit_gmm_labels` is pure NumPy/scikit-learn so +it can be reused from application code and standalone analysis scripts alike. +""" + +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray +from sklearn.mixture import GaussianMixture + + +@dataclass +class GmmLabelResult: + """Result of gating 1-D witness scores with a two-component GMM. + + Parameters + ---------- + gmm : GaussianMixture + The fitted two-component mixture. + remod_component : int + Index of the remodeled/perturbed component (the one with the more + negative mean; witness scores are negative for perturbed-leaning cells). + posterior : NDArray + Per-cell posterior probability of the remodeled component, shape ``(n,)``. + hard_label : NDArray + Per-cell gated label, int8, shape ``(n,)``: ``1`` where + ``posterior >= pos_threshold`` (confident positive), else ``-1`` + (ambiguous / negative — caller decides). + converged : bool + Whether the EM fit converged. + separated : bool + Whether the two component means are meaningfully distinct (``True`` when + ``|mean_0 - mean_1| > eps * pooled_std``). ``False`` flags a near-noise + marker whose scores are effectively unimodal — the caller should skip it + rather than manufacture labels from a single mode. + """ + + gmm: GaussianMixture + remod_component: int + posterior: NDArray + hard_label: NDArray + converged: bool + separated: bool + + +def fit_gmm_labels( + scores_1d: NDArray, + pos_threshold: float = 0.8, + n_components: int = 2, + n_init: int = 5, + random_state: int = 42, + separation_eps: float = 2.0, +) -> GmmLabelResult: + """Fit a two-component GMM to witness scores and derive confident labels. + + The lower-mean component is taken as the remodeled/perturbed mode (witness + scores lean negative toward the perturbed reference). A cell is labeled a + confident positive (``1``) when its posterior for that mode is at least + ``pos_threshold``; otherwise ``-1``. Deterministic under ``random_state``. + + Parameters + ---------- + scores_1d : NDArray + Witness scores, shape ``(n,)`` (reshaped to ``(n, 1)`` internally). + pos_threshold : float, optional + Posterior of the remodeled component at or above which a cell is a + confident positive. By default 0.8. + n_components : int, optional + Number of mixture components. By default 2. + n_init : int, optional + Number of EM initializations (best kept). By default 5. + random_state : int, optional + Seed for the EM initialization. By default 42. + separation_eps : float, optional + A marker is considered ``separated`` when the gap between the two + component means exceeds ``separation_eps`` times their mean component + standard deviation (a separation-to-width ratio). By default 2.0, so the + two Gaussians must stand roughly two standard deviations apart — a true + bimodal fit, not one unimodal blob split in half. By default 2.0. + + Returns + ------- + GmmLabelResult + The fitted mixture, remodeled-component index, per-cell posterior and + gated ``hard_label``, and the ``converged`` / ``separated`` flags. + """ + scores_1d = np.asarray(scores_1d, dtype=np.float64).ravel() + X = scores_1d.reshape(-1, 1) + + gmm = GaussianMixture(n_components=n_components, random_state=random_state, n_init=n_init) + gmm.fit(X) + + means = gmm.means_.ravel() + remod_component = int(np.argmin(means)) + posterior = gmm.predict_proba(X)[:, remod_component] + + hard_label = np.full(len(scores_1d), -1, dtype=np.int8) + hard_label[posterior >= pos_threshold] = 1 + + # Separation ratio: mean gap relative to the components' own spread. A true + # bimodal fit has the two Gaussians standing well apart from each other + # (ratio large); splitting one unimodal blob in half gives overlapping + # components whose gap is comparable to their width (ratio ~1-2). + component_std = float(np.sqrt(gmm.covariances_.ravel()).mean()) + mean_gap = float(means.max() - means.min()) + separated = bool(mean_gap > separation_eps * component_std) + + return GmmLabelResult( + gmm=gmm, + remod_component=remod_component, + posterior=posterior, + hard_label=hard_label, + converged=bool(gmm.converged_), + separated=separated, + ) diff --git a/packages/viscy-utils/tests/test_witness_gmm.py b/packages/viscy-utils/tests/test_witness_gmm.py new file mode 100644 index 000000000..a8802cd3b --- /dev/null +++ b/packages/viscy-utils/tests/test_witness_gmm.py @@ -0,0 +1,54 @@ +"""Tests for GMM gating of witness scores (:mod:`viscy_utils.evaluation.witness_gmm`).""" + +import numpy as np + +from viscy_utils.evaluation.witness_gmm import fit_gmm_labels + + +def _bimodal_scores(n=500, seed=0): + """Two well-separated modes: a negative (remodeled) mode and a positive one.""" + rng = np.random.default_rng(seed) + remod = rng.normal(-3.0, 0.3, n) # perturbed / remodeled — more negative + unaff = rng.normal(3.0, 0.3, n) + return np.concatenate([remod, unaff]), n + + +def test_fit_gmm_labels_two_modes(): + scores, n = _bimodal_scores() + res = fit_gmm_labels(scores, pos_threshold=0.8) + + # The remodeled component is the lower-mean mode. + means = res.gmm.means_.ravel() + assert res.remod_component == int(np.argmin(means)) + assert res.separated + assert res.converged + + # The negative-mode cells (first half) should be the confident positives. + assert res.hard_label[:n].mean() > 0.9 # ~all labeled +1 + assert res.hard_label[n:].mean() < -0.9 # ~all labeled -1 + # Posterior of the remodeled mode is high for the negative cells, low for positive. + assert res.posterior[:n].mean() > 0.9 + assert res.posterior[n:].mean() < 0.1 + + +def test_fit_gmm_labels_unimodal_not_separated(): + rng = np.random.default_rng(1) + scores = rng.normal(0.0, 1.0, 1000) # single mode / noise + res = fit_gmm_labels(scores) + assert not res.separated + + +def test_fit_gmm_labels_deterministic(): + scores, _ = _bimodal_scores(seed=2) + a = fit_gmm_labels(scores, random_state=7) + b = fit_gmm_labels(scores, random_state=7) + np.testing.assert_array_equal(a.hard_label, b.hard_label) + np.testing.assert_allclose(a.posterior, b.posterior) + + +def test_fit_gmm_labels_threshold_strictness(): + scores, n = _bimodal_scores() + lax = fit_gmm_labels(scores, pos_threshold=0.5) + strict = fit_gmm_labels(scores, pos_threshold=0.99) + # A stricter posterior bar yields no more confident positives than a lax one. + assert (strict.hard_label == 1).sum() <= (lax.hard_label == 1).sum() From d01e74983e8dbedc9bc944249efa4bdbbab59279 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 16 Jul 2026 17:09:08 -0700 Subject: [PATCH 29/89] chore(dynaclr): untrack mock DAG visuals Remove generated mock visualization PNG/PDF files from version control. Files remain on disk but are no longer tracked. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../docs/DAGs/visuals/mock_f1_over_time.pdf | Bin 24794 -> 0 bytes .../docs/DAGs/visuals/mock_f1_over_time.png | Bin 62754 -> 0 bytes .../docs/DAGs/visuals/mock_metrics_bar.pdf | Bin 27099 -> 0 bytes .../docs/DAGs/visuals/mock_metrics_bar.png | Bin 48137 -> 0 bytes .../docs/DAGs/visuals/mock_roc_curves.pdf | Bin 27513 -> 0 bytes .../docs/DAGs/visuals/mock_roc_curves.png | Bin 111329 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.png delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.pdf delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.png delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf delete mode 100644 applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.png diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.pdf deleted file mode 100644 index e8bf4dc7760de2a1e4fa5a47ddd0b33242db53eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24794 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 KLqSuK^uGb7_j$1Z diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.png b/applications/dynaclr/docs/DAGs/visuals/mock_f1_over_time.png deleted file mode 100644 index 20dc5b0a0487084b1264b0ba8fe6ae795a8bb195..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62754 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
X`r(bALW*M=Wa%AlE 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 diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.pdf deleted file mode 100644 index 748a7767383717df007e4666832551d3a987baf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27099 zcmeIbby!tR)Hkd&N4insAOg}S;J~4gl1?cB>5?uHMd^_46s0621ySkl6huG~5fDT| zkx)eV_C~#LZ#nOy@0b64dhKh^S$p=(nl)?BS~IhL1Dl4dJTIJ20K!)C5_-QD0*At& z&gS+IF)=8=&Q%vHD8G!Ehna)34U}KQ%*M(ciU2ZbK_w(0R!){+M&Vyxkau?SfFchw z@awCa+gn+9K!v{ll=Jpb(DpF%u!0JFCuo>?cv!hPK?T8|5Pof2GfO)s8z}0>Q#WS| zZ7UDxMW9+)d4LrwZx1NHq9ee23gGTL`}|r~?#`ZW z7C?JI{(<^ctt{=#&N+JnHNt@Z0!SDi8Yv7%2ttv9Xg-*LAW#dx4A4HH5O*m0JD1W< zPR_tDIQYh|G65g|6rzfilZ}Th6#k=Pc{>N7*-(CY2S66GRu;~dRv`E89&T1!b1mUEYPZ4-$x4M!^q#PT zWv0kjVa-h)o9~!;6!bE7gcDU*`8vQ9*S*Z3Ijzc7c{Wu5S72SL7r~KG=rv2Dlrb|p z&6}jHa*c@SL~-)@d6w^& zH_BtnDi`K$GTlO&AEo1O7dU<4epK}>McPQBQWWl)dessck1=yke91(VlF!>GLFJF8 z&a$Q@y*e>suPyb|hy7uEuYy?l+UxSs5L~{1g~7|z;Q{4Dh6FL^XY8w-Aq$Eb(**vx zu0sM>3>R`a?=HEUD5kXL65OMZNaSlE^GCF>RFo!sv4osI*+Hd-uH?%tYA4NC<(DNB ze8p11yNJF+9ZPUL^;v^0*GCz<(G%pdQB?#EtiFS`xMvzH?=F_S48KQl)03p)Vi^`% z>eQ>C$2{&*S?8|Yi@g!qJep`p@E*D`Nfmocnm=IHnj=$b&va@9k5Eh%5>(U5KRJpx zLyUDvbp6^#o!6mH@6n$5AV8kMQe>=VhkK?Zszhd^5Rs*{r!ln|fa6HNsNys0FldJx zbUC+wZ7)&f6RoE3X=nsvD`ZEy@WWjj==Vyucj(WaVpZIjH#o&HID7+73H>-hCplN$=eU2@q`laIiC*8X(=(fo?McUO zycH1&;3)CyQ1iAKo`td}E|HPd_xT|u&P>cT<_ue;Mt_JTq4axxp7%^9jb64igSjbRQpC;O21#$Ej%V5Dqf+IZQgU1mvZ11g&f8MJJ@LpjgJ+i%d!lToxWaqm0?Xj_o(S11CRIM` z(PA4jW1?}@smYDmbN$U0ojQh{7di}j*9`X4OH(cw-z+ep7VOx#Nht^3lc5 z9$65aiCTX-Co3GZF{a$!6uSkPOu)r0P589D_&FxD@8biBBAVxOyUAHInx7fzZ!dRd zqxhADU%fRBPo*nRb!cNNiAL)~hjm2dcM3+RYb=i6?@+*}Yog13(P4Woy|T5(E-e`) zQC>NLVg(2Uy8AXbJFX2udZq6b9v+eDljo)tTXDz4-V zcDq-ezY{LUYRH=xq9EIKvHejYlL*IonEKtGf`XouCMPy0k;kp&*^}db=OqS-YjEzw zXh-Y3AQeB!U%pQBL2fCYpz-kEHr3Wa+=o}^o)_UaxJeWl z!)A8ba82Z*$5usN%-k253kUMfHd`W7h5I&78cWBk6l_-1~gTq zl5eZNhT0bt`kp?(!o?Cx2MziUGxWo>AKEO?j=@nV*iUy{q-zaE7i_pwW&QdA9>Z!=7lG;6cR>D$HWqh2+kUe z%aA)II(*>DJjvF&cCkCmRRCo%p>t~~kJ!v%+C@a~gK)J|klVz4vuDqha#<H|V}yJ+IN_!y=Bydq-lkmnzY;K=b5(qJv2I?Buy|WOCclh8w;`*JehxBAjYX%#7(}F|dZg`9UJkf96e4OEJ1# zilFW}_h%{P4wL!I5NtqO%5@j15Uwg@J=E+9c%DhmmuKA3^N|?-fk}$v4)|ZZDG|Gs2fQFG) z(27|Z&ElZPwnqD^?;~;0t~do_PR2~agU_ji*?3IUNK`p-GHWnV+iR;tWqF2@O1c`- zhxl-=)22mHMm(WO6c*1$@s%$K=eQE@8g851>{~TbknGztGgKf*H1}-57WXhwAoN;d zAmGSRwh7wCEP#dqzD;~M%EEzt1{HYQWlhi_@Ziff3v;j$3DM+ZmyHKl%v?a73YwwMjow%OhK#t9DV0F^F@Alv&wb&EXp;6)`03dN8hQo zWiP&qc;&V%>Zf-mT@~CqJ1&muqOuP3#O7=s(?^QjnU#Kf{1R5xj{Mx(vk8sj5FCNB z2h!Wm!o0q?oHG#!9jlFW)`-~7ezMeew*<-4^Vl)-p~3u!cZd$^T8Qno?``IoC!*5w zve_xq!h9#pYRw4>8fe0D#M7U+&cBS3?z0id2skTG7jZM(2b(1S>e|(5VY-7$JfY;i z2UyC(dRQ2Q_Uk<2#}|ea6!^0mi^nUwVZ#YzcibU-uem;sE{dt;KX6%V8VIbcs;E z;WF96eFpoxv(t(QF{IHleZ1PkXIRbY=eN%Yz3)D``tG6kOZ`tDt>IDWoUR(9=|&<2 z`Q2Q(8`2y~d2|iBjr{NH#Om0IR%?AXWhLuwnn>!oSaV;+&l0*;IXl_^;gUGFMCqfu zJ#SJYp2lPeSq~H%$URmtC{@dnntbYMncbgf+j*TvXZ_o1O%TqVEeEBpA?dkIlf=eW z8}*eh>zl#v*Ck08LX$9b6*#r}O}~-IzZnhVMV+wWM6zGSarsT9_LM}1@T4JM_Z^w6 zI6C+U-2_gOT*yMSD>E&}U+yhox#ZKK=1O|Cp(>w(`;3QTqM~fCuN+ahqC(vX``z%J zg-vxip)fW)1)br_&PB4h6MFDaN1ZEVuT4GqKc?n{xu&iw+k_8LEtI$}hO??G$dQ*U zX)71=T@y+RzT5dU|CsZtAKzwdb$viXO^Nm^hWGCtV^h!aEvr&a?v*wSUsyNLP_c2W zY>o1=N!|P6c}Y@5%Vz4i)vK^$Y4IZD577%QNyVFC_-o?n=2LUA4>zub1pac1HSZc4A9ewlx?rb)4~G3aX5V zrt2M7$DNKros=1LqN%eqFq$w4?y-Mt_^~AG!|l8Ew{PgHZy((8HS&2gvBPiW>79+C zSHN|E-+BdshW+^i5{&P0WR)h8TW9Q0H}!j?t1KoCyI%3BX}9u)1VNtuo0-Dm?Ci{4 zo9tUf=`shaK8AO88+&UI$&F=V+6HAtE%IEShU?b28xl`tw%8PyFKDlGKX|2>Tf!>2 zGSn6^BySNPmFRX!iEh1bP&1S5qVn+=RlRzCf~^Ltk!ww!D>t&M%(d9vZLTbD2C@ozQ8?9nwpLyKg4 z_?CLuDAt;qJ)@He7wV_WFjlL(?B0ID{(}sW?87lNM$`A#{ovJaYw*P`bi3blUZ9L; za6CCxtns0gbjqi$6+Sp>1uNPzx?4Q5tJLuj!Wul?bhFvl*MwOz_L%i8459{?RDKgR z68RUR2F5BUz~svANZ``a6@A%S6pNNOPyxoO+!sBqT$Xpw7Otf~JyTmh#uloy%?^A3 zITzY+1&jv^yFAGc)m8sS;*Kjt@d2rowWp8V1bD`zyh8LYfViw!m zX9boeEdgST#@GGOk7~YNT3Fm|M7-X`j<;=`IqK5XB(r>YKHSRU7OUcW5MeSB-1kgUZwfP{&teyIg>naS6Ce931;NF@FSzdZliwdzA^?8=Sf{uKJRUH2@4rE^juBLR z%W8P?%%ipkIb%)&DkTee*yBu_*qWiPhGnyb}m0XE% zvRGgFeyaWfHNx@Q$Dd$qvturNSQJHjUP#-5SdBscg8es0pO~`x+gu$9B7TvljCDG^ zTmm_3!lojxW;wdH(5O9l{^3h$BzKA@?vw+i&Auh=a{S`)*d9wUzGR(XlQGrQcFoK%d;N4S?C$8|-q7b$P0na)wK5C>!Z5`V z68a1IL_Q&ImLkA$FTNWzR{-sEb6JzzVhN)3swQO!KhZHbOGb3bn_L6c2z!f%I;STo zyRWUbdyF&JZcIL1ZE(vwuv#U5c?UoC1}&x!AHy`@Z+sLa;*kbS1BSw-kbm0=d25Xa z;S4bfp&_&8qg8>0_6-x{oek}yJJ*sp?_%YWfBe{R8LuF_Q2?U%Ybz+*|H{MES_3}Z>pFQOyya*#zEkYu!I_wf+9`ea>Dy-LEOkTqp2RN z#-NR>q@*=3t~bxhGmOT~bhBx{LZ?lhglccv6K?zUTjh<9o?Aq?2=qF2Buv(A(bXD1 zrqhaW*cN2bj(VkX8(G+B>0VR-wGzk;zFcE|PU+64nK{85GonOlRYwH`48cb}D&fD- z8Q!Qm;$|qB%-4)o)@EO#6-qhI?tRC{QWla14^&wp*Yh@|M*Z(Yu%|!V;-b8lio4WU z7~*er<{?&eKhN?^<{M3twB53|D|m1FHXOx1vhLvLiO{!UU=M?f3k*Gfq0_O-B)~T> zvx5$fA%o4kXB8Fm-P@TNx6E@g$Df~M zOSJ=pPI8NQ3(Dp4GhJV_cN*4>HXdES*JVO`^OV- zh1oGQ7n7Ta5dL#>1>@09mT)qePd9JX`W=6EZ$ZqFh`nO{&aS2-KJf!KYPpU3ZH}pR zzTSp0X5yi~yY6TefBmYsXOATfcvCMguj+k#8r*W`^m{Scr=u3Y;H#g)4gzGFk&BkgR@r6 z?^wZMQ*64#DMO~WBwEc%lR1D=bPiZ<jTpa{WF2Ek#nlebx z6Rt*&FZ83r?+q7MG3?%39eDfYSQ6K%s~9-M;8+R%#Zm?mh0=p*3=0Zr4L=mng254b zFrf!J=i&1R6#N*{{fTJ++9;&c^a9!lwuN@FLL1Q=*&P!ICwYm1g!MbfGYi9Wdp$sAS2_hKbIMwot;L9+Qvq`5uiFFS}JyAQ6x%?u!YWEyg zM*1oJ;}H&zLz0AC(c+Yig3>1CJ1c9&pMvIki;E4YBIPc*$|;>StxB#MfH#tdR)o( zA=gKT-IJ1YUrC2UlsGU*2ZLiFg!r>+AVGE4fZ>x{a3<|igtXgA*S&6on%*~fI+E9Xxl5tP-p}|0YXY7wrR}32 z-MgNg*}}H(N?6GpFOJnQZ`P{4iBs-~FBTu$y%)ByHq0&m$nJPd7J9PoTt;A@AU$rH zFsHgfsM^!bNlPY;h%+g!mxp9NeY1I*GAK!RoKN*=tKh$W+utV%hdb!}=gi!#zNZvq z^z>zQxn-^F&2&Ar&79nM)y*C3Ts?seZFxI4cMlm`GdCyV33ec5rrrqJZZzP$6*M3l##Ms6&N;ceS8s zfNloiH;3|D0JADUX{>-7u##sB<+lS;z+mbCg#+6f{7%3Ppt}KuLivGk15YTw7qD>; zym1xS>i~A?`St89J#2wye2;PXHwfZT!at`^|MuiBeQ+qKKMUN!%m#=qKz*0%|0sGW zzqGpr7}0z#qH_aA8-*%;mh5osF#rs2ch|lR&%#5XoU@A?;-2U1d+g;90m$NSOBx2Hp=FgA{^*caRCNQQ(CF6c2n?5NP=KGQjW@pbW4)qyQiY;loQ1 zP=7Fu76SMM=qNY>XyEsH1%P@50i8yo!S@7!ZwLVu280FP4=*6(1r!D-1n>^lf7ofD z00ABV!9u_~1%Y=Ch4Pa*@P4R2U`+zx4;UySkZrJO;2pRQ`vt7-`vnpF(P#e@^*sSF z_`L|gK?h$G1i1n@|EUH)yX)s2;1=?ek;4)Mgy6sn-}@2NgMZ2gbAfF-R3<>7z~BFr z0x|`IK@Ok&C;*K@10Uq~3si*fIsodyf3HJX19}1K#4p#OK7cy$^E%X(A36c*!><>} z*WquV3{W?IUSP{nFkzq%0bW5Jz;K~~Iu7*$uqvouuAe&b^A6|&z@Gr*@D8SbxeojK z`*SeQKNqMMV6TGre>Z)9^&e%RfULnFC}8Xbgm{=zhr&U#4bZfphkH;!$_xth>>Yz41h4==ryM@L3Vbt% zsei|P0kD{#*X#eoT7DNfUia z(vht%W^cmEMsYrQ=dR;MZO$@YQ3lk#$F+yrJbB|W#Tgo2EB0}l;=m18VteYw50psC zQ5MB^3OB8o(+ofU7fZ@w`vN}$KV8Yi`4s4`4t@N zX~oOg-wqyzX!2uYPfVOyZ;?ME(H^^YR_}W6vOwPlQG@x8A*I>*hA>DTS1ZI0f5=T2 zNtAUe4J!rt(ZE_FjYgD4L$%%2YhEBwI?T>Uq3<1RRBVaxi3FXiMS($?`T6(LS`CfQ zH|}utDvVF_JW`t+Q<~1PSy43dGEsN5yF}+nC4?JC!Yo>k|K;>s9zKycr|k^R(4;&T zVO~Gx@NAtSl5%qP8`fvMd$ZKma|PbI)jAX0etK1JKSD4P>SDy87vNAl8rZ09mQ?x&~fANOei!0)ErT4~4 zgxn^di;MJ)X_y#(4H~qq{>Jf~zYcD~9885Kz|dF>lc&F#SE~+y%YH;S!&#hSSg#L& z*o@PYO;N<;*_C%ZIBuJULY!RniK;~zNF75Wg+EVqq zYs!W6%G?@hUNS^rU<|`7O6V`v`;dY{7hoC{)yF9Ag}jBll~4X>T8B&7cf6`!rQeT7 zM%gksypko>%G@lZ9f7L!>piWHCw0<6m85@@p^OMvg%G5r$FT7Lcrr(aC(zUXQ|OQ? z@HGMR@Vc+md(Fp^OFFKq-;yR>Jt${mq*`quV^ubKv(_$4V^X8>{9NcjnFK9!t z>SX#Tr**eOVV5J9E&?J{9C_=jj=Ul125J!DmoOKEFjV&{Vb9qc<4B36rF3oO6BVT~ zr#x{gw3<0dqH^#0`+?6HcBT2TuF|IkI`U_1>bn-b4~`|N8(~=01h$Kg$`3IAf0Un~ zhH@FOhzo{ZN##pX5G#a&AVA|Kaw0QHp zOy5g+&Kh+f=ONK1VX7ym>gN}rWzCz%8MO@N^2PfT(*k+_AVtCn*s)Z#3SXUmy^k6*Y&gs>HTz( z*~~~M>Zr~Gp6U_#zz^q$e+W$jSGn4tL^3;>=M;_|_o;|)n|Vyu?{W2 zfP@DLrBR$T<+|cVy0ujxy!s+O)=ah+#c&Cx65Y3X>Vz1 zdGFJgTB(ezm=!zl5-mqbU+LW%^C|lTBVYf7JtHk)iJ{3qXKmo*`X2*@__Im?O?Ja! z`O)N?vtqZE267hnUv#gfIm8w>Z&122P}+P7BgkXeD>1XypsWuKmU(6>diQ;^jg;E! zlYAbWF1b@d?$>X(WkAv&hjuwUJYRBqL4{=*#;)kFq}Yubv~`=yd|9mXO677x=bH?- zD|Nk>r;=M6LVI_G=XB8bxh}_{$kXI5pK6h3TB#Vyy>HuR!1YonQq`C;sV$>p&0xCc z&8_uvqq%21E&V%fkD3^m^`@7&x+Z#qCbbgYK1FYpCy(s9R7V8+%qm=CKkwpsA9^8w zd~izJ=bQG+GrQlgH(T@=F$fHU7l-_t)g?hK(We>G1n--)m$0%7UiDqg?8*D=et^Of zrRJ$PhxHT-Kg~OwrC77!bXZ(#&JXlIM*uwNZJ(EB3Vw+}m59`p-nZVDQpay4QjxS+hz&2UwPi zvOBcSk$rk49uSu*)D*!Rz@37AZn;~@EO+W%;G5md`|?52ZI9(2PW8m9^qn?N!kD>dq<+f=q|-2nZbnO<1LSV62XdX#Zv?vY)zT?;oBhHXXwxiQvSg^_Y+>Z z>6sjN5_Q>fP#E3crngTBnr@Fv)w%)kp57fnh2ILfS{Cf%wYQ^2P=n9m_%1 zB9E3d(X{v|)#5w%dGe?Eyh)z3XImutl-U{U<_=F@9?k%-s! zD#i|4_pdRFnaEyPTEbTdKW&jhyErFJ_)e+S)-WvTiX67|+D3KnHNv$Quk5=Y;1{J1 zigH?4y!X)JbD|ApVd~x}{IHNaarxZP=L&AN&GQo>NH>l(+s?OR-EU&|Uq#_4$CTN! ze0p8(G|p#QuPq{HZmH&0Vi$2iZAXJ@^hsL-5!sf5oD8RX+_U9NZG$H+Cp*x1EA8Sk z`dX37KV{a$E1>UGrMOoCYLl@%~2gu-F<;7dToJ$}4q^HW90;#uI#zCA6s#Sfkm$IRwmZzLKi3=#XO& z7zSS#0jv-FSzzkuc)$quT@99iU7&j*)l^VWgPQnqvo=0WE%pOT&nP8hns*v2YlU^! z3qwn&^=oaFj;8HuQp)0;*G#7qBTkrw#JP-@%sFL6(;j#w4e8^rqdwm_0RTXT)>nXK69swHv?W5_b@DcQq7iSGgyT4}BG$KG@OYtb zki0*EiejN&{LJMOfC|i6xw?*Z(oOonc=|JwT-_2I^b{e@xTg79=0eb=1}_h$hi56= z&!tQAQL+rYlHe-Qh)gyiq0$gYRlOGNQ2}jEw z1_|?jIJ3d(!X$s6*Lr=X3_G1uL*+&d^2{)~zXB0Fv3!oL^lq7dQ^dKCPH)}^wnFdA zXh8>kNW(lvEgcqNVUPeZ9 zW^rA+<4wb}+Ksx%5idRuoz>C`@5#TD*emJSn+OrH9>L+Ec=FnkQdPLGPEjk8J`u6Z zbkFOwZ;4oUuE>(5>R<_L+T^JEi?q)SE{`rvS*j&>C)1@Yrq zafEcmnYWzhI-NA>(S-Hu2Ghln2gl3kb!3yTt#pMI4Hib$%eG6XtL~mnR^ip{bke53 zw3iq;cHIM)t4LrYabx&G)$j@Z@T(Nt=)Low7snk~LPVU#H^LW~ksSz%K~rV4X?Dt- z!s{0Xp5$eWPaY3?_%kPsyQoyZztO#=zVGqEaKN+PjUY#AK~?%g9Z8vdWqUh9;&v3L zPYzGT$@F@8WQ!RY`S?y-H}=VIZuMjW1(#1(>z~eeXf7Ld%fVf|0cTugy#qboM%n7H zetSIo0TRi@5-?R8V*g&stE%ynOU)fg287eWgF z+eg5{s2(>4yWW znSZQJHAWf?=D}XS8hl3zD&;+-dx7-D!6oGhuhA1sJ$??Fc>Zjm-l3;vM_!Q5$f2lh z<}xp7ahkKFs=M5QX){D|UfHmS@pDbI=P9uDHc=i_ZKLJALdlz7nvy{8{CF*8Mz?%9-ok$s(*qM9>mNe{yC1tUFh4X8#$A820rmUlLx7b zr?oXdYZdaDpEC|SHIz;MK|K31g+XP*M>J1vntLNYbMRvvWcG0jUq!@|!na8*lxjqt z3?znmt&cC8x4khO5sP~1^i|&4iam%p#`4@tI=?XJd#nd^Jc%e2CBk43dzH9{*wFNO z(J9S{0Pe(bLXRBX_Tv86Rd2_=gk*R(UKjd)+_lwBF%2G3-qQEJ6fBWil;*V-f6ixC zRrGW}6C@8I%FF%mTKlq-|E^@8)`xFBJD(ZLNeA|fGBHRHgX0Wnnd*utwL7Pog!SbSv_!@wm5&-bq;Uvj{(DGMxMG-M?$f?!*<101jrGj)M0MZOG9~x+1l-P?zV}&?5Z5ay zrt7kDNz_VQA=RC!k8_oq^7kgG4Y$@t@&|G>2YS2F-DKHW)U^+;jgd@#@G&}HyPi0} z!q9i1;m>0=?FM0!zBqhkg?xsTu`GZ)z^vjjU*M`0V(efyl*VY?cgMVo&yS{Lv9X2Z zSYZKWHEuWk)EbJ@_Sl8m2*uXi4Id}39EaQs@4he`Lz2MUn#0&GGB%F9I+pBfyd)QO z>l?R}rh(L6_cO1SSMZ+mE;MiPq8nVqI?`N}g*&mNZZ*82^ZCFw@s?k&EG+#4p=Yfn ze|>A#)Z4{#2?SS4kCB*Iww2z`Elgg0Cl;`6Bj+X`ITj%-!V+)z6dmy9>0KtnIU}LU zH;$~8PxNNim}fKjjke#@Dz5~!W9S_WKL1}W+{(pvx#9vo|9UYN8_W-~Rnql|^!De({+?Sc`r}To1$(3G6@D(7`rW>#gXB(34i%+dfGI@z?U}!7`C;P9WUJk=)D+G>H zgwU}645yW9EDC>ej*s>{QG4(4Dk8sXw#3>m$cIsoAECV2d*KaLl5c87}nohNgemH{Wj@2_^dhekkX`flzJ?4wF-@rpR9XKbIX<+zkIlqsy^Em-e|2qs1{MNd z%b(3dtSAuvMcdV2ww^atznzST`$G4 zvDkZuauTGN$MN*j&6~4L)bf4OVVC6b(H7;-eWH|-(VE03F_-=>nF#f()tq^^duxMV zWL&l`Z8e!sqcg(&dwLlO29~v7enE-m)YkUw=XJd-d47X?<${@=fpQ<=quX`XQiLzH z(Np)uC7B^2+Zfu4!8t+z+thz-tGLFKC>&r9)HwL0ezVVNkqu3R^1Xx2^Ix`4kI=|z zaS5iybMkv8vKeT<61kq-PI5J5!O0TE#wEv}P{Fn{4Eg9A{+4QI`dM<7j6jMl`o;+) zSMe#vQDs!R3m0YW-Is01U1CQMY~4_%%8St9Jk#ejP`55pI--^|%81iMN~(AVWo|Kd zOd?K6w0)GJii;Zp!o$#;4dS zzscT6yqMf;wZn>b?x|gn2~|t7+i)5lD6iblCBpJL(9j_(Wvf)`H+t$+DWJ$d*(My7 za`|EOV$roAgIw(R%=J6=V-9O>$#jg)`LTR38$BuomU)E-H4y&vbFNQoz7 zqo5~nBEP%W=bT_`6goStq0Pk~jcwA75{-M&kih2?kGgVj~;+mZQJWeHcO4TwTt1FE0 zaYD}$_PW-^O3$U^t7I3>7*`QlRF>Adh@0UEcKA@g-GW(!$1B{qp#1(gF6HN^Uv92B zt$X<%H|>BOMxFfkbOHc1fa?0J>*7V+n6m;zbT z^{b3i({#A1v}UXAI$E114WD((M!7`og}=ko*;8LOO8lT2SRN1GrPHYw4UAO$Y$Es4 z5Wc!E3#+8xCUll##|;XvEShW_G7ah9fjk%P`b4Y}AoTudLxJGIBRYfxVuSwJP+l!% zw+;YH<_-Rr0F?}iN@2SG?I0Pr+K%&E)7l-${{IK7p`8(y&ufN38*zKe`j+XgUsE8KlRWLZH zyPL3IGPHRj!Zd{6vt2epu2qLc75oG_=stUPR9C*5#tU`c-pIi@`kTJeaIlk+{4Lhl zJ4L6(?&kIMvqRp*WHPyNVVf2j}X)+=q4hlkN+Sq;eswjLRj^?5-mRrMP|? zKlSOcq0J?cLKmew{(1zDs7}=fDjo~8|BTO+kA!M0N_EzmBebvs&P=%5xe|oCv4`Kx ztIwL@D9y{>6gNogn@Ld4A7)flTRPs++o5>ZSn}j)mFG4?n&ZVJ6yQC6 zE2wbC99;HArhK;kb}EsIF06X%^k)~AOk0^kIV1H>R24EgY+R&F%3~~@qo0Jm>Vs*U zXU|sPj=eRv3Rqj16D)5HTh&Z3ZgW{n9=Jj5<ufEV=orfzPU#gR)D1_#(SOvEe_S~mf)e1RZ zSg*_SY&{K8{z%XUr&}w(bv|$2wI@KbEx+JI*O!{g@`Y~37$$vc)@<2kL`VD5$>hPc z3hRqElI@yfyn`~i$9cQ-akWlO3$NbV$-c__057Z|E%_FNQ8%3ChV;j~3Q@k>bUOOb zkyQpom&kC9I6p7{sq;kWYdl_>%$vxwaLT2rQ*tk*{n_GE(H*rOn=_w>EAQ@4L<%*D zaJ{=|XkhVF;?!zlClBJCkN&2|r}3`969VfwdyCJACv z2jE>SpGdOW=n0i}#i*KM*eDqjyJtv+ z_?_cLDog(F4$411Y<&KlnQ_&x6GP`<@*n@klJaUQx?xKb)D3ZO23Z%(Sfp@=&L2D{ z=*7n)j*#*&d~-MQ4wOf)ej)C@ZQBeI9x#I&dV~A9qyD^<4ph-XLonOxqt7POglj5c zf5LbLk#z~_UL@zsrH)xRz0ITNR%2u<#^vH^(mf?c?QE2^D$mSVZAc{35Pa3{;zH0x zdHoc#SSQ_+NQ!8I;PWiO#Tj{nIMNi6W%5xdH>$7s{4pLO7orN`EAG6Gmxfe@FAuNi zmu$=@G8wz_anSOYFl=t2uxT~zN;ag?-Ex1BFypPfI7l-(cWt0&qgBVWBjdS~ z6zbWVnfkD~uaV`8i(mGzFPUBp!yrRUz9bM`{>KqnHCB;C<}jwDm9{Vl*}N#$R9tJj z-c%}s@9Doc4|Nd}8+JXz=V7@N8O@!+FxOlKwRkfke9-e)q8%a4gG->RC<;C7+j+#8WuTg83IDmH_m*%*Ax zzXDhb0w^er;fFfIf(X|vr;sF(EEutr{SBwZK&)jLl)h0Ca&vPf^a(@i2>AP!^ z#$c!&g9iv84gM(eA1Evo5U*z~{~HX;5(2`o4AMFE^^eiBTffy{NeyA(v(R?Gdt6Gc z^PcI+;vYb)q*#h|3@l;r3<2=MA6Wtep8a4%ve(pS$=@_h;|nmQC+EM}$?Otjue zEr^gu-}C2?-byopPQE*?EB8iIvHw7|p-4`9=-%pmRU8gv^p+<>Vfv`gu;E$91e#Ya zzQ-~dSC29XzzL4pmA{GQ5szsBqeW!azd#P8@+LU5ud5f>hpruzvntn}@WHZ(3?3dn zRh-@yVwy46@sv&H@@J|JDK??*G*goNyr+x`aX#k4bVf|or=s6fF71-n#asIhcb=}R(61x<1>-`-s(AL2lkr=B1d$YT8u)f6Is$Wv=Rzg7PKXN z()coKUq0`*7g$WL=5_abTvN|q8s8jl9TwPPm-_g4y4Z1<=OcxhGPF#?WBJ-=D_-JA z+g*#P)QGYBf_-#^lyaxVzC_=9M)}@Q;30czV10$L}cUBp;QJz^{JFzxXTQaE*VEbmvqxHUER81C{n41RW44_X~pV zJN)Vw^cJ8%hY*1OBIuw$5Ohb1{vE!BaYyja$T`R_$TJH&?x06T;(R&I9AmQVnb!tZ0{=KLL6$M10kNO{=0Sy@5&t%0+0q5Rf% zUI1Ls9YFel(O2K`Whh`ck_N;KBJcQRq5N`yQ~^lg5Bwd!#t#S{zcv)W0P$ad0Du>M zJt(*qeT>}K^Hpa(?*!-xfx-x9><@moXrffI*+AoPHDE`uOFe&DneCxAVG zWe5hO9{&}LU_HPM1N-h5(8jMH)4$5{OLzYQ);rR-Km!j!dPu}y_45BCAU#0ce`(Io z5%2#8=^cW+{(q3(A3=J6{`}ty(gPrA|K~w^0R9kwSp8oB=^_3B=z&1J|8GHhg8wr} z4?raUzY5YjG#ftwYSPd{yqXp`ULV53fRqe?sQqr+WI=%Sp_P*ZZNXtm9)wySrW62R zB$$E#u6`704@urAiziuY}*~u zECE>I_mmZ&ML$x9#_2Gk3aG~#(AMwtLlXqxh7OB3v`T;hIxGb^IsQi-wjdDvkY)#f zG{2`V1F+lgDF+ZBe)!Z8dbpbi@Ztmoc6kqJ&H&`{M+yKI0`r?gstW+_{gL{|hyu77 z;2Gq*S^aKH4;Q2Xc7NDY0DpV+4^zNl^FQhajVJ&KIxOKkxE)Z8!#p0q$M7Qs z+FmGtGW|{m%rRg>57V9iuoz51el?f4I3eoy^9-@Ai9r*iavETiU?|)K(U)jzQJn8^I`GTb#{-FU+ z0L*_Vz8_~X1Hs_`zAOX$u>kO3hqJ)R3oZ`M9u9WqP%k7O9Qd0BJWyK?4;Ob4etyS) zp7A-m*>FPmfg>0!JuQGgKJe>nE|%6%b2E#}Ks7&$0J0udVCU>43;w}|v$7%x7y<hyyK z1ayJ!*>5yK@YDE>h60hnztMz2f&ESck-xvwz`^N1G~fp~aQsFC!;XHVAz;Ai5x>!3 z2;j_!-)IOJI3R%k{%IRbSO~-%|3(9G@4wSv;IQ}Kd}tx?6aOy_0S7VGztIFRc9B};NZaX8%+>25x>#UpkeqgO%M*Ew|}Dv9MJ=qpy1K| z5JZ6k)o<^j!6Ed&Gyn~Mgl7~I9DaYxhoNmK^if#}!9a8H-*<)J;Me>c4MRJCQz4G# z69!G}f8IqPfDS%F69Ns=Z}|}5(IWq)3BW*{`+sPF<9nnIIH0G$%LNV-#E?%IG}-?x z4-PvbdpH~cx(~nQ1CNpTohAevA@Caw0sKBfLmbgb;P0&*(JMG`lKheKknp4Y0s8#A zp2CqrAhsX;_s@3;7XbD~kI;}u+a`blU5fv{D+mWq4)~1*d|XFpK*l39;iJ3&Zu618 z1U}y*Jfl!YzEwC%2smQ@H(r3#MUIpQ=<|{G3V}yl{PwO8@X;Khp}{j3{@Y$*80ez> zMgt?=f2X02jCF8fVbG)a?OpVdZy)Gg;3$pX@}W_%BXywBNAdxx26{mMsROWIK;-N1 zG(a%F)8I$HEd(%x9Vrh~q9gAL9+5o)e)L;Fz|r8@7XRfBICbQgKJsugvvaU=JNzR| z+IBv`3JDOGrRD4lx&(**hN6?TGjQrMHAW^abffhj|s35^W4ua4~20bbq6%-p3k))&+6j6fY zpeTxfNY0=l85$%9fj5@-tFOL#^-|Tle_qwQbx&~)G@HHmnrqH6#~5?n($Z92yL$WT zMT-`#rK%}wFIu$Z+M-2^2Uae}cZ@Ppe&dTQ?dTbrj=ed}<-DWWB8~Gj2U~lZt<{B{ zSIiuptn4p|3-1>dmJ!-%NuxP9$%=^B{pTly?Hw&d_NY|r<0`8h)byPeE!w(){PUMZ zR2BYKjx$yHkgjX^K&y-ECcTb{Umn$~_{TI4X43-O4y{wZ|8lYR(xcp3hc^ASIks{A z`a?%g9#p<9v2592tMaI8j~+S}sM$jaPIFuM);ew0sKL`1_x&9E$ctq!A^&!D)^FWP@-JHsuUtyLuHjj_7+Q{x@UTOptrY%$)6dU>=CX)#p()KO@odJ+ zzyJQbtYYBCs*PgVXJ4(Gy^>*G{+MRvGt`#nK2pM`_v+jsH#fHu^V9_E+Qi-zA(zLB zUfH%%VYj!6L{=KT^%8UL)5TTihI|%EdtUBbWmETJ|K!(L{TtWEyPP_6Gvm&4GtN})mxftC2M3en-9}8$L`|_C z5ACgH2+GSFYCM)W8MH?uWk5_@Kuk=#$d4@~IXSt_y+4U%a7~IZ8ynkNZbd_*Y+B_G z``o8dKJzj$IX@p?TX+$~cVfF)=|joS5BJ%G%ez%Q6_Svs2{e7Rszs?fUPGM1{^9oK z(yG#{9-S3Y?SZDbBcBCTXf&FN%Ze>#3oje8ou4;1bBUS$!&92Q8-ZnB-db?u^C|NQgM*DR-Q3bjIMAvZTKPdrog zRpQw1mkl{C$&>`U<~IU{nGV_e_!QFY+l>dCGb(y6eN)VI8;#$i8NQbqzWTPr=Q%PbBZn-OE;X@6tFLsSx_;5S4+GuWkpt1VJ>=(JA zZFK+QfE_!0&u=52G|0GQ_p>DE`UkYR5UJsNS~^1E#Z9G;xa_jD_2Z*L#|^=tp?Ekc&M$;PeZ z`>*Yqr5J9)M%kt;+=?yFP9;5FzipePtB1#q>CtX!0lh@i+~Id?Sd&j0*fyqo=xIuS zelpkfa#4MK{qW?Z^x?yY{Z#_Cv&X33lhO=7aK28XZ@kWk@mBfhQFf*IshG+<50{u@ z4+K;q<=x80FEzhO`qgY-kTp<|CsZVyKBR8$GHJETXMUPd`TC-2%)x7meG#Z9Q%tEH zuE=xv{`X0SCg%}4*;^go-d<ne4KYve;hIMjracL$`f8_SDt<#wLV42$J|Lxm3 zvb;|R>y16i6HZK~uoS7y&C)khT26`>=QfbUMPbKo>{@7M8!+TF-R67#hM(UKBx476 zkq!B5BKcF%rEY_r?Vh6nZsk#P-?ZqjFL$Wy_L=|vC;eo*jr`o-{v4V9K-y&A^JLkv z-Ke^(%`}n{Z0cXijPL&5?eg`>p|0N(6*r!*vs~~I5fMo(nD7zNPkEo|-2d>`r``7+ zHLJa|E5>~`G0lW*l2zpHpdiSe8sDMm%2hfm^>B#`mU@*`I%ZB!J2xiP`G_G=|K3vrXOxvbZ&U1MDkHb{GR`CH*%jOh)`m{ZBd zcJqNdNbYxY3pKmBdb6YYI{$zGNj8;1pSN${vU)w39}0B%5-vl{cOj)=es0nvHGeK! zan@o#605JDpI=c*U9!p0SB8g?ilJe6^(@D_b>Ciz2%UZNVm%j^cSUl=1Hsc}^OxFk zWh&h#)*bNv?HYrk(lxW^_x$AWuxS3=#MzKhh8gF;Y!{b;`!=LhiI4~47MevxMZ5KQ zhBMl|#gPolCii~#{zgwuF3&&4^Ji;O8hn~;n>_+6WHm4)^?#JR& zx-~8-1g*aI{rh*7EXU3=uUWt(&d)j?zk?h?N%qe$^7!QJ>?vbjwhcQF6*so+AcY$7!#})OWw~faCPnHub`Ojy=JymMVNSIo&S5JRN2LV zfAi+eW?URfqh;d7-;4apQ$BrCyEr>BSd8=$VB#}Zdpgnn3sE96&Ua_i?eA8vCGKR1NW2ixUx~`o$e%v36YB;*{6%whY z(m7=nmB%JKfh+gdz0kkdlI>h7>MyrqO~=Q3(rit8jz9XA_WOnjlT@?$sSdVLr-SSDM6=Rba zFv*s2{3!}ZWsU^>A@gzve&qXWvfXC~o5EP&be4N>T*#k#Ioee&9vu@CFg4{$o?E4a z&byYX0&r~fQZ-D<&L5Hfcutz7h+M+0V{OU!y>qKfDo7>mEDez+@&5k92lijTeigs5 zYc89b;3-|e-Ddg4kuUQR5b>Sxmu*q0A9M+#1?+XjF`D$65j`$(A! zSCCPhjB;~x4I|m_KXiUI#8|R;F$*&3#g@#=U23D_{q+%lYhKuJ$%CwOq;!vjwzK$=g#;Z&IAYm-&jfp0s>QvN&$M`sWQ!lXJZb4^aMx#pu=viMg_f<^=|%eM z{8%|14Wc#+o_aJGXBI1~ux_#VE&QR-g2TKmmq~0B1oVAq$Ub*|rr(#+fqv}rqYSI+ zcwfpz)I-+lR`$g~O@~~1zP=4Km9TGXNqC{3YK|W^ygGkNxM=?QndUckobk;{v9}`P zJ3n7%Q?yibQDLbntxGyv&UxltH7~v6rJRfR?_VEr_3wV%n&bbu1p$IG<%iE?ImYQ* zwB@?7nh|8}bA~!_H)4YO(#X$}jx^4O=C!L=JFet#Q5Rm?mU+2|L$a?b?oO!`nj6bE zmnZiczN;8|a_IVoLDhFAc0N=)~pflC+Xs;E!Nw-ajS{v{U3^P&O6jTca?{gD`v@F6=Nyds}t|f zQ{p;1n86v(FfVwtfq&ICKq9AU{h7`X6T0V6kld#er8Zu}-wuuDj0Q-zsr{PC-o3P` zNCUu;!~0cgbCwfhdUtuN#RUX4Agz(_mRSoq0MW@budE9T-=89DOiDz_F?mFvL+h==PIT_CyJX~dW48C!M!vS{8Qx%rW*Z=?QXSbrMzp$Eqz6-mG1Aa* zrsLWQE_7-ai~X~@73C)yF5dbQRy9r=XE#nY50Eiv%yO~_xu&@9qtdDneUuF4imU2- zckOx?Zh^+2q^=+;bN}%@_ffm_610!f)7;0con{{HJ4+kw(x^=~F-bB=cN8eV{*|(C z6QWv!i^v|Y08;aO*x1_i#!isBdd(WAN5DugYoDJkEiDzSbmL7>y~jn5)q8%5Us(7{ zc;s$XR%#Wps&-37O_@{jKs8BZV3!ZGXizPg@{qNd1(!>+m_G z=W;7|q)k1bVrmsKA;)J@bE#D%TJkPhs zCSZ$DkNws+M}E_J`}Z97ltZH>HQ)V{B^G3BX3uFy)h!s z9gSFLN5}5o1?EzBk*X1cibwm8Ai;kI9!8hyTv4i-BzXE|50Xp>&4RmQXlO{6VUTX! z_x0&@iq*tGxSVCnmnRzKT%nuhOY=L=j9F2&v4M4TQRrvf0UjdaElMA1TwlTV zkh0Gt-|%#@v1DmWI;)IRSIruc+`wq>Jr)GPaKC9wF;gF1@LRj1OP7f#f4Pvy9Ew|z zyGyn9$gCd^SMtd>cw?IY%+MDXVoX-CY=|P~!nMG{C4VL|GO}a>k7(G{-JBm`wncu| zu8mf8FG8;jHpL;5!geqeh5;~-BS?b)j{w(_Gcymkxw~6q2WA7}qVC3{>y16z4*Dvv zC!%%rW&sMd>uq8y2nk*)UZ#LazGt*uv(mYfCr@U({)%=BoJ2I4c8oNYhZBJiNa-8nlPJh)43J zKD28}qc&&QOEV&5osWII#~nf$@2%!5nUnwlMyLIrx?GDEJRuj0kudm%5;`XU@QW_A zBjqmIl*f`b(@U$n{i2|Rt{ub^qkZ9_GUCDYS>0jO>B)vI+1fDdvaZzy3w`c z<2LXASwDl!FX29B=~v&%#e)80a4a8*v`6?>QdCGt$VgZDb8r_jdGjUIej7U-zQU=}eAS3}wf5!Kw(aHmpfGcTa0NE8dBO4~_a98z?J05-*DCVtj7{ z?=g!0$J%*gI<`{`zfQa*w#sTYw$rAjvD2Zi)e*xXDarcj4m2&d$y_5IaM-u=8zZe{3~n-9#lmttt*o^Fi}|JJAnG%oyo>KEh{6eKOaaHNB0#9CJ)N+rQ9OwZ0fH_me>dBuHVAhhp#N_m*{ zbCjNJ$Ija_`*!X;I6pt1(rAn9Q4J1<(%N{a)n?`K{#kmkMwnD+foD!LKwJ6!9FO}o zP9+0o-4#*N4Bp>yU*=``_+HoZCB1EIOa2*|WX^4OJd-P-SM``;H-Ey$jT<{e=di%; zKo;e54=B@9Q+r7+IP?o$Dn*89Sb?Sw7p(x=P80n%6c_VQy>tg7Ai12l#>; z`mKF5;yiqehU%UiTH=Lt=MH3$?m6v zGbz}jNG4S8{Gbc%n%YgpdEQ)uN@}eNj=_(DJ(Ja57M!ihW@Y|9B$Z_+k{&gU7R9RY z>R-3--3y_BdtbIUa<3EQ#!@O&qKc=#MqrmuPN|ZP+s!nXIn>+WcK*@_<)W+0qRQ!9rjoWUsSAHYocyKG|7N#wg_n<{`6j7I0`66%8E9M2ar#e zZp4<&J9a$B+kR9bFav;28*Gv^D)2Vy{t{8$m}-%3R=Op1zO^7RP8k_7R#{cGAHctN zWGg49S4IEh?Wb9F{@@oc_(5&^aG=k zto|iZ;W@~I$1yw!jqySBhEL|&Em>S0m3zdd<*CE{%|L(bQ`ICl<%PXm-4xfo`a8 zVz4=z)=QtB_8zZ2%^NpAYS*sm%fe2NwljrtFj?cxY{}x~RZQ$;Wg7VTw?*-FCl}0D z6<%AinVUNqS;>a1BT5)pW7|t?c$Sdhu?Yu}{^>ID3N<<*3ij!zP%7P-^7C0C^Np$_ zgU$NpX!Dg9FIiIlDgzJ={OO^}%1Y~+XZN#SHx{*l&O~C21Gh&#HdAxM|AJEMckIiA z`SBAo1ty}>whd1Roam17Ifb%8u-!O#_s(P)y+qy0XD4IEQ2lyP-;g06Al_n8YMs(V zlRP~=Yn~oip%E%}6n&eXvht1mnLa^)XLUZ!a4i(kp{#O*b3alOQ5O(Q<2^C=Wal=7 z)Y-_kjOS-o#ZrSd4V@VpPsUbA=&MFeqVH3mx?}vVn#LRt-sO@yDVU}6Y@joAJlJUV zQ{6x7cS|^YJ9zWPjRTyyjQaFr6O*IHZKbp6Ya5a$XThYDnfx=z_!}EQwq%JfS;m-u zEn_RscZ+=q6^y(xK+q42?eUDmH&`h_9-4)yMq_;Iy7XEz!jOJOxH0(sP8m@64;h1Y;GB1xiaim*?=?*_5*2T}#cy zCJHm4NjWXi*tXcq(-W}QizKn3yvfK|xq_8^xmR}bZfM)f`#s%)sS76GX{-yvm1Cy* zL%~%(*Y1F5tl)Lw{m$99foXcFJwKvH9-V1da?q{8N7}m*%qX?evo?`6{hS;eLewt) zKo{VwSTwwkdVXU84BA3oSb|;IKuPzZ2rxo5ZYz=4L`B|m{& z?eDJa0vDL;VSG5DRmA*?m0Oa>cpop?0i#^klS8?qnzutju_`DGk6g5V0}>kS8Wt`%(JavtT^9Fe}_JtBJcG8}})&el&FF zZmzBeTIQ#W38V%?R3FT*!;#-W(!=afLD-5kU?)8!iSiL#;TZj;QBGId#T1*bPp+j{ zR0tP|j{OWe@i<0l;!T)@75%5AD8IgM@XREypsZ7uKbObAeu(f|RS`>iL0l75O=td& z_L(fVHTD>kM6RadCbZE2Nm^A={7$xF-#~9N>#L0Mb2B>aPS_y!dGm_MBrqYBzb}6S z!g_q*a`bXmj=jB_f$342k&O-ZY)X(`_8De={Vfk+_c$8CTD(vWML}i_@T?baJggqj*XOx@VQw#9eYT0Sz;WhikQp-7XiU3N}mg`q&3IIW~`?YT5&LBp@^Ol zL$~3#E6R1G2S1*ou3}|91!OGgGw(U-G=+`S513uu?z6BDTPJ`^VJ83OpR6-6)Sf&) zHyd_HKtSM_PW*A`VG&h>O$WltQvwt zlp?tY_Ry!lzbyv}aB9?aWQ%52Gw$g#LF3)Habp|ct~gok)Vp)%s}YKtX$2(;G#5Z! z6|_2KvS^%DfG^7ZxSAI>C*Wr2TGw_}pw1dTOF4j$Zpf_eZJxBePcLvMK$3rOyYUbnmGbk50Zgv)@nA zYq~Rpb#TSfrH}dp`A%^8tq{*~H_;)ISL-ZYkJ7SdtBA4fzqB2qi-8&v0>82!S)(r{ z0HMPUM&xqC6~~{S&O`T?$yG$CI6kg7zA|||W9VUvMGX^zxO0yx*)x7x{R>LAGbojs zqj5h+ZMH2;I@I1@bxSSHMrw>#zL0$RSVrahHjg5; z#y3F^>t!Kaz~50&?G5%O9BiYw(yWVmiOV@{wmaY>ABkJsE$ZjqwCU$?gie~}$qqm| z-00Z(asJj2%Ex&w%0PE(vK`_foXfxmw6&9$yu7?Wxz%=lASNQV04{LeJv%VFch=>{ z#d`|oxpNcEx0rXd@j^xYi9j$l{JqOVpoHvYvOq*KI&UOp+u%@DF}s3`tD}5w>ct`rxk<$CNUz5L*jq%Cd|1%Y5$_9^d=@Q{?j< z`8Cf@7Rh_<+PhZ`{LG13M{w>h?(bH8ygOiFel{8McI1y{8bcTH?~F$$ksGjGQX5Jw z4Q3P&H+^qcaMy@e$j5-cuzGi6RhK+4d7VhX?`UFeA-oy5y3OqD{AhL_h?p49%}&q)VeHksNUsTf!el}S z%yu771Y!mcxetj#Wp;AtG%!hcR|MdkvDdiX1AZM9FcGrezcYzAKU_L+MN-Pum0p<{g5Y)=~Z$Y%;$6TzX?c9Bcr21=eN0kyS8Gi$tKwf zEmYT$;-OPu^$AEvXHvKEg#6RLm#yhTHokrL?kPwywecD|G#-f_LJy-p>AX$JI~O8P z5;CL!RKVN*{<`PRJ$Wo;mq-8&a{Td;Q*G=?XI@`u%J<3vm3L7wqzw4*p3;1#_rjb@ z2;1%}eCXGJcMgL3)PuyNhd&RsK^>i5qG8q_3d3t|yk2v(^`2cJSlaKpZvoc%X3FZ3 zz*GN50!LbTjEaOndjf9)KZT-lfH}sJwg8FpB6Ht{4NJ=`Uha(#7NH0YI_)KE;Y9YCS#d{^J&3+PSxjQDaVMGOj-q zHZpkn^eOpnu(Z7~;7R3g&DZy?x_NlmAPu^AN4V(5A7{sZmWs0AZ>WPFit|Q3q7218 zqMJdga3eh|pgjul3AitwfaGun_10Mys)L@l8UkZF)DT^Tm|8`HSrDXr9@T;1AxR^> zU6%F?CIH&(gdJ&Uz{*u(fy#BlhaR>O#Bz^Q;Pgu&NZB7ge1H&e%za^Q5}aYNa+KVl zCd?8w&y({L0hM8e(t$-nx|CV}s{)zJdE_T`v#{Y*WTc))K?BR~h%ZZ)US&x!FW)}; zanqH{i>?M+g62GY-%{{9fWdTSqURKc@hoR0msw1YT&sw*9+eayo9@ zKS0@( z-pre9^FZL$eY(G!lc^FO+S2T_4A;B5@C*@jhEPSvj~|DnO?PRITQ(mQ=c%4#Mn zv6U{`@-E;!`TD>2RjMkt@GV=tNlRBZ7&k7KEl7S%?3T~M+-Z;v_4sF~S&V&E?uqE= zXwXVQm969hfw$hjf4^5ipf6uI=zihsd&R|>J`3|K%vJc=)v{=m!%44D|Nldar^|}+ z_bf$$=eNP{_bnf37;G7_{^9*ffD6%fBNl z+6|#f;DcR`d&P!%;=W`ZM1_O%Y3 zLAq(lhQYSHvxucn-?+>5+*Ef=44h&wzG9Ph^FRX5Etcnqt-S^(oP%$oHYt z#UF@;)wA}yg8FCd_#_Bz*5H0{pEkGa$v$TyzCP6eh}6WWvQevmKsi67{UYoyy<~yNmu5#<$Ill40#e&_9v(Tq^3T+0-EeTZ8}`YFA>!Jc+Nh!{)lD(^BdvY7$>40 zQj&~vPGNy{5p%@Ij8`r2p`z>#NN1#|XUNN_f`+VSr1Zc7Q4@W1jrgKt#Boe8fB#r- zHChV&+YgzB+7ctq#@R0VC*BW9tu@}P9fojNOb}z&V$1VzSqZK*b|_D$fWN;>jS&3CA5{* zD_4F6jI4!}1`6p6-bfu(mx!)gKni!Z>`QYQGIoZz33ybAzV-#4-Wr7+|D?m-_YC+5 zk#z$0uo`%d!lLV~0S*c7!Pi0EH{NsZ)GAcs0o}S>Hzz+?{>yze{9ds4kYWaQQ|PF& zGR2@C&qN$6L}_VgXxJn|Oz)RWO-~Z5AVp>_P6fo@$wXa#c#fi#S9M@bC7`*TQxk(M zbrH6U{pE8Y&vRkiou>2?tI7&i4pCN)1LyGXac-gdC7^Ghc@r~@r~)KmSBSp&Z;)9% zT35Qt*gK%sYucgY5L;_8KFkr{oN@o{^%Zi*htL}Fl$-&1En^2r09hR9Q3Xg|#tT%d zxg6j_*BJ3Y2Ab0PYKWatnz1m^Y>y9u9b+vOLLUS1zbn*&qpcs>B0C+PWXEk-IF~c0 zAyS!U@Z{jVwm`9uQff~afIJKHIQVS`fgE@pc}gtNZKssJDzI=_Agc5fkjushdwB2Q zqQLIFRrh+t0{QWb`Z349bIclT76bG=M??A?#hFNpB=Dj3F%pxI1^R}+7ZItY>q~^7 zNV1$rDZGu*gs!;0d~Fq?xV2%nX7E^TffTev6*fIrso zw-V6lC*=*l1`5Acll z@837bb5CE+#`S{q4aDG=!MH&Th7+&xBqhQtNX~nJT@S}rOVt^^Ag7K-22gaqLXhL%%eXey3WLr%hQGAEM65z)0K0v242vgEQ99dcRKrm$W zR^b7ydM;b!w1FB;H1(U14&br)`jei1J&vzw?XN8w7|M@r509W9ON@{h{1Mo zbN|wHmP6IVK#^pRXH_$Nxb_+|1qv2aN|KJcR^E(2dyiH*hBOcba}#0J&$Lz4F1_D} zELu{>5Bj6@6zUYB>s|7A$v}qS|-V; zZ<&AP>cNez=HR2RxReRalfFS=UlNvl2IA1BEnBWA<(Ro)9pD40gzP|+bg*1nL>YxI zil2HrAfT3L%qY~B_$KSM3)rew-aFl3b_QFK*wbUl*y)Nng^=-z2)XW*eY3RFe@W!Q zZGXCIAz{0Q1+<9){gAhxy&2`W<=89}e!ZuMmr<(#w>xy}UY$QeB|^e?-8a^?0)Day>p`O; zPCvo$YxY$R+-Iy@#f?9AWih>O8`^5BX8o;3Gw_>q8hZ3i>8EkE5TBRt{?d+zMC{&| zz@QPQUA#0()C1VfVfYzv;QR)*c?>EV!z~nbCy0U_{Q3LI zA52^8ck`xJ0pTt=72I_RBL-le0C`jZ)Cc%Js*Vp+58G2W=ZYPyhS@?3z#z3i zpy`)d4Un%-BA!st7}(KPmC;AR4>&zrAiEb0Jp2XH40Lse<*p29LE+rAVM8?Bumt{) zIS070;}8ro`+)ryYN5D{gHp(H_CnMLIzHrH#j9=9XZavOT9$ z6lVv-2jSHaH!l-SRzLm;?7aFzA)*WtCkw%a;K6lJYlwbJ(B?j`?5!0Fli{Ruh2ACi zfbdGlI6)v0qLGvP;pDBue9sf)i)X+Q{MH3{9}sNl2&)%F7l(XCRt$V@67oq7M{nxA zpA#^`*a;$mU)4=zUsYjL`VKB?(xJEZ_pxvs&;pbXC5a!!=2L3`)0ZH`h=oV#}ZUf64kJoa>Q>HI-^B`ibhKV-tTtc`y}1w zdIVM%cVHSr#ho0V29ReZJf|FL6Lk1ua-#X7Jheq~hYzc`0LB%LqztHigZi9^VTeZy zckkYPNb$SgipV15b`Y&Q%W(x8R}E@pc++Fi^M4~9M=x8mBz%!K|ez_KP09+*}9o zZTRpWhm1DTKe(6^X#wap(70uVi+nc$mxlM7fFYmbuV25u^~Qxn^mfT(Ae3~lRUyl~ zL7g~RXEbL@8c!cysxw$B`a_9QH1f}gNe#Yn z0~o_muscPEKn+X6DTW}gP3k8|jN)5;=zaox9yaLhKuqL9FyzHFR>*EA<|%X&;0@L2 zX+(lcL8{dPcdkvGO^csUh*JqpoH}rO#OwID`r?HP#2i4a0+||)4}ik>Gl7b(cd$4e zIdahk)s@QeDYRZ~()a&ID(3R}ec7%h^CTp^e0wiAw$a!jy^2uXG5xk7g5YX`Fj0{S z_e>?&67zviahnOCQ>en#A3yR1|M>?~n)ZILBq^6u7L_oA!Pi z7M(ONXUbo90RMmc_H8<4*|KF+IvJ_73`5Wzo0$F^d=N3tdC!fXo}QjgfZ@6;z$jLZ zEgVRqfGigRWlxhPtb11m8hEfAf4otqZ7k*pSAN$&bm#1K{OPyzwuL~^2`8cyHGPOw zi(kQh+(|sV(G)9eC*fu}hi_NG7nZq~tXj2-5LGmQLeTYZpT2+#h8~Lw%_~cW9TIj( zUwVcv5IuY#g_wnq_i7R9ef-*Z9|VDOtmWr-gPJK-mLK|WVu(l(-N z7W$3Eh4(8#(DcEfD9P{ud5(PxC`qQ0hPFTcN;XjO4tq-^2!19XZYgj}%eLk0m!KTW z`poA-sZ7LtXo$1GsTa?%i}~TRA`q4^!FEj&AkbBSpZ?ehfns3bS6{t$tsch10rTR3 zDilA=eL`0QMkJZ+U~~;q*G(}+I|c|qDj%uQ5}1^fv}@A+kF-|z(umMMxU3pd3C%JU zDR%~a?-(-MGlJbfa{-Hj&uYGcusLaFU6-7T8iakvJ zp62I(>Bn(9iGZf?q*uScwUJ1<&@WC9wjONW&)#0Q6JBnE>GeN{hLSPBsf^Z4lyMq! z(jY9p!h3{p&Yz#Lf&4?oWoxnYV@MTY^}=m~J`1x^q#Qz7KM8HH61`^}NvqI%`dYG5 z-`Kam1f$cUqeP;-CWYPiyeWp2CCu8_o|nNG|igIDSAmp74C=+MVGj`NO*|`ps}PqpfhT{~{t3R%ztGZ{m0T zl8p_h4WgRk`~XtV!IBEVUt%{%n4wd}YCFXu0M6Cvq_z-d619cBnFl}nrgQ_Fj8|6H z0K(TPpiv?VVaF1*C)^)-;PPQI`v^`%7^!G8pbatpf-T|f1FAiOt)vHan5+%}2uZ(> z;lI_l_)i`=pxP0vDL)D^l58633$&~z>8du^QhRwSxk*qZ9GgPo^_&-=L(mmF+Mgg1VUrQa@L8TiN4q2z6d z?Jw336K5h)?RG`aOyaF8@F6)!1J)J-$*Wr00YIL^upnTRokncO{~F0aXYU0bl1Pli z7@@H6dlaHul}&P9JY*iZbaAJy<79vV+l2J<1P>>oFdO81W?{~9FSQRG8f52jIJ;%b z3t*cdo<=kuKu+qT?s%?m1$S&fj5nyxkqO>;%<50ipyeZloJuI~w!9o+zLo&+Y|x7wb5Pg9lyn^Y{#LoA%Wu^^4|#5+~~d9M2J70F&Bd zF|*t7N=5^qh0XOeXC$NTjf2bK$H}D_i}q5{Ytktn-oLj* zmqI265Ro0a$r$mlVmc9;9e`rhY_>w&Z=+tJ-XkOm?`56g>Yj^5=A>{aNqgJ)f6F=nlxIbFQF_?XMzC-5hn!AOW7 zuxrmZqSqj7Da~@O!%ueY-mM1*TFA9BPBLy_*+>X)Oxf;5k{KBu4ul7xKT5`__(e$7 z_+qhu=v5)wyeA2v1R5^n1F`%hI)~=l2T1}H0fCYWBv>sB>>UgdZQ0PfCvHLUGT7{=sYD{ z`G(7i5ss}zkJYvc8g=3wLX@QjQM zVoZoo^fCZcKamzm>uyp+8sqk!di!0*8`S8P>4Cyn!Zmq&dpE(MQY1c$!J$}`pY{ZA z6dB^R1T)?T!x}3g&$7Wz7lAB0M*tm3NjSg;tb`l7n*PWca#~IQ1lI^3HWq>(@hj&t>bGCz)K1S>d#2^B=BVr=eyps15X=CG{Xe=dVAEgJ<75obQUpYUMX!VARg26?S{KAaHSwGT2Cjpy(i#r4lNKb`U zI0-LI4G=BCOb61G-~%Tamjr+Q)Q>epH$pkotlis;S2Bj9F|fw!GmwVTFMec2OD0gc zqGqzE@(5sv9>nf6PymEK!QVKIhy_rCSV^UiBy-D7OCwomS#2sQxn;juuPx;om5;t4o(T-m!GRqH65hyzPKu62}Xpp z8eqXfDvc1V2e%YSUOTvD1lm^*5-7>NBr-v3%g5vZOI;Q6UANMd2i`I=V@1r8pbDBg zomP`g9=mSwHK7~ot|aJB8E*VH9e{Mh3}-ADp-;#8LsX|7hyj$ZtS1`hWdn{y_9?cA5Mc}?)~4;-A6p$Z0GTZTeNwq}*m%rfQ5G2#bhgMbRq&FfD~-3 z8UiD5LJT>wNGNqh5wSEOY+PWLYr{i$7+(UZ{`d9kI*t7ckz{a$RLD19Fv+8bWJfL! zj$^>9j}zBzxrvI--=I3enTP$phOprW{3&T?O!$05k9&_H%H2A%P0o zwdLv~{psL`w1I~G*mANZHdEk^W06e~QA0?B@=!=W8Y5Ra#ApH?Zx_|P;cU*8B#69L zk>>gk#Zx2DDOzE?1VVWwksv^O5U^4qKk82?Wu7DRLrtZ)8qw8m-?~*vBo*M(n!j!QZt(B!AEGD43EUw~5jB)#G4(49D+XLij89*T*$5&#{L!RgXy$ZI6iLUh&_+v$+Sa=Lv zUWO7c2AOF>J*U!PoIpdo2fb_!%T+o$%7n@oRfeUTOBgy8L~7@722z=BI&pfGkwD!} z0u`!#8nk#aZc0*zWA_)k$oWP{I9`*jF0vqzjs@@izG-oWUGtvaFV2p*8=AEeIvQC_ z_tCvpu31CIm=VbP87UZf?SWrFxcfOAAk}GTGN^RST}2lb7E(e4F${-^Oqd@!bhoTu z&qFm|D0=sfLPab)C7y*3(#@@PR&^Fh{@6sZC|>WU8AO%@++8n7s3Iqtue8Mma$6ha zrR;$JBtV(8wgBm&E*sPD?F7V^tTe-+S-g#Y$W3zc8+e}LL>XRL6Huwu5+3W|*-&>3 zy%Hi~LQLJ&drc2d%$p#h=)~d2AT!v)+~fGmi`39d?ZV7;Al8QP0m$Cu!H7FV-x&UF z(jAf-3cKWHPR_w$%aya1&2U&?USm#ooCh9d4(A+F6m@F5@`Re}>rW$x2l}a2Sy(q8 zVLZ<-#>gg`jNU3$)Gw*-tq;q4gk$ydS*cO_H_PrJJCYNFju3PJkNz080oBoa-UK<3 z?DkRJuQ%9kB?GAZ;e-;HN04isVO8K%@@`2G+BewLSY(7K(a{GGcZNU#AS`!uE;tsZ zP?-lHVKo4ftorxjIpID==A3C6g@A9)gXu<{wOf9YhICE>nP z82PlD5K=|C;06iOa|?JX2wYZ)?L-K55;k39WMU60gq5+W^Vrc-y~-5L=~l<_`z zm|?C=#D(R87toA;##}i_ZU$Zm8TSN%Q;Ypn9##a0h80{WR)iNic;U0~6ixvAwq=DJ zD>c8$XA`=ZW5_0w*!M(I0w{9HetT;!yAY?tkcO?RV?(T*v^V5~yQIwpCjaB+NB$zm z{*iHHtY9tN2th2@fa@!PL9lZq$f!E;@)87zUDbPEm8%ld5_APU@vQNnSdvtUSr-mm zGH?i5g>a}us+YiiAY44yYi&qtq*$Wvo`Aoi9yzSsW+R`*<5(PM)&n+OGHPQcI%ldj z-l&gmB;5H-_}6?A zVqA~^7g1Gcgd))|VTZCqa+%Bp!t$Hyg|n`1i39zCX74evnqaWl2A(N{*B5Rg-6g?6 z0&$9bGGTeEp{6x9(AiD`Y>Ax_>tBF^+S*_=Bri zSadPDfg$eDu8GgyubS58eLl0oPpY&>7>~BF4pvPJHEcci zVGY1h3$bl^9{nh}lP!e58*jLHkKev6mv|QOh~qeZ3>wJM-mf^n6;)=NzSPn-*wK+J zs~_#ZxQU%T0c)Lzj{qM&M!_eIWPD~42-O~v44^C!<2Pw-k$0={0WyY~H(qzPH**iU zqQin|<2szKa)v-SAV2s7{D@+2*P5*tuZSJ}0gf2~taK6>s!0fvZraekpj` zUa$hE(RS8CA(oifPJUa=7{C4B8&4u_&teQ>26i|?ouSQ7Jam1zJDg=;wwDltCuus+ zBMalm$;l*gnMJ07Let@euq054R#7?vV}v^WrAa|#^aMKKCrCzrT&;Mp@?JDE?mzFV zLWBCgX)*~&M3})1ObRS^{IVRmSRhx*PqimG zyU52gk3QsBmDav?3|AK}g|~)6#p!P)Z_?qN&%*&n1dHNaE6$M} z8yy>yQ2nbu>1;^fJo%l2=J$DzoWu1gR2*vf=XcOQm^P=ZCOfL#Cy*h zt5T=Ol6kUvFpq?$-m)bgFQEUaAD0;8#DB2@G{KY?K_P}b0~~2}q!Cwq0k_RbKq~ke zykwK$IY;BCq(4$DN*}|b+aPv`C1YB25;esJ^v>rn>EwL0qJA@k0E5AjKpMYZf;=Eb zlYxN*02reDbGd%|3;IzR2d5{n2Kvj!**BNS^#hH`no_{&6P*ZTRtVyRw1D^%;6;oH zBMdZqKl%z1FT(pdx&UJPtAghM{Q54OYD|uoAnVlzWKA>PL9UvqWQ)Vo*jrZ#zEaw? z>$R%dA~kL^r85zgMrx_okA#`gwl3OPG`Dr4o1T_mwjh?h#;WdX4}R)!=Q^jKDQzpo z?6~Hql-<|JwXS-o{(DbpCi=*tMRi(N-ukh(F3UL3a9K^o<*!9oCC*d+!~Bl7Uuu$g$oqoT#V|T zuzt77Gn{n@;Yjg~+g1`>awG-d!aoWLp|>+RF?B^+%8+Y$hT zQ8Unl*(HW@R+fp}(t1IykP3tqUvT~H(27FHGHB2VMwTY&BZ+qUigNS}+ENTbc|n5N zHtM(J1>W#$B`_8PrP3sDyLoJTDkzX!+FQ_CMM%L&d$xf&fYg{^gXjiIMPdM=X2K!V z@;|qFlV7e;HWsHrwXQrm1wc!oP{BbZtU-!SGrew74t{u{+{o2aO0l@kg%X#n|j28dEKfo$9^HiBDd`<=;L%yZ;h=nA<4JVbh-F^VS3Cf9=HTW(T7!I;n{uiR8 zLJTbItq|yiF+`3ZKq|Y%8Q%ETrJei1GPYDKjK2&$#CSh3NF!5Fj96+(K~pP{2_ii> zXdCoMI}bFKXbYgM=;q)^KuKT#*rg=S2Jvh!2?)?k#DYCWNB2=(zFQ57)kAb%8C#JXUMe2H>+@Eg8eY6_TWrm-zma_^7KwzFxYdKJ0eR`cv3gplSct?zuPC zl$w(qtNyoq{O(2-0vh0J@0`FI>|ak=qc7B!rD# zLw|jGTXzfdG=WF~v=c=p5K_|-?{%c>5RIk3%u-AIm8JKS;%}R0M&Tla!B8gFeI!#^r9O-ibYXHiLq{n*T}~y((aD5S!nJ; zqkC_?z|y;R?r&k10fCF?z1@P9kh{H z2D^Cx$VDFGDgK@*rnF(`2Sz;zJuBNZ51|wZ)`}roWKvo;|7*AU z(w0JPMI($O2@*7dXy8o}f+ryC9o%Hh@J7|8&yBpw=KTR6grs+3@`N@T+3#Rf$85VVVb?rv_Nkfh#fWo4D@NqR+7 zG}pm~zx>DPxeI%XNg7cn%RuO>sk@S@PZqdhez1l42w=Bti!u|Ca1ZhU%z*$HphYv)paf0f#KTtmmgE3wQ6PG1C@X$Wn!`3E6lhPV z>y?33Ic9b$))hQBzws(|oyNp-Gxo%0qeevfpv>yqA6Y7CdW}^S+f>n|PT*gWCX?tC zfcy4<5_LS0+CV)?M;y>I-Nxjtjv#WQS>lfC@u5v4CyKi=G8%c*L%4bpJzv?2S-HnGv#LG3K7s@PjZ`T`m zY-gGXHTc^5m0xpqXKc%}EA>h*Sy#(s&IF&{A1#&e@Zq|s<#)aqyqS;OKDe+qddl<+ zpZOI=*9`qf-8+M6I=$ZxaydBZxMVNN=^r(}RIu4kfyGtMB77m-tw^Ul=2r|qYbkfC z!*SdAT0Xy%CWqhbjW(FE$=Yw}uK1$=r2oa!Y*VrPhDz)1QAdsIcrLsiNI&o1^+)#P zDR~pB_aRuu<)x!Qo1Z*`x` zb-Y}LjGZi<7v=4K`V~!`pPkGq6>*tw7(L)&#tq!jpEC5DN1;fPhBz$(0}+e@O^YNr z-J;d`XDPlTsvU%GfwKB)E}3`#wzJ-fG7QDtSwX@)B4h%xdzZ2QTLH}+kq3}(AXfu2 z+FxIF%!hS*$Db{vIuTF%mP@H{~iPMnykAKqD5 zRT-fQSVQMy{jSKf8ySzCVqbW=K;KRxo|aFEC5JWoY2d>4A3~%-7)r#mkcbb|XI@Pr z5Q|Q-he#TVeWS=Z_GfYTrOgFH>B0JG!e0Y3Q1D=a{79Y*`KvNr%E;K=)<>2Ea3?)q zHwHj(i3B8*4pJ_(D~=#nA~2!nVAy1E(b5H{ZaiR~M*fnC%MtiV{}amIz#mmnirEjU zB&&bENf~>jB2Kkgfs{G6)yG z37(YD2T+A-2AiKqm7)5PYWIHW0Sxx()8;Boes{LEwkBQPkbR$m`;og&o)-a83*-V~ zV*eHR@M`mlLfi>8Je@}9-19EEHFW*osl5WO6Ttub|G4TKfNCVkOdUbE2fm&@mc*>y z>uZZJ8WjGUpHFNl0vpJ~1ntylo)FX|=qWx48G6?3273g=XAsdOW1BGEsjyMKa8 zL>kUX3kx!kOkn0dXrI0_NO}(lLyYtW{@dL&@@x zb#+C5h)7j5nFN(9^p|U)v!@G)CLD62s9;j)ngsOR0P;pi;kB9MfB4tUfD}vmK*)}3 z`D=;B^lqu%B}lb74#7da&uA|ywEb)E8SHDsY%2W6KP&iuGWV>*orLO?eRk|;p$YLf zm`6$B`xkij|JqzNWpY3Nhhs3WKk)v*xgp^`pbkEeMX)RO5j7aZ@*kSUK>8v>NXUdY zbZ9z%5hl-cv=r@FE;!YqOzho%`$*km=O4GLZX#Y#b!zRMHsUs7Y?8CWzF$($|pApZ^vD z(QoP`t%>+9(O*XO&i;!jD8yzuN=TAG3cvXvpTtBw*#r)kBcxsgK?WcnBEGy9Hb%HB zl$1=N-D^*ja}Fa)F^h#TnPxQfsIHyd3QUJAC@CVNf>dD*EIQDpGBjsDqNiT_PiQo#>ZD=YnaBRN4L^I`oR*VB3V2enU`dsh|USkLtueM zpG*ax1q~JhmY840KLU#)l}f&+Y;Rk|`$)GRIL-2HO9VuJv(T9oV3w;FrKf%hs z*+>fVF!c7Qi~lYwCr@|P_~`4#J@@4zJ&*jaLl&>^a{mP**5&ZN1JR7AMuBKXDD4h_ zB2a<6L7)g&A^sU2Z=PM-OS)f#jj7xs)WLft_*H)v0Fb)T#17&-5D~!FEyw~=&`xl( zmuN9Q_w*-sx8RY}|1ay+^#9e6M`iLu|A!l+|9bZSyN1*MdT}Z|WDWj@gH8WaA9kpt z8m4FpXO?7Aq@9H*NFas8zynj#+HGTF^TBPYirKv|Q9I0-f`UN-VTF<^KPdjhv$2AG zh)9Mo>k0NxN{uJw2*yCf(SSMt(#!yr>N4hTBHcrcL5j3X5-4(Re*XIllO@|VQ;S+5 zGUH|-o(2WZ=0vds_;NFT?~b>IhNC+qdu1iz#4LUx`Wz z$?V$0%Lq2&ABLLp|Cl;pZ=$nQn}%+Zj{aX|c34#A!v9c2xNpB3m<19qL0um0QF4Dv zb?yJ&GurPaOTRRW)ZSqUn-RYv1~F{)KUVWcBRymwsZ2aOK-ACrC5U4h z`6A_$A?o7@PM;410vlWO$RpPrg(uBbDz*mTN5nXg2i@tF!ep&`h)RZmd6KA>jCalT zVj-Uwzxkix{M#J9qbfoKaDeqCA5Zi%0%njdSF!{#-qZjA17QN`cI4MYX+-)F)%BF| zzE?YtvB3Ygs4^)W)k%cJBAit!_Q393Pb`1}=Jk+fiUjCY`vD+FF7x}iI=Po_#ky$e zkf;>&9)Y=;{Q+qeQKKe}UJ(9l#a4|0qmryG<4+O5)1U%>!4okyc0r|uU}jKsB-)Hb zym6-sTIfddmZ~1EU7*ekqniXtFVXIqLAzGonQ>{jSb^1|dpFlZ<@; z-#hMv!qUO_UknuTmQ6DU9e~UOAp}2?^%033(E4srxnlVdD{Om1IQ}8l{Ig5@X>qx+ z?2?!sWo-5Tc8@<A!^yd=2t5c&Uw`Pm{sV zzrDD~sS)1?T_~a#j@Cp8OpGMGrvNVrT0|ZzICW9XsPwd0S-y~#UgX@}*vZlacL&~z%h;YW>W3gGkF+iz&ijFpp>UC4A>gZ03o z)Rc^s3DL~_`9nn^NT*wwgEyGa3(bW~e}VDU64|js^~4Fm+}zxEV5GK2pZ@DdXuWTD)~Vcm_;4qBTp1{F z6839bU6?4ynj;Y=(X3dJ3=p$p0{nQ$b+1D&Okb6qqvI6}=Db^4$^~E`$ZY3O3qm~3 zPoF+!V;8a zq4MqL=Vw;vN`+?2z@#Kz#!ZT0O-%-28zkvqA_CF0k=>W`_;Cdw5;0IUuHU%PhrTjF zA)%M3B)fskA9wq9c_kn3_Z*0SZm_;GH}u|HN0H`K!ZjwU_@R`1nlQ0n`Rn%lz35%z zoh>wL|6V*d16uy2I|;hVM9WJ3_iZgx30D5sNB;Qh@?ItPijOJs{QdEi72CFLW2U7! zl5_MmXZtJk!0|BCb{?4vGT8gqH%nP{baY(Z-F@0$^WfTytkkc4FN%eLh-rnfOOFc* zE2*t z8tP6oH-!}I9fpqnEGNu~D$7HM0wL4K zh;p+kI&P9OGwB^22|%$7CBhK1orl{6q@~$m!vjl7-N+bOr}G&QaFnzf-ch(@acTUy!J1j2;9z|YYIQ{!%B z&ivpZ9A1oCX25>$&nK$fylhw2Ff&u+fN5R7&Lc0+g{OTECV(pnb;Do2WQ@9?hvQv~ zk%d4}QIU+tAOjRz`~ioq!~XAUv2S~jmBoOKk1@?M=&K7wc4h;KT#7$>*M-aqnN3Vg zAp28|1Kp_`5#?pPXAc!R7gj$x!T|nl7`MWPW5ySeF zMoUNcfx{B%rFUYLjTl-rLSaH=cJEF)zqs%?H&;zZhY`eN5on68MdL*^8VO%JU5ksm z1Yn*=PL30Ld@qsz>FVnG!%p0h^I`-{+WQ<6sBO`A@vgZ!`1Wm1cMlIo&knqe-U!rR zD5yU^2T6pCyT&Zz=S@wy`NsP&LPJrJL&lYd!lR_L2^^kE#Q$zn!+ID3OCK8>>#{po zO;Cj6WGY`dM_Vw%6%eeQ`qH^&VO-+D@Rh>#Qym_y^z8V`F0{Rh`+-U}+ z;#GJ>Yq+@PTsASLZ`rtUBYGE}jUHv5@DWq#e`M)~ql#j)8(IzFBtur#wgYWffdpT!nTwZ{8G= zm%pQV|4=oDE#(MqHPK_LzkCo)cgH9niDb(|CVF~X@ta{{)gRFg&OwpF!r1@_iJf= zLN2zRVsW+^+?Z%8E6g@<_&W1zw2tzaWN${V_I8>}#?xbZRu&Kd1`383uCWah!+4v) z;o)hvLw_c4jpkUpW5>jCERUTyQ8T;xOp7Pd)=JQ=Uc#}(B_wc{0LJCimhQm=b1kbN z0$*1H=0gquOff9} zvE#>Ifnrew-a=ZwQuF+{9kft_QA*l{qxm2+v+m@{lSg!Qndu?IL+=Yoi53X{9dmG~ zg>8TrdixPbxx;~jhYqbZEpp%D;qGozTwMGH5u$A=6Suq-P!GHM`uYP04@N>O{sy>Q zPtgUC7OdbmBZ2-(gg%X29gLRm~e0ZsA4|RKv9X&)xY-D9+9lDlz?{4C{ zUMdakzX>YxF6{tQE^U1McLo)UN^OY(Gw{PtQOo&;7 z@7?2@nwqk*vkQhj+6>%YIO~AG3$QS|`}&qe?W57x*T-qG0V%@aGX?3kD2R=P#lCzfNpXOJRBg23R=pSY4tTCB#BXM`yuFo|mnR06sgQXwi=CZa9Hx9^=kq<i7jv4phcT65;5Au4#9~v9Q(EZ1-UYv%aYhe+AF?Ff}*LdMt&_=%( zhv^A+8+z4e@;-kB&tVe+lU|sTVe}?K=OGk)A3gNs_PKj>Pm4!3J-_Y15I z<)FmI$7hReynCyH=>zG6NjQJ2^n~`^@bJ~kDx$AlbBs?wkJ7FH+w_avgM=td`8fBIFa|;^Kv_u+Fdo;j3tO#bT=oX=cG%)Z4 zK-yDu{r+%xyNm?@}e ztj)~I>d%I27m}1TbV?)vdEG0``46IvV@-(Lzm0H`5O{%#JOHz_0X~ zc@T!gQ_yu-g-jN$vH?f|h?JhFxOj%!Z5W|unFD`t?jS3;F(oC1XVi0^E@m03H#qm7{bE6K>Z5+W~CnJ#GBZj z-#f8@HJt_lgNgzt*D(ytO3TFbikwjsw~4{k8)aEQXL|_~ z`5rPi&VOLQDmeOP#+9zXVFCDlAXRojSFq_!`5ZcHVa_fPn)#8*pW+gELK&t7JAW@i z&dncZ?PObSD0@pq1qB4vy?9JFP)5)N5X}AxTx##}T&H$6coeMAFZ$FTFh8M9Sq)5~ zXLz_qSXxrD3X%q*koNh87Y6{gze>!xLk0PRt;7k(# z5~0>jTz`0w$S^^Hrh>c>)TFb_OW0JBm=UWyM?fZu6o-S8b27i^>>qK``6>1cY86TF z5oR6|a&jf?`wt?r>v!LcWdb8x$_nA+)z?R9SFIv(ml#AS)zs8VC(OuDVm;HAVoyhO zHV?)0ZuuUI!uVLQIzmiz$zDMKDkCG47M=s5w1dU90rXFh_enO>Bj&V``v?SAqV(>M zm3Jj)kHdDurv2In4iqjv&B^%zw`2p;T1w4BnHu}diOohBIFfACP~PGkd%BPRIoN)O zGF2`JWH?H0-8ytXf2&9;cE78eTP0ZNMYAGzPtrLYJNDqT&WYY{-?E88#_$p|kGC>lcYHGR_3mjV+jf{-qFs6fq!kEOq5k1)#%gPj7N)Yr?Y6D$8GuY?6 zzIPg$wqn3so?U;l9`Dt;tyz}k+(<<_j>9*G2P+%SP9qY)6h3S8htSf}Rv;CYX!!sf z!gYQXyt5lrzz-zIuLp!xg~*WHYMzt9^1VsAv>qNFBwvDNr81nGW!;nL#aLu6w(!bi z_kc1T2%FP`D7VmKh!I@sP#laO=iUsxxj9*FA!uM^q>`j-g}(VXnA3u@aHQl7J+vsh z3=?8MM|FM;qQ;JWzA!spV;z++DDNQt<}AxTv2Z0cbkBhU3>4+VhuMGq`V}YdEe>i= zO-a|Sn>W)Hgfi&Vn^O<&J9zLFLIzhf+sU7uzIt0lJ&y@f57$9fcR9APx{r@6j66up zvUgD!D80b}Iu_IR9Xq0K-_CpzVh$IN3~=}E-K7$yM~?W5ttc{!fRbQ0h@j8$&t>eg za1kcQD+2V3zgHtRz_wS$kV1r7NUYp`kC~rEFtB0o>PPiB%zf@pj)bB5aTnPFf&h|$ zAP$p`KWJ?H$x-vXvNDBiT(r|zVHrLay7A@a<-Ld3I@wXU24`yJU9P0VJ8w){SQu=L z+20&%=x3jtoSnToSf$eMp?)eOS!-14cqAlPz-d@X!JHH>(3bY1a!2LwYwY6Wl+Bjv zaPz^oLWf%035PW1eVv@xJ!gJ&Q^6-T39##EY4t65xx1^_+mGz(%gN1siPS?6{S_p9 zg72yXMJBN*JUx9II?XRbPJy&3zJ$4PgIao?t08?vP*cdwo9hvL)Hk0=dGNpnM5w|u zNqL3W~3ZSuVxR1lO?AXr6IiYE5IC*q1{sY zAUH26!!T**vPDHhURO{oXc-v#=|nTAED};51kR$qsb^pigtFH?aD7TW!G_+kfB$|9 z;T}WG!dL|WBT#Mg9_$2=;;$f{2pV8n8Z8TwO(9+K9m<7de_Yc57zX-5Ogt<=+4H?)yEjeN`oI&Je$E(kCGyp@zYq z*#&y@gnA2oCcj7g_+$7z-?z>(P5`#gaU{qB3K{39*-BFZfdH|41t7pnp`n%d z8x;kqOchwfg`)mB>y>P?s>?IxZ#X1>5%`hZN`iERjJzy#WE22hW~y=^C|AYZki~iEO7!Bc z$HWjz;&QZWJF+2+2U!iFR8jC{5XOuZX*cH@G)gA>Fg7;LT`ye^Bi$?)(%x%gp2HZFh8jzFUV@ z2sWL(Z=MVc_d->Yl=bx1P>S;}9m_~R3wqXDQwtN5TF!^hWCN5bkV-v}sJIcgyNnVP z5<;!ZsLRJp%e-dIc^jbf9rpxs^Mfkj1sI6tlQE>E9XZM2aiR4fE0AYGvpS-9gJxyLiA>GAO!vDV=TFA%^S z7CvilzpeESqpk%#N(K%+@2cMs$&&Wj2X6KnB+Yv@~{|>zXz-pF3hfWO%y(?&Y zVH{$CC5c!C7SuH`j)w-Zus?i-HR${t3O*8`|1!F`d^c&hY4#b_SY6ml4BNEseHn`E zawk+{LSP{(>*}t|eoljx26_`VH+NRT{sRq)F4ZWn^nCq#1=A-1m+AI#<>84%Ofnj8WaQ#L^>|DD{mfHfxb^*&@bBCIgvEg)Yn(T z14l(iQ!6QzZ`eALEvv3(ZOscX+ZBQch`q`un0^7!J(GAXI=avfc-s+(a_y)(*y)GN zuL6DWJp%*5c;Je*whc(XNW_8^?%U`n39mb)8g6Y)2GO@8G8?uYaa%AfxDB|#Bvc06 zWo2z0(9vN=0B>~vafn(`3d-Q=z|@aG^A%ZgKyx^TOXzvJ0r4XN(j8t1`hIU#|7pbq z=qUab3g{aiWoPKC?Yn=dqs*5b47y;%LTI2bxlwmuB2gOIa+XOR9Zc&hoE~cw7{C+Z z-@27gRFoMsW3!J_Q>VxpBXaxk^Jg$H4uW@}Dz=Aztu?0VnSDg0#3v=ihE(Ad0!y^6 z>^fm@&%0&Ia;UJY!2!FWKZjC*1e!cGRMHs{Xp6$=!eGH#hUDak!#vWUiNnGEk?bJ{ zP0dvwK74RRqldiD6fF>vk7d5n;r`QXy8+(9)E2rA(3zQ=TNWNn6(GKgTo|Q)VsTMO zLO4(bCn$Eb=4xxpH$OyA$fH!S*$@%XD<~)w78O~;z5vu<5c!5Z^B&P03F3gz?ZLYR z0Eh8y0f4hAT3dhL|L_hn>0~j4gcz{D)qs2h0wjj=&7IxXq;;NyHA3JHyhUpqO?gp^vs=b_P(_3OK&Wgf1s-Sq;Hzm^E03DE_Qq}HWm0&H(NQ@*SM^u*Vu1js}%ck{^; zF%&CoA*5t&^W=zA@+gjQK3*S2H&vjZL~uv~_wi|jEuoyVm{($ja*s`CXXgP+%fy>; zaiZ`NQsCBfs`WxZ{v?d}oA&nGH*eh%C(q#MSO?hW1%{@W+gcdRuM1UsQ&|}RDC8-O z#}k~Yp}}>$L8Z>r)fsPA9QeD`qZB^#ccrIhWzgUMdm@5_+Nd(kb-2REsslprI^$Dw ze%;^YAMd?L>{oW3oqX%@0$~_3f>H?w#{H;PUAR_wOs>9og?r+z$i-kjlZRL9_G6A) z91YhRnS1-@W&}JR3SL((J-Tc^x57CmH@41j_2;$14t0-~M^=tD43_54&vCMBxi6DE zaQRj0^Mp;)te?3QS4&?!c;VX~*$CP@Mdh{{inNO-m?vC1nhWFk1*q`17u!l=X`3f2 zyc={13x2H>U9JDvpmt#2%mRCmRHs(yhu%nyMABs#GDG3 z^tgIUk=7;4;wZ29^o6l7!w7D3W4(a5BHqHLg>_uJCibKytlVzdc=p{RRh^HL9bGf$ z9qL}(`n~5{>G6iD+3n+t1C7ha>!hB!&q*fTA|sf2aE+)~9!mJB_#fnsm9U z_G(#7jHuJMd5zzETH2~JmFrHNpA_WR8>jdWo_D_RESq(I z38TbkDH-fVE(WnT_>Q&gpI$81?3bzyT{L!GMXtI8l7XzC+y4e{q`rneD^dj@S%77@sLa* zk&C*A3u4`>Ulb-?Y@3iazv{D&Kgwp<*i^ZzdLr)?UMW=)LZ$A`;zXqXW@87J)W)5b5rGX~yv<6D;*|n*Etyu; zNx!iBBFN!eOighYrOLRYPr|}B-X+b7Ec>77@#|1utF!*_IG{~d z-G2R;OUJ6Nx3vP<(Ze)HPMugiX{0StZP(jK*|<5z!!0E5+ibo0rs%XYM?C5Kg?H$b zGO25hc+j_qa4J#a3YzX%KBAXgxh05sVo$n|$Sb{#UPnU``eHRVSBd_d%oz7LGO2jU z{Ba0-!+iGQMg7ylMGX@++iZ?KdTxBQk+iJyg3Log& z&qlb~Yt9cycWjWPf2GaYGx*pZ19Uh2>Ol{(YpD(&m$Xw*rhnkQ3!lN%n zx=uEZ$n-sU>@wC6#uPx_oO~+RydMf{7#1p-6`@*I%TQ$Ewt9x%ay&Un$b}c@~ zNjZ2+e4#LrUFz7c?Q@@xQGV|}y|5uDZ}>u1qcyGL`<1U4%9y!DnR8Z-NRAzxd;1%* z=NeAlA3`?D*frj95WTbulGIeq$(lOa3I_uiG4-lW+zA9m%O0);Z9AiM4M=?LE$_x?(e@;&WM z9H*BRtLho87%4O0@Kq?cdc~ot_e~*uxt4Z9jP#Ja?B-+j1#{D^baMmktHquk-WK`& zq91VpbDUmoMK74S)_M1-+;bKz<)l~sl=2f>F1Wzd1A zzTg;loLPPI-EFnCx_4X?53f7Iu`R^(MkGbv{@1$Ow5>N2CwrsRswTOOoX<SflVJGmZPY!P`jgdAGHT3<=b)##P^0EvW6=Ty-hFoGpa$r>=`@ zLu>T+2EI_1dxDDVO=f&f zto_boN5RsK7asUNYdo^+q;A&=if(NB*~eN73+x;p+mkx4c8Vmd)Ej>%{_bK;bCl_W zlc!^8TkUNrlr1AS3p?BdzxZ)6wA$aR+`fK-Z%p}a_5o9G3##Ys3_%g;VjG-{=>j68 zY=*Kcv!kBAo9VAVK2{>3u#VX3zOU)EgYQ<~arAU%soJXAKN#e*X{BoWM_TX0&YOLz z>EfonE0g0V)HR)MY3)y5hZnkO&}sht^eaQ=FC)nkyG%b_e_|qR^z-W@L0Rtm>pxwx z%wIp|aXomhCiDvP;#&=gKfBfX7Q)KR^ll}rDl({MeZ#ut{yv6JD}L{`bbT9;=qXI? zFZ73srRvbwH2?cm6x$7rCl~FN#yoKLF5R3L?s&F={*j?2@6dC@jt#r%H+vnAD{!DL zG6>lbuM}**&PC$Sh$-zUPu(+eE2!gBBfqz<)WP+5+qz_pJf3M>*3&x@6f|(Vh@Gc- zQe!P^Ly1erVi)%%_Uwy8IqOA#omMqA+9|HupxXYDHh)=h(hp~@iAP=P*EZHFx@COz z-^POAYHwk~dZqx~OT997;Ym>!=^rH~Dr?cBxOYRxc>f06I7e$T^K@ycm}Aeyz}xRj zow(;FIQMT-7`)Y>l?>XT*DDeKuWuT9el^ReXSytxtfj{4Gzi+zh7rM^D8DFRUNOk9G%;#Sy$1OJ+tHcMaLo4 zh7Vl3t=C*-XH}AZ=&1}Qir{5KwwDJsZ0qX1F~puMA$-C2?zK*> z02QO;U#CshvNAnoJ4dv$6WeEwyxRm5!mX zi*?uaN~bv=TGDpgKb1E!4mC}zr%-5vRP3yy#6JESeeT$4!m_c!cUt1t={{EKOvQeO z^~3k%%b5$5ZZB&!w(XhBcGsoN+&QYtl^Aw$(QCMC2aQJ6i<&xCVMeEm%7Qe-g7n{R zCJhy-+)YoEJ}`e>yXA=8mN(1hqWpfXop|_pkS;*|{kYZT{WNoVOe=mm6-^K1tnKJ3 z)WRqlOkLH>4==BF-;6QXAS8_M__%9>_4N0}1jmr+>5rCJ*XIw;&y5(KAsZUAa<2Qo{7p0EbtP5V6rIwxXrH45X9f9+*2{XYkv;a{M|*xCy{-BE zZN^hmcFeTFw6t;+&BY@oaear{_XyU#zK{@HRPD#EzEwHx(Z&3pl}S$ghqz6J8&+rD z%}1O^uYB_nPD<3dA2K+RKj;GT{Ysk7e8X`iQJvGG^NI1<#litNQ6nk7pKe1#h6eIk z#Yzg!c!j49gufr0<32GmLPc?ZzL}@!#iN_)wIACzS}|0u`mL85%H*FavhU6_j_P3F zmONTP%MGheowE8i@T>nw-(5%RddIdbLE#G$V!^ZASJp+ZnQY!Z?QB9lv~@NI@n_&y zuc-VX<~OF2dpes<1-0wcbe~&x(9;?!ddxoCNdGi8+)-Y`&gbR3Y{y@3O4HAE?Q8d? zAz|KRP)|pW&xJrL3d|#qNOW>U`6H8c4SUNIgH(*DXQOIOOf+*R?!Re%$miW-!u5wL zzf5j2mQ`VuV{%fcuV+IYC$7zFF}Lxz@y-}t=W(wSkFD1JPR&uuz5+0#;E zjNKmTPd{7uVAROGO+h*_A($?J@bUx^{l!fedN7S3M9S3;D^f{9ut_O=V2(e~o;ua! z5XS$0+?8Y3rCFh-R@IKcydTrYWQC|rC%F5yxD_xZ7>aqxP4dzjB2_zvA?t z!=gphV^3dAb~bT(7xF9?59*0il{O9gN&Xbsq1mA3?X0-r&Fz{xX}*JpQ@%6#`J8+b zddV_}3I;f7>b-5Z=B{@c7k2GS?(LBnTW3MVOgVI3mSUb+{MdV<+xSjgU~P_j2mPlI zU*2|#pySrL^X-AfPKgl{f}xs00T(vrGE7N6FqaI>dJ>uQhd(pTKdPXl`Z2uPT1WZ4 zG;M8W=T(ye6p#Miv>5@BOvF4*^7gLtBYh`RMil}z7EMU_w;YmREP@ zjsS^2RA;5ny=A2Yv?X$SA9mqA_ep@I^RmXLzSOrmdDZ8rl|OBLdgKnny7F7I2h$x~ z6>jJpHgz6864@G-3!`v<T=&T z?C^$0c7Bhou3cW)53BZxg(;X9UY!1tCsT2ajh1ftjluqKxg?k4%U_9IXqw3Hw+<0s zWSG)3wumTM$M$@n?)`?_>|Q!PzfKfql>VNz9@sVcV8&0iPMIlPX@M^4_PUckE7K*d zW7R@?ru%2Jx4Zp3U8uwPVpMBwJVn8gK3D$NE|EJAY@^h+Dr+50JAJUR-`m@^+i{Tj7M(=02<5ua!F> zo&U6Fg=>`BWorZpv*R%e`!1F~zU2F^@k6U>rmYgMbET}=stt^|*TQqVZ#l*qv1~ba z=(gTyrKl`b>>elY<(puoGtKuV3+>{>K){NRb*tM1G?+!qR(e1AW?y`-*ZSLNb*7t5 z*q*i5Xot2P>?-E;Em6HS#~kCn`$#ZXC~rH7g-(nprLQS;T}_wHu=Q&OlZsVmi={0$ z+gAR*(!Ht2`*V&d(D4-w9y8S39P`KaeN>lHro0@JPrebwKd@b_kVTX^*1Wya>&52b zcH;|TAzGs=JeE)8+>yVoP#-XG?Lp~w{8)6$B16NL#)NYXdN!2Ws6DH{Iq5}5hop(z z*z7YSPJBzSK8sf3?zn?90lFtb8LZzwW2JL>MWIY(eR<~EnKqF$GyFtdZ=AVE&W$Pb z=fx=vO$XUAm(Leo8k&;W?`(^(zjvlU&HSuvoJv4R-(cTZ;U9=a$;icRO*jYy6cHPc5GH|U@MTpbY0_JwIQ`(%id4THMowiHdKCWmi1 z^&zT0BCI-=3QygNqPD)7<5ZB&R)4=r^p|_4q43P)M0M^H=^&qC!NT>+g;kgF zKm0=#Uht^<;k}$JEPP;@K)Z{0ObQ#X_m986qHH(N z`0U6*hieBCB}Vl%m6+)sq^ZqY`xV+{3`S-|?Rh)LxyM52#pruW-o-YjxB}L4<6vgNzfapz2(qlYtpRvTSO z4&Vp=P-SYfbvmMBo2uz7>wMpTKuwChqc*SOI^GwiSa94L( z?l!!p3(K!r-AfA1Iu~QV;Z8MEaN~3mkTyOX#|{peHI>n6Catur8kdvwU(u}ib$V%A zh9s#fNGKI9cQ;=3;rGTp{r!)uyRBA62J08zSke4l3FZX38po*v+6jZ&r{upSG@Ve0 z4K^kBQm$DW5$==ci=EM4F53ssdx%s8Z{dab{E$!H9!KjykpX@3*P$(=-vFF)ox$;Pp3uvCpU&65j@D`v=XK8%x4NuCH6Fd% zU$=(!4j{Fp^MkKTe?xcx@9A3XHl~w`*@t)4G57{fpH!D1pT2=%TVH<%H6CLj~nn#d-}Sj)$2u71#1nIErOA56!XKo#!dy8t*zAnLJF> zG{5hY%K-}^^)nU9A0xRa>nxdSuA~$e_{fEE`nXPp*x%;Z<38hZG=BBP{3mM;aQZ4~ zYvo-uXcXuKesL)wpGC^Aga_A@pBa65?eRhSur`6}n%(D@4`$z1se4E9yd@QsV$!%r z`IABOyHDJ1^sRf+?k!7om_PGn^++(%Gyf!8g(EAL-_0qdCJDoxs|@ABbw6AL!&p?i z>gftJ-uO>8>0z}r5cHmSe1fi(y*}4iHDzIX2lto)gA<=B3Bm`Ai)uHtYz=nPG$?Jm zJ^yO!&b$2cPfQlKXkL%we7RTsRTIr1)!VC{GcIR@4H@UGl}bep<&Rc{Q?2WM(ytXK zG376@ja9R2)Zgq-nAFfOS0+d5B<9tvA{F70Tl*Y->ml-{^)d3)1EgT@8>w9IaJ@;B zE^Sl6(~#Hz#SgzX1t;nIv2;f4`PR{F#md^Dda9Ob_9{}slQXA6_V}5ybcVkuyE^K6 z{?I+!_uDA4o?&a=PwpzzS!9s)JfIt$5Sn$3z30!{sSS?35(AvNB_1!xBaTh476>nc zAGuoi<-GD1U+2l13+ID~tNlrE?5R6abh>lY;h`s)N^7hD?vlE-6%NLiQ2IkV=OBpF+c`3ZW$art1 zd+YD`Cm#4lP{>!dmNRE-sSAC=4Vy_ic4>HicbK1oRcFR3c=0pS=P&Bsih3)l-z+5- zsA3d&(U(cJ{|jx@6@|5``IYC?&UaeLG=DF+#nKrXJY_+LY(dZ1C?W?&WoDj6ZA_ZkeHqj_m!3l7?9PP3;%&h+@q?K(pTcd=Yw@Ty18_LDx#$5-(9>ireoR>FZy-i}fj zx$;N_1!vm&~BFA3Xo;-==Jig`UkGZR=#Xq_FMY929QR? zdCz4F`Q&ft3^^p2J%~_Q$dkJ?{B!sA9sRViYHs%Fio1*L`9vhl%9&T~$mel7FZgZ6 zsSB+ckA5_~y5`NK-`Q2a&nk4wyA_(qyQtWC??y(_bKZ(ioH4QTry>$;d&UNr2QPlsy*HDA zljG4Yj6`JlqN0<{`ROY@oLYE-B2gI1rtFqTF|nhZceeV~a{f@$Gg)E(VObuPH>E5PP=zO>iB6s=N?%kauE}E&JMR01UQxNM7R)ba(+)qfWo_(CLR8) zj?3-$jxVF8V%Yf2>irYR*46{-kRwa9<#RppP;ouqu6X41$?z3d1p0gu-8#G+bi3FF zyn`<#JmaD>UWGS(VtU?2Cc=nOH^uST<(5=0KET9<3df|dmIN@$taoxeNtaP8tIMjzc zT;}WCngbiWpF576LOwVcKXmJZNa)%fvp4MHTJCMPb@38R5g)$Q8qjxq(_~BYpviT$ z%Q1WbiX$a-lymK|ePitOZY{i&W7)SV96uc}89U?mDSB*Jpxm^njWcQ;+a3!8hAwTbt8UXmveiF^ zvaYGUa{NYb9y9&*`&I9$kvrKLdu}10NP9xSKE3yOqAP<`rn3?<6GS$Qu|^AuoqBV? zTDKv@bg*RH@nyYvHrMLkeOA+(KGJBW#Q9bpZp_asW0S156n4^BZT3!lYVS*7vx}S; zuD^|WYa$(>Cp>6zmW|3*L^pM*QazDSQ*xT~uq>ncP?OfXr)P^QO=!K@6+X_d>@Dx~ zXMCS3DW$b2{dwMos@7xj{ait|i{!>;bBD!MYC9P_%tC@9mmm<-yi4~kIPan)Xr*Sp z<(y8^zaG11?fQ$ketXAVTQ?1Jctb#ghDaW8APvx(>N;u< IRjdO4FTlI(UjP6A diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf b/applications/dynaclr/docs/DAGs/visuals/mock_roc_curves.pdf deleted file mode 100644 index ac3a972c3d3d3141066b478d6b9a47222afcc697..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27513 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%0A5gXQx8mS{Q;@)~$$fkt zEY?DiqP>E+i&QOIuKJFB#2V+ca#sdHZMxR4s7abas>-CJ+U+usXB{_HcWh1?%{ zzzl(v?QKjjh}o#k9ZRQNJJ{%(lo#XV0Ao_p-6gA5O`KkAW(z@R>x>GlXT5*sWNp{K z@lCsrlgr^6N3@WVoLphzi{r1o?ZWOC-ofjQqlOlqk?h+>os~=h)n%xpf^y+2*(gA}nn zT58F}3B@lukr=v>J_XCZ(0^)xov9Mh!tZswzi-^QH9J)n7PGYbhmeAi{V(H#N#1oA zVL?-0qm`D5DmLrJnvgCNwWR02ZRX2Lk~0uibZ`^f097A85y`SiLjKN$-sP6dd^6>6 zavGX=U&& z

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 z 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 From 68f354366a78fdcab653a0b2d7cfd6b2102fd85f Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 16 Jul 2026 17:30:02 -0700 Subject: [PATCH 30/89] refactor(mmd): debloat MMD utilities - Collapse _get_device() (~20 lines + module cache) to a one-line _DEVICE constant; torch.cuda.is_available() is sufficient here. - Remove the unused public gaussian_rbf_kernel() NumPy wrapper; nothing imports it and the internal _rbf_kernel does all the work. - Hoist the duplicated _subsample() (identical in compute_mmd.py and witness_gmm_labels.py) into a single shared viscy_utils.evaluation.mmd.subsample. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../linear_classifiers/witness_gmm_labels.py | 14 +--- .../src/dynaclr/evaluation/mmd/compute_mmd.py | 17 ++--- .../src/viscy_utils/evaluation/mmd.py | 74 ++++--------------- 3 files changed, 24 insertions(+), 81 deletions(-) diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py index 4cd1c2c95..8a2748677 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py @@ -36,7 +36,7 @@ class vocabulary — a file indistinguishable from a hand annotation. import pandas as pd from viscy_utils.cli_utils import load_config -from viscy_utils.evaluation.mmd import median_heuristic, witness_function +from viscy_utils.evaluation.mmd import median_heuristic, subsample, witness_function from viscy_utils.evaluation.witness_gmm import fit_gmm_labels if TYPE_CHECKING: @@ -134,14 +134,6 @@ def obs_filter_mask(obs: pd.DataFrame, filter_dict: dict) -> np.ndarray: return mask -def _subsample(X: np.ndarray, max_n: int | None, rng: np.random.Generator) -> np.ndarray: - """Randomly subsample rows of ``X`` to at most ``max_n`` (no-op if None/small).""" - if max_n is None or len(X) <= max_n: - return X - idx = rng.choice(len(X), max_n, replace=False) - return X[idx] - - _KEY_PRIMARY = ["experiment", "fov_name", "id"] _KEY_FALLBACK = ["experiment", "fov_name", "t", "track_id"] @@ -216,8 +208,8 @@ def build_marker_annotation( _logger.warning("No control/perturbed reference cells found; skipping marker.") return None - X_ref = _subsample(X_ctrl, config.max_reference_cells, rng) - Y_ref = _subsample(Y_pert, config.max_reference_cells, rng) + X_ref = subsample(X_ctrl, config.max_reference_cells, rng) + Y_ref = subsample(Y_pert, config.max_reference_cells, rng) bandwidth = config.bandwidth if config.bandwidth is not None else median_heuristic(X_ref, Y_ref) scores = witness_function(X_all, X_ref, Y_ref, bandwidth=bandwidth) diff --git a/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py b/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py index e70455b53..5c049bd8b 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py +++ b/applications/dynaclr/src/dynaclr/evaluation/mmd/compute_mmd.py @@ -19,7 +19,7 @@ _resolve_bin_edges, ) from viscy_utils.compose import load_composed_config -from viscy_utils.evaluation.mmd import median_heuristic, mmd_permutation_test +from viscy_utils.evaluation.mmd import median_heuristic, mmd_permutation_test, subsample def _extract_embeddings(adata: ad.AnnData, embedding_key: str | None) -> np.ndarray: @@ -46,13 +46,6 @@ def _extract_embeddings(adata: ad.AnnData, embedding_key: str | None) -> np.ndar return np.asarray(X) -def _subsample(X: np.ndarray, max_n: int | None, rng: np.random.Generator) -> np.ndarray: - if max_n is None or len(X) <= max_n: - return X - idx = rng.choice(len(X), max_n, replace=False) - return X[idx] - - def _run_one_comparison( emb_a: np.ndarray, emb_b: np.ndarray, @@ -90,12 +83,12 @@ def _run_one_comparison( All metric floats are NaN if fewer than min_cells cells in either group. """ rng = np.random.default_rng(settings.seed) - emb_a = _subsample(emb_a, settings.max_cells, rng) - emb_b = _subsample(emb_b, settings.max_cells, rng) + emb_a = subsample(emb_a, settings.max_cells, rng) + emb_b = subsample(emb_b, settings.max_cells, rng) if settings.balance_samples: min_n = min(len(emb_a), len(emb_b)) - emb_a = _subsample(emb_a, min_n, rng) - emb_b = _subsample(emb_b, min_n, rng) + emb_a = subsample(emb_a, min_n, rng) + emb_b = subsample(emb_b, min_n, rng) n_a_used = len(emb_a) n_b_used = len(emb_b) if n_a_used < settings.min_cells or n_b_used < settings.min_cells: diff --git a/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py b/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py index eca4b1aae..3f89ed844 100644 --- a/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py +++ b/packages/viscy-utils/src/viscy_utils/evaluation/mmd.py @@ -1,37 +1,24 @@ """Maximum Mean Discrepancy (MMD) with Gaussian RBF kernel and permutation test. -GPU-accelerated via PyTorch: the pooled RBF kernel matrix is built once on the -available device (CUDA if present, otherwise CPU) and reused across all -permutations. The public API is device-agnostic — inputs and outputs are NumPy -arrays / Python floats — so callers do not need to manage tensors or devices. +GPU-accelerated via PyTorch: kernel matrices are built on the available device +(CUDA if present, otherwise CPU). The public API is device-agnostic — inputs and +outputs are NumPy arrays / Python floats — so callers do not need to manage +tensors or devices. """ import numpy as np import torch from numpy.typing import NDArray +_DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") -_DEVICE: torch.device | None = None - -def _get_device() -> torch.device: - """Return a usable CUDA device, otherwise CPU (detected once and cached). - - ``torch.cuda.is_available()`` only reports that a GPU is *present*, not that - the installed PyTorch build can launch kernels on it (e.g. a GPU whose - compute capability predates the build raises at the first kernel launch). - We therefore probe with a trivial kernel and fall back to CPU if it fails. - """ - global _DEVICE - if _DEVICE is None: - _DEVICE = torch.device("cpu") - if torch.cuda.is_available(): - try: - (torch.zeros(1, device="cuda") + 1.0).cpu() - _DEVICE = torch.device("cuda") - except RuntimeError: - _DEVICE = torch.device("cpu") - return _DEVICE +def subsample(X: NDArray, max_n: int | None, rng: np.random.Generator) -> NDArray: + """Randomly subsample rows of ``X`` to at most ``max_n`` (no-op if None/small).""" + if max_n is None or len(X) <= max_n: + return X + idx = rng.choice(len(X), max_n, replace=False) + return X[idx] def _sq_dists(A: torch.Tensor, B: torch.Tensor) -> torch.Tensor: @@ -103,7 +90,7 @@ def median_heuristic(X: NDArray, Y: NDArray, subsample: int = 1000) -> float: if len(pool) > subsample: idx = rng.choice(len(pool), subsample, replace=False) pool = pool[idx] - device = _get_device() + device = _DEVICE P = torch.from_numpy(pool).to(device) d2 = _sq_dists(P, P) n = d2.shape[0] @@ -111,31 +98,6 @@ def median_heuristic(X: NDArray, Y: NDArray, subsample: int = 1000) -> float: return float(d2[mask].median().item()) + 1e-12 -def gaussian_rbf_kernel(X: NDArray, Y: NDArray, bandwidth: float) -> NDArray: - """Compute Gaussian RBF kernel matrix K(X, Y). - - K(x, y) = exp(-||x - y||^2 / (2 * bandwidth)) - - Parameters - ---------- - X : NDArray - Shape (n, d). - Y : NDArray - Shape (m, d). - bandwidth : float - Kernel bandwidth (sigma^2). Must be > 0. - - Returns - ------- - NDArray - Kernel matrix, shape (n, m), float32. - """ - device = _get_device() - A = torch.from_numpy(np.asarray(X, dtype=np.float32)).to(device) - B = torch.from_numpy(np.asarray(Y, dtype=np.float32)).to(device) - return _rbf_kernel(A, B, bandwidth).cpu().numpy() - - def compute_mmd_unbiased(X: NDArray, Y: NDArray, bandwidth: float | None = None) -> float: """Compute the unbiased quadratic-time MMD^2 estimator. @@ -159,7 +121,7 @@ def compute_mmd_unbiased(X: NDArray, Y: NDArray, bandwidth: float | None = None) """ if bandwidth is None: bandwidth = median_heuristic(X, Y) - device = _get_device() + device = _DEVICE Xt = torch.from_numpy(np.asarray(X, dtype=np.float32)).to(device) Yt = torch.from_numpy(np.asarray(Y, dtype=np.float32)).to(device) n = len(X) @@ -171,11 +133,7 @@ def compute_mmd_unbiased(X: NDArray, Y: NDArray, bandwidth: float | None = None) K_YY.fill_diagonal_(0.0) # Reduce in float64 so the estimate is symmetric in (X, Y) to machine # precision despite the float32 kernel. - mmd2 = ( - K_XX.double().sum() / (n * (n - 1)) - + K_YY.double().sum() / (m * (m - 1)) - - 2.0 * K_XY.double().mean() - ) + mmd2 = K_XX.double().sum() / (n * (n - 1)) + K_YY.double().sum() / (m * (m - 1)) - 2.0 * K_XY.double().mean() return float(mmd2.item()) @@ -238,7 +196,7 @@ def mmd_permutation_test( f"≈ {(N * N * 4) / 1e9:.1f} GB. Subsample X and/or Y so that " f"len(X) + len(Y) <= {_MMD_PERM_MAX_N}." ) - device = _get_device() + device = _DEVICE pool = np.concatenate([X, Y], axis=0).astype(np.float32) P = torch.from_numpy(pool).to(device) # Compute full pooled kernel matrix once: (N, N) @@ -314,7 +272,7 @@ def witness_function( """ if bandwidth is None: bandwidth = median_heuristic(X, Y) - device = _get_device() + device = _DEVICE Xt = torch.from_numpy(np.asarray(X, dtype=np.float32)).to(device) Yt = torch.from_numpy(np.asarray(Y, dtype=np.float32)).to(device) q = np.asarray(query, dtype=np.float32) From a3d7a5f0ce9ae02d703ce74b206f2a7b5eb39c68 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Fri, 17 Jul 2026 10:58:12 -0700 Subject: [PATCH 31/89] fix(triplet): reject non-positive pixel size in rescaling _read_pixel_size now raises on a zero/negative/NaN OME-Zarr X scale instead of letting reference/inference division produce inf/NaN that silently corrupts initial_yx_patch_size. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/viscy-data/src/viscy_data/triplet.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index 6bb81ad0f..47d009b8b 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -57,7 +57,10 @@ def _read_pixel_size(data_path: str | Path) -> float: """ with open_ome_zarr(data_path, mode="r") as store: for _, pos in store.positions(): - return float(pos.scale[-1]) + pixel_size = float(pos.scale[-1]) + if not pixel_size > 0: + raise ValueError(f"Non-positive X pixel size {pixel_size} in {data_path}; check OME-Zarr scale.") + return pixel_size raise ValueError(f"No positions found in {data_path}") @@ -585,7 +588,10 @@ def __init__( if reference_pixel_size is not None: inference_pixel_size = _read_pixel_size(data_path) scale = reference_pixel_size / inference_pixel_size - self.initial_yx_patch_size = tuple(max(1, int(round(s * scale))) for s in final_yx_patch_size) + # Round the extraction size up to an even number: the dataset extracts a + # centered window of width ``2 * (size // 2)``, so an odd ``size`` would be + # extracted one pixel short and the resize would then undershoot the target. + self.initial_yx_patch_size = tuple(max(2, 2 * round(s * scale / 2)) for s in final_yx_patch_size) _logger.info( f"Pixel size rescaling enabled: " f"reference={reference_pixel_size:.4f} µm/px, " @@ -602,8 +608,7 @@ def __init__( BatchedZoomd( keys=list(self.source_channel), scale_factor=(1.0, *scale_yx), - mode="bilinear", - antialias=True, + mode="nearest-exact", ) ) From 9b0a11475506adfe805619a0aef790b818c5efc6 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Fri, 17 Jul 2026 10:59:30 -0700 Subject: [PATCH 32/89] fix(data): clear error when batch mixes FOV timepoint stats _collate_norm_meta took timepoint keys from the first sample and indexed every other sample with them; a batch mixing FOVs whose zattrs expose different timepoint_statistics sets raised a cryptic KeyError. Validate the key sets up front and raise an actionable message instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/viscy-data/src/viscy_data/_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/viscy-data/src/viscy_data/_utils.py b/packages/viscy-data/src/viscy_data/_utils.py index bf133e184..05db466b9 100644 --- a/packages/viscy-data/src/viscy_data/_utils.py +++ b/packages/viscy-data/src/viscy_data/_utils.py @@ -189,6 +189,13 @@ def _collate_norm_meta(norm_metas: list[NormMeta]) -> NormMeta: continue if level == "timepoint_statistics": # Nested {timepoint: {stat: tensor}}; stack within each timepoint. + for m in norm_metas: + if m[ch][level].keys() != level_stats.keys(): + raise KeyError( + f"norm_meta timepoint keys differ across the batch for channel '{ch}': " + f"{sorted(level_stats)} vs {sorted(m[ch][level])}. " + "All FOVs in a batch must expose the same set of timepoint_statistics." + ) result[ch][level] = { tp: {stat: torch.stack([m[ch][level][tp][stat] for m in norm_metas]) for stat in tp_stats} for tp, tp_stats in level_stats.items() From 4c6aa9f5dcf44dd0f1bdcc4bacf509ed91161091 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Fri, 17 Jul 2026 16:10:33 -0700 Subject: [PATCH 33/89] fix(triplet): pre-resolve timepoint_statistics norm_meta to sample t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The classic TripletDataModule path returned the full per-FOV norm_meta from zattrs without selecting the sample's timepoint, so NormalizeSampled(level='timepoint_statistics') received the nested {tp_idx: {stat: Tensor}} layout and raised KeyError('mean'). Live DynaCLR configs (DynaCLR-3D-BagOfChannels-v2, DynaCLR-2D-MIP-BagOfChannels, DINOv3-temporal) use this level, but only via MultiExperimentDataModule, which already pre-resolves — so the classic path was latently broken. - Hoist the timepoint resolver out of SlidingWindowDataset into a shared _utils._resolve_timepoint_norm_meta and reuse it in both datasets. - Resolve the timepoint in TripletDataset._slice_patch using the row's t. - Drop the now-dead nested timepoint_statistics branch in _collate_norm_meta (all collated metas are flat after resolution). This supersedes the mixed-timepoint guard added in 9b0a1147. - Add a triplet integration test that fails without the resolution and point the existing sliding-window helper test at the shared function. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/viscy-data/src/viscy_data/_utils.py | 36 +++++--- .../src/viscy_data/sliding_window.py | 27 ++---- packages/viscy-data/src/viscy_data/triplet.py | 3 +- packages/viscy-data/tests/test_hcs.py | 6 +- packages/viscy-data/tests/test_triplet.py | 92 +++++++++++++++++++ 5 files changed, 127 insertions(+), 37 deletions(-) diff --git a/packages/viscy-data/src/viscy_data/_utils.py b/packages/viscy-data/src/viscy_data/_utils.py index 05db466b9..d26848111 100644 --- a/packages/viscy-data/src/viscy_data/_utils.py +++ b/packages/viscy-data/src/viscy_data/_utils.py @@ -171,6 +171,24 @@ def _read_norm_meta(fov: Position) -> NormMeta | None: read_norm_meta = _read_norm_meta +def _resolve_timepoint_norm_meta(norm_meta: NormMeta | None, t: int) -> NormMeta | None: + """Select the per-timepoint entry inside any ``timepoint_statistics`` level. + + ``NormalizeSampled(level='timepoint_statistics')`` expects a flat + ``{stat_name: Tensor}`` dict. The zattrs layout stores a nested + ``{tp_idx: {stat_name: Tensor}}``, so the dataset must pick the + current-sample's timepoint before the transform runs. + """ + if norm_meta is None: + return None + resolved = {} + for ch, levels in norm_meta.items(): + resolved[ch] = { + name: values[str(t)] if name == "timepoint_statistics" else values for name, values in levels.items() + } + return resolved + + def _collate_norm_meta(norm_metas: list[NormMeta]) -> NormMeta: """Stack per-sample norm_meta dicts into batched tensors. @@ -178,6 +196,10 @@ def _collate_norm_meta(norm_metas: list[NormMeta]) -> NormMeta: ``{channel: {level: {stat: scalar_tensor, ...}, ...}, ...}``. Returns the same structure but with ``(B,)`` tensors so that ``_match_image`` broadcasts them against ``(B, 1, Z, Y, X)`` patches. + + ``timepoint_statistics`` is pre-resolved to the sample's timepoint by + :func:`_resolve_timepoint_norm_meta` before collation, so every level here + is already a flat ``{stat: scalar_tensor}`` dict. """ ref = norm_metas[0] result: NormMeta = {} @@ -187,20 +209,6 @@ def _collate_norm_meta(norm_metas: list[NormMeta]) -> NormMeta: if level_stats is None: result[ch][level] = None continue - if level == "timepoint_statistics": - # Nested {timepoint: {stat: tensor}}; stack within each timepoint. - for m in norm_metas: - if m[ch][level].keys() != level_stats.keys(): - raise KeyError( - f"norm_meta timepoint keys differ across the batch for channel '{ch}': " - f"{sorted(level_stats)} vs {sorted(m[ch][level])}. " - "All FOVs in a batch must expose the same set of timepoint_statistics." - ) - result[ch][level] = { - tp: {stat: torch.stack([m[ch][level][tp][stat] for m in norm_metas]) for stat in tp_stats} - for tp, tp_stats in level_stats.items() - } - continue result[ch][level] = {stat: torch.stack([m[ch][level][stat] for m in norm_metas]) for stat in level_stats} return result diff --git a/packages/viscy-data/src/viscy_data/sliding_window.py b/packages/viscy-data/src/viscy_data/sliding_window.py index 7e109f555..254a0bd45 100644 --- a/packages/viscy-data/src/viscy_data/sliding_window.py +++ b/packages/viscy-data/src/viscy_data/sliding_window.py @@ -12,7 +12,12 @@ from torch.utils.data import Dataset from viscy_data._typing import ChannelMap, DictTransform, HCSStackIndex, NormMeta, Sample -from viscy_data._utils import _ensure_channel_list, _read_norm_meta, _search_int_in_str +from viscy_data._utils import ( + _ensure_channel_list, + _read_norm_meta, + _resolve_timepoint_norm_meta, + _search_int_in_str, +) from viscy_data.foreground_masks import ForegroundMaskSupport _logger = logging.getLogger("lightning.pytorch") @@ -145,24 +150,6 @@ def _find_window(self, index: int) -> tuple[ImageArray, int, NormMeta | None, in tz = index - self.window_keys[arr_idx - 1] if arr_idx > 0 else index return (self.window_arrays[arr_idx], tz, self.window_norm_meta[arr_idx], arr_idx) - @staticmethod - def _resolve_timepoint_norm_meta(norm_meta: NormMeta | None, t: int) -> NormMeta | None: - """Select the per-timepoint entry inside any ``timepoint_statistics`` level. - - ``NormalizeSampled(level='timepoint_statistics')`` expects a flat - ``{stat_name: Tensor}`` dict. The zattrs layout stores a nested - ``{tp_idx: {stat_name: Tensor}}``, so the dataset must pick the - current-sample's timepoint before the transform runs. - """ - if norm_meta is None: - return None - resolved = {} - for ch, levels in norm_meta.items(): - resolved[ch] = { - name: values[str(t)] if name == "timepoint_statistics" else values for name, values in levels.items() - } - return resolved - def _read_img_window( self, img: ImageArray, @@ -267,7 +254,7 @@ def __getitem__(self, index: int) -> Sample: # spatial transform co-alignment. This does not copy the tensor. sample_images["weight"] = sample_images[self.channels["target"][0]] if norm_meta is not None: - norm_meta = self._resolve_timepoint_norm_meta(norm_meta, sample_index[1]) + norm_meta = _resolve_timepoint_norm_meta(norm_meta, sample_index[1]) sample_images["norm_meta"] = norm_meta if self.transform: sample_images = self.transform(sample_images) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index 47d009b8b..230f816e0 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -32,6 +32,7 @@ from viscy_data._typing import ULTRACK_INDEX_COLUMNS, NormMeta from viscy_data._utils import ( _read_norm_meta, + _resolve_timepoint_norm_meta, _transform_channel_wise, ) from viscy_data.channel_utils import parse_channel_name @@ -356,7 +357,7 @@ def _slice_patch(self, track_row: "pd.Series") -> "tuple[ts.TensorStore, NormMet slice(y_center - y_half, y_center + y_half), slice(x_center - x_half, x_center + x_half), ] - return patch, _read_norm_meta(position) + return patch, _resolve_timepoint_norm_meta(_read_norm_meta(position), time) def _slice_patches(self, track_rows: "pd.DataFrame"): """Slice and stack patches for multiple track rows.""" diff --git a/packages/viscy-data/tests/test_hcs.py b/packages/viscy-data/tests/test_hcs.py index 1242e1d81..313cb5488 100644 --- a/packages/viscy-data/tests/test_hcs.py +++ b/packages/viscy-data/tests/test_hcs.py @@ -615,11 +615,13 @@ def test_resolve_timepoint_norm_meta_flattens_requested_index(): }, }, } - resolved = SlidingWindowDataset._resolve_timepoint_norm_meta(meta, t=1) + from viscy_data._utils import _resolve_timepoint_norm_meta + + resolved = _resolve_timepoint_norm_meta(meta, t=1) assert resolved["Phase"]["fov_statistics"]["mean"].item() == 0.5 assert resolved["Phase"]["timepoint_statistics"]["mean"].item() == 1000.0 assert resolved["Phase"]["timepoint_statistics"]["std"].item() == 100.0 - assert SlidingWindowDataset._resolve_timepoint_norm_meta(None, t=0) is None + assert _resolve_timepoint_norm_meta(None, t=0) is None @fixture(scope="function") diff --git a/packages/viscy-data/tests/test_triplet.py b/packages/viscy-data/tests/test_triplet.py index 6a6fa939f..2f2de9866 100644 --- a/packages/viscy-data/tests/test_triplet.py +++ b/packages/viscy-data/tests/test_triplet.py @@ -288,6 +288,98 @@ def test_z_reduction_runs_on_normalized_stack(preprocessed_hcs_dataset, tracks_h assert torch.allclose(reduced[:, ci], expected, atol=1e-5), f"channel {ch} not reduced on normalized stack" +def test_timepoint_statistics_resolved_in_triplet_dataset( + tmp_path_factory, preprocessed_hcs_dataset, tracks_hcs_dataset +): + """Triplet dataset must pre-resolve timepoint_statistics to each sample's t. + + ``NormalizeSampled(level='timepoint_statistics')`` expects a flat + ``{stat: Tensor}`` dict, but zattrs store ``{tp_idx: {stat: Tensor}}``. The + dataset must select the sample's timepoint before the transform runs; without + that resolution the returned norm_meta stays keyed by timepoint index and + ``NormalizeSampled`` raises ``KeyError('mean')``. The tracks fixture places + anchors at t=0 and t=1, given distinct means so each sample must resolve to + its own timepoint. + """ + import shutil + + tp_stats = {"0": {"mean": 0.0, "std": 1.0}, "1": {"mean": 0.9, "std": 1.0}} + data_path = tmp_path_factory.mktemp("timepoint_norm") / "data.zarr" + shutil.copytree(preprocessed_hcs_dataset, data_path) + with open_ome_zarr(data_path) as dataset: + channel_names = dataset.channel_names + norm_meta = {ch: {"timepoint_statistics": tp_stats} for ch in channel_names} + with open_ome_zarr(data_path, mode="r+") as dataset: + dataset.zattrs["normalization"] = norm_meta + for _, fov in dataset.positions(): + fov.zattrs["normalization"] = norm_meta + + dm = TripletDataModule( + data_path=data_path, + tracks_path=tracks_hcs_dataset, + source_channel=channel_names, + z_range=(4, 9), + initial_yx_patch_size=(32, 32), + final_yx_patch_size=(32, 32), + num_workers=0, + batch_size=4, + return_negative=False, + ) + dm.setup(stage="fit") + dataset = dm.train_dataset + checked = 0 + for i in range(len(dataset)): + sample = dataset.__getitems__([i]) + t = int(dataset.valid_anchors.iloc[i]["t"]) + for ch in channel_names: + level = sample["anchor_norm_meta"][0][ch]["timepoint_statistics"] + # Resolved: keyed by stat name, not by timepoint index. + assert "mean" in level, "timepoint_statistics not resolved to a flat {stat: value} dict" + assert abs(float(level["mean"]) - tp_stats[str(t)]["mean"]) < 1e-5, ( + f"sample {i} (t={t}) resolved to the wrong timepoint mean" + ) + checked += 1 + assert checked > 0 + + +@mark.parametrize("reference_pixel_size", [1.08, 1.3186231]) +def test_reference_pixel_size_rescale_output_shape(preprocessed_hcs_dataset, tracks_hcs_dataset, reference_pixel_size): + """reference_pixel_size rescale must land exactly on final_yx_patch_size. + + The fixture pixel size is 1.0 µm/px, so a reference of 1.08 makes the naive + ``initial = round(final * scale)`` land on an odd number (35). The dataset + extracts a centered window of width ``2 * (initial // 2)`` = 34, one pixel + short, so a scale-factor resize would undershoot to 31 rather than 32 and the + datamodule's spatial-shape check would raise. Rounding the extraction size to + an even number keeps the resize exact. 1.3186 mirrors the SEC61B_DENV run + (0.1494 / 0.1133). + """ + z_range = (4, 9) + final_yx = (32, 32) + batch_size = 4 + with open_ome_zarr(preprocessed_hcs_dataset) as dataset: + channel_names = dataset.channel_names + dm = TripletDataModule( + data_path=preprocessed_hcs_dataset, + tracks_path=tracks_hcs_dataset, + source_channel=channel_names, + z_range=z_range, + final_yx_patch_size=final_yx, + reference_pixel_size=reference_pixel_size, + z_reduction="mip", + num_workers=0, + batch_size=batch_size, + return_negative=True, + ) + dm.setup(stage="fit") + # Extraction size is rounded to an even number so the centered window is exact. + assert all(s % 2 == 0 for s in dm.initial_yx_patch_size) + for batch in dm.train_dataloader(): + dm.on_after_batch_transfer(batch, 0) + # z_reduction collapses Z to 1; the rescale must hit final_yx exactly. + assert batch["anchor"].shape == (batch_size, len(channel_names), 1, *final_yx) + + def test_filter_anchors_time_interval_any(preprocessed_hcs_dataset, tracks_with_gaps_dataset): """Test that time_interval='any' returns all tracks unchanged.""" with open_ome_zarr(preprocessed_hcs_dataset) as dataset: From 4a28b207779de77472532c2c676d8b953b7e1a55 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Fri, 17 Jul 2026 16:29:48 -0700 Subject: [PATCH 34/89] refactor(triplet): lazy-import viscy_transforms in rescale/z_reduction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit viscy_data eagerly imports triplet.py at package root, so a module-level `from viscy_transforms import ...` made viscy-transforms a hard requirement of `import viscy_data` — forcing the working tree to add it to core dependencies even for HCS-only users. Move the two imports (BatchedZoomd, BatchedChannelWiseZReductiond) into the reference_pixel_size / z_reduction branches where they are used, so they load only when those features are enabled. viscy-transforms stays in the [triplet] extra; `import viscy_data` no longer requires it. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/viscy-data/src/viscy_data/triplet.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/viscy-data/src/viscy_data/triplet.py b/packages/viscy-data/src/viscy_data/triplet.py index 230f816e0..b58e35391 100644 --- a/packages/viscy-data/src/viscy_data/triplet.py +++ b/packages/viscy-data/src/viscy_data/triplet.py @@ -38,7 +38,6 @@ from viscy_data.channel_utils import parse_channel_name from viscy_data.hcs import HCSDataModule from viscy_data.select import _filter_fovs, _filter_wells -from viscy_transforms import BatchedChannelWiseZReductiond, BatchedZoomd _logger = logging.getLogger("lightning.pytorch") @@ -587,6 +586,8 @@ def __init__( extra_transforms: list[MapTransform] = [] if reference_pixel_size is not None: + from viscy_transforms import BatchedZoomd + inference_pixel_size = _read_pixel_size(data_path) scale = reference_pixel_size / inference_pixel_size # Round the extraction size up to an even number: the dataset extracts a @@ -614,6 +615,8 @@ def __init__( ) if z_reduction is not None: + from viscy_transforms import BatchedChannelWiseZReductiond + labelfree_keys = [ch for ch in self.source_channel if parse_channel_name(ch)["channel_type"] == "labelfree"] mip_keys = [ch for ch in self.source_channel if ch not in labelfree_keys] _logger.info( From 7fa02ded78e6f97af12fe414b862bc982ee03ed9 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Tue, 21 Jul 2026 17:18:04 -0700 Subject: [PATCH 35/89] feat(dynaclr): per-marker embedding-consistency QC (MMD + correlation matrices) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dataset-to-dataset embedding-consistency QC that reports, per marker on control cells, two complementary matrices: MMD² (distributional, primary) and Pearson correlation of per-dataset mean embeddings (bounded companion). Reuses run_mmd_combined verbatim and iter_embeddings for glob-driven input discovery; detects and reports batch effects only — correction stays in LOT. - EmbeddingConsistencyConfig carries the model/run/ckpt provenance tuple - consistency.py: mmd_matrix_per_marker, corr_matrix_per_marker, plots, driver - CLI: dynaclr embedding-consistency-qc - example recipe + 6 integration/unit tests Co-Authored-By: Claude Opus 4.8 (1M context) --- .../recipes/embedding_consistency_qc.yml | 33 +++ applications/dynaclr/src/dynaclr/cli.py | 8 + .../src/dynaclr/evaluation/mmd/config.py | 42 +++ .../src/dynaclr/evaluation/mmd/consistency.py | 278 ++++++++++++++++++ .../dynaclr/tests/test_consistency.py | 166 +++++++++++ 5 files changed, 527 insertions(+) create mode 100644 applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml create mode 100644 applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py create mode 100644 applications/dynaclr/tests/test_consistency.py diff --git a/applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml b/applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml new file mode 100644 index 000000000..324c21d30 --- /dev/null +++ b/applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml @@ -0,0 +1,33 @@ +# Embedding-consistency QC — per-marker dataset x dataset consistency matrices. +# ============================================================================= +# Pools every dataset's control-cell embeddings for one model/run/checkpoint +# (globbed via dynaclr.evaluation.paths.iter_embeddings over +# {datasets_root}/*/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr) +# and reports two symmetric per-marker matrices + heatmaps: +# - MMD² (primary, distributional): low off-diagonal = comparable; large = +# batch effect to correct with LOT. Same statistic LOT validation uses. +# - Pearson correlation of per-dataset mean embeddings (companion, [-1,1], +# readable but centroid-only — a fast sanity check, blind to spread/shape). +# +# This step only detects and reports — it does not correct. +# +# Run: +# dynaclr embedding-consistency-qc -c recipes/embedding_consistency_qc.yml + +base: + - mmd_defaults.yml + +# Provenance tuple that identifies the embeddings to pool across datasets. +model_family: DynaCLR-2D-MIP-BagOfChannels-single-marker-fix-shuffler +run: run1 +ckpt_name: epoch146_step117600 +# datasets_root: null # null uses the canonical DATASETS_ROOT + +output_dir: /hpc/projects/intracellular_dashboard/organelle_dynamics/_qc/embedding_consistency + +# Restrict to control cells so perturbation biology cannot look like a batch effect. +obs_filter: + perturbation: uninfected + +# Measure residual batch effects independent of a global mean offset. +center_per_experiment: true diff --git a/applications/dynaclr/src/dynaclr/cli.py b/applications/dynaclr/src/dynaclr/cli.py index 98ef5748b..51d83fc93 100644 --- a/applications/dynaclr/src/dynaclr/cli.py +++ b/applications/dynaclr/src/dynaclr/cli.py @@ -231,6 +231,14 @@ def dynaclr(): ) ) +dynaclr.add_command( + LazyCommand( + name="embedding-consistency-qc", + import_path="dynaclr.evaluation.mmd.consistency.main", + short_help="Per-marker dataset x dataset embedding-consistency MMD matrix (control cells)", + ) +) + dynaclr.add_command( LazyCommand( name="prepare-eval-configs", diff --git a/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py b/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py index 296c59db9..b2f439a26 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/mmd/config.py @@ -262,3 +262,45 @@ def _validate(self) -> "MMDPooledConfig": if not self.comparisons: raise ValueError("comparisons must not be empty") return self + + +class EmbeddingConsistencyConfig(_MMDBaseConfig): + """Per-marker dataset-to-dataset embedding-consistency QC. + + Enumerates the input embedding zarrs for one model/run/checkpoint across + datasets via :func:`dynaclr.evaluation.paths.iter_embeddings`, runs pairwise + cross-dataset MMD on control cells only (``obs_filter``), and aggregates the + long-form output into a symmetric per-marker dataset x dataset MMD matrix. + A diagonal-dominant matrix (low off-diagonal MMD) means the embedding space + is comparable across acquisitions; large off-diagonal MMD flags a batch + effect that LOT correction must fix before downstream tasks trust the + embeddings. This QC only *detects and reports* — it does not correct. + + Parameters + ---------- + model_family : str + Model-family identity to pool over (path component). + run : str + Training-run identity to pool over (path component). + ckpt_name : str + Checkpoint identity to pool over (path component). + datasets_root : str or None + Base under which datasets live. None uses the canonical + :data:`dynaclr.evaluation.paths.DATASETS_ROOT`. Default: None. + center_per_experiment : bool + Subtract each dataset's own mean embedding before computing MMD, so the + matrix reports *residual* batch effects independent of a global offset. + Default: True. + + Notes + ----- + ``obs_filter`` (inherited) selects the control cells, e.g. + ``{"perturbation": "uninfected"}`` — so perturbation biology cannot + masquerade as a batch effect. + """ + + model_family: str + run: str + ckpt_name: str + datasets_root: str | None = None + center_per_experiment: bool = True diff --git a/applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py b/applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py new file mode 100644 index 000000000..0d06384a3 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py @@ -0,0 +1,278 @@ +"""Per-marker dataset-to-dataset embedding-consistency QC. + +Reports two complementary per-marker dataset x dataset matrices on control +cells, so batch effects between acquisitions can be read at a glance: + +- **MMD²** (primary, distributional): aggregates the long-form pairwise output + of :func:`dynaclr.evaluation.mmd.compute_mmd.run_mmd_combined`. Sensitive to + differences in mean, spread, and shape — the same statistic LOT validation + uses. Low off-diagonal = comparable; large off-diagonal = batch effect. +- **Pearson correlation** (companion, cheap/readable): correlation between + per-dataset mean control embeddings. Bounded ``[-1, 1]`` and easy to read, but + blind to distributional (covariance) shifts — a fast first-pass sanity check. + +Inputs are enumerated via :func:`dynaclr.evaluation.paths.iter_embeddings`; the +QC reuses ``run_mmd_combined`` verbatim and adds the matrix pivots, plotting, and +the glob-driven driver. It detects and reports — it does not correct (see +``lot_correction``). +""" + +from __future__ import annotations + +from pathlib import Path + +import anndata as ad +import click +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import seaborn as sns + +from dynaclr.evaluation.mmd.compute_mmd import _extract_embeddings, run_mmd_combined +from dynaclr.evaluation.mmd.config import EmbeddingConsistencyConfig, MMDCombinedConfig +from dynaclr.evaluation.paths import DATASETS_ROOT, iter_embeddings +from viscy_utils.compose import load_composed_config + + +def mmd_matrix_per_marker(df: pd.DataFrame) -> dict[str, pd.DataFrame]: + """Aggregate long-form pairwise MMD into a symmetric matrix per marker. + + Averages ``mmd2`` over conditions and temporal bins for each dataset pair, + then fills a symmetric square matrix indexed by dataset. The diagonal is 0 + (a dataset compared to itself has no batch effect). Datasets that never + appear as ``exp_a``/``exp_b`` for a marker are absent from that marker's + matrix. + + Parameters + ---------- + df : pd.DataFrame + Long-form output of :func:`run_mmd_combined` with at least the columns + ``marker``, ``exp_a``, ``exp_b``, ``mmd2``. + + Returns + ------- + dict[str, pd.DataFrame] + Mapping ``marker -> square DataFrame`` whose index and columns are the + datasets and whose cells are mean ``mmd2`` (diagonal 0). + """ + matrices: dict[str, pd.DataFrame] = {} + for marker, sub in df.groupby("marker"): + pair_mean = sub.groupby(["exp_a", "exp_b"])["mmd2"].mean() + datasets = sorted(set(sub["exp_a"]) | set(sub["exp_b"])) + matrix = pd.DataFrame(0.0, index=datasets, columns=datasets, dtype=float) + for (exp_a, exp_b), value in pair_mean.items(): + matrix.loc[exp_a, exp_b] = value + matrix.loc[exp_b, exp_a] = value + matrices[str(marker)] = matrix + return matrices + + +def plot_consistency_matrix(matrix: pd.DataFrame, marker: str, output_path: Path) -> None: + """Plot one symmetric dataset x dataset MMD heatmap for a marker. + + Parameters + ---------- + matrix : pd.DataFrame + Square symmetric MMD matrix (datasets x datasets), diagonal 0. + marker : str + Marker name, used in the title. + output_path : Path + Output file path. + """ + n = len(matrix) + fig, ax = plt.subplots(figsize=(max(4, n * 0.9), max(3.5, n * 0.8))) + sns.heatmap( + matrix, + ax=ax, + cmap="viridis", + square=True, + linewidths=0.5, + annot=True, + fmt=".3f", + cbar_kws={"label": "MMD²"}, + ) + ax.set_title(f"Embedding consistency — {marker}\n(control cells, dataset × dataset MMD²)") + ax.set_xlabel("Dataset") + ax.set_ylabel("Dataset") + ax.tick_params(axis="x", labelsize=8, rotation=45) + ax.tick_params(axis="y", labelsize=8, rotation=0) + fig.tight_layout() + fig.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close(fig) + + +def corr_matrix_per_marker( + input_paths: list[str], + obs_filter: dict[str, str] | None, + embedding_key: str | None, +) -> dict[str, pd.DataFrame]: + """Pearson-correlation matrix of per-dataset mean control embeddings, per marker. + + Loads each dataset zarr, applies the same control ``obs_filter`` as the MMD + path, computes the mean embedding for every (dataset, marker), and Pearson- + correlates those mean vectors across datasets. Cheap and bounded ``[-1, 1]``, + but only sees the centroid — a readable companion to the distributional MMD + matrix, not a replacement. + + Parameters + ---------- + input_paths : list[str] + Per-dataset embedding zarr paths (one dataset each). + obs_filter : dict[str, str] or None + ``obs[col] == val`` filter selecting control cells; None keeps all cells. + embedding_key : str or None + obsm key to correlate; None uses raw ``.X``. + + Returns + ------- + dict[str, pd.DataFrame] + Mapping ``marker -> square DataFrame`` (datasets x datasets) of Pearson + correlation between mean embeddings (diagonal 1). Markers present in + fewer than two datasets are omitted. + """ + marker_means: dict[str, dict[str, np.ndarray]] = {} + for path in input_paths: + adata = ad.read_zarr(path) + experiment = adata.obs["experiment"].iloc[0] + if obs_filter: + mask = pd.Series(True, index=adata.obs.index) + for col, val in obs_filter.items(): + if col not in adata.obs.columns: + raise KeyError(f"obs_filter column '{col}' not found in {experiment}") + mask &= adata.obs[col] == val + adata = adata[mask] + for marker in adata.obs["marker"].unique(): + sub = adata[adata.obs["marker"] == marker] + emb = _extract_embeddings(sub, embedding_key).astype(np.float32) + if len(emb) == 0: + continue + marker_means.setdefault(str(marker), {})[str(experiment)] = emb.mean(axis=0) + + matrices: dict[str, pd.DataFrame] = {} + for marker, means in marker_means.items(): + datasets = sorted(means) + if len(datasets) < 2: + continue + stacked = np.stack([means[d] for d in datasets]) + corr = np.corrcoef(stacked) + matrices[marker] = pd.DataFrame(corr, index=datasets, columns=datasets, dtype=float) + return matrices + + +def plot_corr_matrix(matrix: pd.DataFrame, marker: str, output_path: Path) -> None: + """Plot one symmetric dataset x dataset Pearson-correlation heatmap for a marker. + + Parameters + ---------- + matrix : pd.DataFrame + Square symmetric correlation matrix (datasets x datasets), diagonal 1. + marker : str + Marker name, used in the title. + output_path : Path + Output file path. + """ + n = len(matrix) + fig, ax = plt.subplots(figsize=(max(4, n * 0.9), max(3.5, n * 0.8))) + sns.heatmap( + matrix, + ax=ax, + cmap="RdBu_r", + vmin=-1.0, + vmax=1.0, + square=True, + linewidths=0.5, + annot=True, + fmt=".3f", + cbar_kws={"label": "Pearson r"}, + ) + ax.set_title(f"Embedding consistency — {marker}\n(control cells, mean-embedding correlation)") + ax.set_xlabel("Dataset") + ax.set_ylabel("Dataset") + ax.tick_params(axis="x", labelsize=8, rotation=45) + ax.tick_params(axis="y", labelsize=8, rotation=0) + fig.tight_layout() + fig.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close(fig) + + +def run_consistency_qc(config: EmbeddingConsistencyConfig) -> pd.DataFrame: + """Run the per-marker embedding-consistency QC end to end. + + Enumerates the embedding zarrs for the configured model/run/checkpoint + across datasets, runs pairwise cross-dataset MMD on control cells, writes the + long-form CSV plus one square-matrix CSV and heatmap per marker, and returns + the long-form DataFrame. + + Parameters + ---------- + config : EmbeddingConsistencyConfig + QC configuration (provenance tuple, control filter, MMD settings). + + Returns + ------- + pd.DataFrame + The long-form pairwise MMD results (same schema as ``run_mmd_combined``). + """ + datasets_root = config.datasets_root if config.datasets_root is not None else DATASETS_ROOT + input_paths = [ + str(p) for p in iter_embeddings(config.model_family, config.run, config.ckpt_name, datasets_root=datasets_root) + ] + if len(input_paths) < 2: + raise ValueError( + f"embedding-consistency QC needs >=2 dataset zarrs for " + f"{config.model_family}/{config.run}/{config.ckpt_name} under {datasets_root}, " + f"found {len(input_paths)}: {input_paths}" + ) + + combined = MMDCombinedConfig( + input_paths=input_paths, + output_dir=config.output_dir, + group_by=config.group_by, + obs_filter=config.obs_filter, + embedding_key=config.embedding_key, + mmd=config.mmd, + map_settings=config.map_settings, + temporal_bin_size=config.temporal_bin_size, + temporal_bins=config.temporal_bins, + save_plots=config.save_plots, + center_per_experiment=config.center_per_experiment, + ) + df = run_mmd_combined(combined) + + output_dir = Path(config.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + df.to_csv(output_dir / "consistency_mmd_results.csv", index=False) + + mmd_matrices = mmd_matrix_per_marker(df) + for marker, matrix in mmd_matrices.items(): + safe = marker.replace(" ", "_").replace("/", "-") + matrix.to_csv(output_dir / f"consistency_mmd_matrix_{safe}.csv") + if config.save_plots: + for fmt in ("pdf", "png"): + plot_consistency_matrix(matrix, marker, output_dir / f"consistency_mmd_matrix_{safe}.{fmt}") + + corr_matrices = corr_matrix_per_marker(input_paths, config.obs_filter, config.embedding_key) + for marker, matrix in corr_matrices.items(): + safe = marker.replace(" ", "_").replace("/", "-") + matrix.to_csv(output_dir / f"consistency_corr_matrix_{safe}.csv") + if config.save_plots: + for fmt in ("pdf", "png"): + plot_corr_matrix(matrix, marker, output_dir / f"consistency_corr_matrix_{safe}.{fmt}") + return df + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "-c", + "--config", + type=click.Path(exists=True, path_type=Path), + required=True, + help="Path to embedding-consistency QC YAML config", +) +def main(config: Path) -> None: + """Compute the per-marker dataset x dataset embedding-consistency MMD matrix.""" + raw = load_composed_config(config) + cfg = EmbeddingConsistencyConfig(**raw) + df = run_consistency_qc(cfg) + n_markers = df["marker"].nunique() if len(df) else 0 + click.echo(f"Saved consistency QC ({n_markers} marker matrices) to: {cfg.output_dir}") diff --git a/applications/dynaclr/tests/test_consistency.py b/applications/dynaclr/tests/test_consistency.py new file mode 100644 index 000000000..6c51b66d6 --- /dev/null +++ b/applications/dynaclr/tests/test_consistency.py @@ -0,0 +1,166 @@ +"""Tests for per-marker embedding-consistency QC.""" + +from __future__ import annotations + +import anndata as ad +import numpy as np +import pandas as pd +import pytest + +from dynaclr.evaluation.mmd.config import EmbeddingConsistencyConfig, MMDSettings +from dynaclr.evaluation.mmd.consistency import ( + corr_matrix_per_marker, + mmd_matrix_per_marker, + plot_consistency_matrix, + run_consistency_qc, +) +from dynaclr.evaluation.paths import embedding_store + + +def _long_form() -> pd.DataFrame: + """Minimal long-form pairwise MMD frame: 3 datasets, 2 markers, 2 conditions.""" + rows = [] + datasets = ["ds_a", "ds_b", "ds_c"] + for marker in ["TOMM20", "Phase3D"]: + for i in range(len(datasets)): + for j in range(i + 1, len(datasets)): + for cond, base in [("uninfected", 0.1), ("ZIKV", 0.2)]: + rows.append( + { + "marker": marker, + "exp_a": datasets[i], + "exp_b": datasets[j], + "condition": cond, + "mmd2": base + 0.05 * (j - i), + "p_value": 0.01, + } + ) + return pd.DataFrame(rows) + + +def test_matrix_is_symmetric_zero_diagonal(): + matrices = mmd_matrix_per_marker(_long_form()) + assert set(matrices) == {"TOMM20", "Phase3D"} + for matrix in matrices.values(): + assert list(matrix.index) == list(matrix.columns) == ["ds_a", "ds_b", "ds_c"] + values = matrix.to_numpy() + assert np.allclose(np.diag(values), 0.0) + assert np.allclose(values, values.T) + + +def test_matrix_averages_over_conditions(): + matrices = mmd_matrix_per_marker(_long_form()) + # ds_a vs ds_b: (0.1 + 0.05) and (0.2 + 0.05) -> mean 0.20 + assert matrices["TOMM20"].loc["ds_a", "ds_b"] == pytest.approx(0.20) + + +def test_plot_consistency_matrix_writes(tmp_path): + matrix = mmd_matrix_per_marker(_long_form())["TOMM20"] + out = tmp_path / "m.png" + plot_consistency_matrix(matrix, "TOMM20", out) + assert out.exists() and out.stat().st_size > 0 + + +def _corr_dataset(experiment: str, mean_vec: np.ndarray, n_cells: int = 40) -> ad.AnnData: + """Control-only dataset whose per-cell embeddings scatter tightly around ``mean_vec``.""" + rng = np.random.default_rng(abs(hash(experiment)) % (2**32)) + X = mean_vec[None, :] + rng.normal(scale=1e-3, size=(n_cells, mean_vec.size)) + obs = pd.DataFrame( + {"experiment": experiment, "marker": "TOMM20", "perturbation": "uninfected"}, + index=[str(i) for i in range(n_cells)], + ) + return ad.AnnData(X=X.astype(np.float32), obs=obs) + + +def test_corr_matrix_per_marker(tmp_path): + # ds_a and ds_b share a mean direction (perfect +1); ds_c is its negation (-1). + base = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32) + paths = [] + for name, vec in [("ds_a", base), ("ds_b", 2 * base), ("ds_c", -base)]: + p = tmp_path / f"{name}.zarr" + _corr_dataset(name, vec).write_zarr(str(p)) + paths.append(str(p)) + + matrices = corr_matrix_per_marker(paths, {"perturbation": "uninfected"}, embedding_key=None) + corr = matrices["TOMM20"] + assert corr.loc["ds_a", "ds_b"] == pytest.approx(1.0, abs=1e-2) + assert corr.loc["ds_a", "ds_c"] == pytest.approx(-1.0, abs=1e-2) + + +def _make_dataset_adata(experiment: str, shift: float, seed: int, n_features: int = 16) -> ad.AnnData: + """One dataset's control+treated embeddings for a single marker, mean-shifted by ``shift``.""" + rng = np.random.default_rng(seed) + rows = [] + embs = [] + for perturbation in ["uninfected", "ZIKV"]: + for _ in range(60): + embs.append(rng.normal(loc=shift, scale=1.0, size=n_features)) + rows.append( + { + "experiment": experiment, + "marker": "TOMM20", + "perturbation": perturbation, + "hours_post_perturbation": 0.0, + } + ) + return ad.AnnData(X=np.stack(embs).astype(np.float32), obs=pd.DataFrame(rows)) + + +def _write_datasets(datasets_root, model_family, run, ckpt) -> None: + """Write ds_a/ds_b (similar) and ds_c (shifted) as per-marker zarrs in the canonical tree.""" + specs = [("ds_a", 0.0, 0), ("ds_b", 0.0, 1), ("ds_c", 5.0, 2)] + for dataset, shift, seed in specs: + store = embedding_store(dataset, model_family, run, ckpt, "TOMM20", datasets_root=datasets_root) + store.parent.mkdir(parents=True, exist_ok=True) + _make_dataset_adata(dataset, shift, seed).write_zarr(str(store)) + + +def _qc_config(datasets_root, output_dir) -> EmbeddingConsistencyConfig: + return EmbeddingConsistencyConfig( + model_family="modelX", + run="run1", + ckpt_name="ckptA", + datasets_root=str(datasets_root), + output_dir=str(output_dir), + obs_filter={"perturbation": "uninfected"}, + center_per_experiment=False, + save_plots=True, + mmd=MMDSettings(n_permutations=50, max_cells=200, min_cells=10, seed=0), + ) + + +def test_run_consistency_qc_end_to_end(tmp_path): + datasets_root = tmp_path / "datasets" + output_dir = tmp_path / "qc_out" + _write_datasets(datasets_root, "modelX", "run1", "ckptA") + + df = run_consistency_qc(_qc_config(datasets_root, output_dir)) + + assert set(df["exp_a"]) | set(df["exp_b"]) == {"ds_a", "ds_b", "ds_c"} + assert (output_dir / "consistency_mmd_results.csv").exists() + assert (output_dir / "consistency_mmd_matrix_TOMM20.csv").exists() + assert (output_dir / "consistency_mmd_matrix_TOMM20.png").exists() + assert (output_dir / "consistency_corr_matrix_TOMM20.csv").exists() + assert (output_dir / "consistency_corr_matrix_TOMM20.png").exists() + + matrix = pd.read_csv(output_dir / "consistency_mmd_matrix_TOMM20.csv", index_col=0) + # ds_c is mean-shifted; with center_per_experiment=False its off-diagonal MMD + # to the two unshifted datasets must exceed the ds_a<->ds_b baseline. + assert matrix.loc["ds_a", "ds_c"] > matrix.loc["ds_a", "ds_b"] + assert matrix.loc["ds_b", "ds_c"] > matrix.loc["ds_a", "ds_b"] + + corr = pd.read_csv(output_dir / "consistency_corr_matrix_TOMM20.csv", index_col=0) + assert list(corr.index) == list(corr.columns) == ["ds_a", "ds_b", "ds_c"] + assert np.allclose(np.diag(corr.to_numpy()), 1.0) + assert np.allclose(corr.to_numpy(), corr.to_numpy().T) + assert corr.to_numpy().min() >= -1.0 and corr.to_numpy().max() <= 1.0 + + +def test_run_consistency_qc_requires_two_datasets(tmp_path): + datasets_root = tmp_path / "datasets" + store = embedding_store("ds_only", "modelX", "run1", "ckptA", "TOMM20", datasets_root=datasets_root) + store.parent.mkdir(parents=True, exist_ok=True) + _make_dataset_adata("ds_only", 0.0, 0).write_zarr(str(store)) + + with pytest.raises(ValueError, match=">=2 dataset zarrs"): + run_consistency_qc(_qc_config(datasets_root, tmp_path / "qc_out")) From 10bea7b4fc90abb493b1b383ac4d8acc4864ad0a Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 09:15:34 -0700 Subject: [PATCH 36/89] docs(dynaclr): gate-free witness reference default + rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default the perturbed reference to gate-free (all perturbed-well cells, all timepoints), matching Soorya's approach. The per-condition GMM separates the remodeled mode from the dirty perturbed mixture, so a hours_post_perturbation time gate is redundant — it would discard data and re-introduce the hand-tuned threshold the GMM exists to replace. The config still supports a gate for special cases. - Recipe: drop the 18-24h window from the perturbed_filter; add rationale comment. - DAG: add "Reference construction & the GMM gate" section explaining the asymmetry (clean control cloud, dirty perturbed mixture, GMM fit on the perturbed side only) + a Mermaid workflow diagram. - DAG: add a "deployable artifact" note — the joblib carries the frozen scaler / PCA / logistic; apply is .transform-only (never re-fit), valid within the same microscope/domain. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../witness_gmm_labels_infectomics.yml | 9 ++- .../docs/DAGs/witness_gmm_classifiers.md | 65 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml index a19cc654c..68f4f94d6 100644 --- a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml +++ b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml @@ -22,8 +22,15 @@ witness_gmm_labels: experiments: - experiment: "2025_07_24_A549_viral_sensor_ZIKV" embeddings_zarr: "/path/to/2025_07_24_A549_viral_sensor_ZIKV/embeddings" + # Both references are pooled by well identity across ALL timepoints (default). + # The perturbed cloud is intentionally a MIXTURE (early cells not yet remodeled + + # late remodeled cells) — the per-condition GMM separates the remodeled mode from + # that mixture, so no time gate is needed. A time gate here would discard data and + # re-introduce a hand-tuned threshold (the thing the GMM replaces). Only add + # `hours_post_perturbation: {ge: N}` to the perturbed_filter if you have a specific + # reason to pre-restrict the reference. control_filter: {perturbation: uninfected} - perturbed_filter: {perturbation: [ZIKV], hours_post_perturbation: {ge: 18, lt: 24}} + perturbed_filter: {perturbation: [ZIKV]} # From viral_sensor the witness axis means infection. marker_filters: [viral_sensor] label_column: infection_state diff --git a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md index 1cd9966cd..558bea6a6 100644 --- a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md +++ b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md @@ -62,6 +62,48 @@ flowchart TD +## Reference construction & the GMM gate (why no time gate) + +The two witness references are built **asymmetrically**, and this is the crux of the +method: + +- **Control cloud (X) = all cells in the control/uninfected wells, all timepoints.** + An uninfected cell looks uninfected at any hpi, so pooling every timepoint gives a + large, *clean* reference. No gating. +- **Perturbed cloud (Y) = all cells in the perturbed wells, all timepoints.** A perturbed + well is a **mixture**: early cells have not remodeled yet, late cells have. Y is + therefore *dirty* by construction. + +The witness `w(z) = mean k(z, X) − mean k(z, Y)` is scored against both clouds. The +**per-condition GMM is then fit on the perturbed cells' scores only** — it separates the +remodeled mode from the not-yet-remodeled mode *inside* the dirty Y. Control cells are +taken as negatives wholesale (well identity). This asymmetry is why the GMM is fit on one +side but not the other. + +**No time gate by default.** A `hours_post_perturbation` window on the perturbed filter +would pre-clean Y by hand — but that discards data and re-introduces a hand-tuned +threshold, which is exactly what the GMM removes. The GMM is the principled replacement for +the time gate: it finds the remodeled sub-population within the full mixture. Add a time +gate only for a specific reason (e.g. debugging, or a marker with no clean late window). + +```mermaid +flowchart LR + subgraph refs["reference clouds (per marker, all timepoints)"] + X["X = control wells
clean (uninfected at any hpi)"] + Y["Y = perturbed wells
dirty mixture
early: not remodeled · late: remodeled"] + end + W["witness score per cell
w(z) = mean k(z,X) − mean k(z,Y)"] + G["2-component GMM on w over Y
(separates the mixture)"] + POS["remodeled mode → positive
(posterior ≥ threshold)"] + NEG["all X cells → negative
(well identity, no gate)"] + X --> W + Y --> W + W --> G --> POS + X --> NEG + POS --> LAB["annotation file
positive / negative"] + NEG --> LAB +``` + ## Recipe / config **Stage A** — `labels_config.yml`: @@ -71,8 +113,8 @@ witness_gmm_labels: experiments: - experiment: "2026_04_28_A549_SEC61B_DENV" embeddings_zarr: ".../2-phenotyping/predictions/embeddings" - control_filter: {perturbation: uninfected} - perturbed_filter: {perturbation: [DENV], hours_post_perturbation: {ge: 18, lt: 24}} + control_filter: {perturbation: uninfected} # all uninfected cells, all timepoints + perturbed_filter: {perturbation: [DENV]} # all DENV cells, all timepoints (no time gate) marker_filters: [viral_sensor] # from viral_sensor → infection_state label_column: infection_state class_map: {positive: infected, negative: uninfected} @@ -109,6 +151,25 @@ dynaclr witness-gmm-labels -c labels_config.yml dynaclr run-linear-classifiers -c train_config.yml ``` +## The deployable artifact (do NOT recompute the scaler / PCA) + +> **Note:** each `pipelines/{task}_{marker}.joblib` is a `LinearClassifierPipeline` +> holding the **fitted** `StandardScaler`, the **fitted** `PCA` (when `use_pca: true`), +> and the logistic-regression weights — all frozen from training. Applying to a new +> dataset (`apply-linear-classifier` / `append-predictions`) calls +> `scaler.transform → pca.transform → classifier.predict_proba` — **`.transform`, never +> `.fit`**. The scaler's mean/std and PCA's rotation are **not** recomputed on new data, +> and must not be: re-fitting would re-center/re-rotate the new embeddings into a +> different space than the classifier's `w·x+b` boundary was learned in, silently +> corrupting predictions. The pipeline is embedding-only and self-contained — no witness +> references or GMM are carried into it (those live only in Stage A). +> +> **Validity condition:** reusing the frozen scaler/PCA is correct only when the new +> embeddings share the training distribution — i.e. the **same microscope / domain**. +> Under a batch shift (e.g. mantis v1 → v2) applying the frozen pipeline is mechanically +> valid but biologically off; that is why the design is **one LC per microscope** rather +> than cross-domain transfer. + ## Related - Annotation training path: [evaluation.md](evaluation.md) From 6306aa3dfd28735bbb71df9ba80702c834e18490 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 09:27:50 -0700 Subject: [PATCH 37/89] chore(ruff): ignore D and E501 under scripts/ scripts/ holds executable analysis scripts with hardcoded example dataset and model paths (like examples/ and evaluation/, which already ignore these). Matches the existing per-file-ignore convention in the root config. Co-Authored-By: Claude Opus 4.8 (1M context) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index efde37d5a..6f7e35410 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,7 @@ lint.per-file-ignores."**/__init__.py" = [ "D104", "F401" ] lint.per-file-ignores."**/docs/**" = [ "I" ] lint.per-file-ignores."**/evaluation/**" = [ "D", "E501", "NPY002", "PD011" ] lint.per-file-ignores."**/examples/**" = [ "D", "E402", "E501", "F821" ] +lint.per-file-ignores."**/scripts/**" = [ "D", "E501" ] lint.per-file-ignores."**/tests/**" = [ "D" ] lint.pydocstyle.convention = "numpy" From 06f665529c183c72048c709002b42a6e3db30117 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 09:29:07 -0700 Subject: [PATCH 38/89] feat(dynaclr): occlusion-attribution report script for classifier eval Standalone report that, given a dataset + tracking zarr, a DynaCLR encoder (config + checkpoint), and a linear classifier joblib (optionally with a separate StandardScaler), picks random cell tracks, scores the classifier probability along each track over time, runs occlusion saliency per frame, and writes a PNG + PDF (per-cell phase + occlusion-overlay rows and a shared P(target) vs time plot). Preprocessing metadata (patch size, reference pixel size, normalization level, image channel) is read from the training config.yaml since it is not stored in the checkpoint; CONFIG values override. Uses the validated per-timepoint normalization + focus z-slice + pixel-size rescale pipeline. Generated figures under output/ are gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../explainability/occlusion/.gitignore | 1 + .../occlusion/occlusion_infection_report.py | 340 ++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 applications/dynaclr/scripts/explainability/occlusion/.gitignore create mode 100644 applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py diff --git a/applications/dynaclr/scripts/explainability/occlusion/.gitignore b/applications/dynaclr/scripts/explainability/occlusion/.gitignore new file mode 100644 index 000000000..ea1472ec1 --- /dev/null +++ b/applications/dynaclr/scripts/explainability/occlusion/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py b/applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py new file mode 100644 index 000000000..bcf535720 --- /dev/null +++ b/applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py @@ -0,0 +1,340 @@ +"""Occlusion-attribution report for a DynaCLR classifier over random cell tracks. + +Given a dataset (raw OME-Zarr with ``focus_slice`` + ``normalization`` zattrs), +a tracking OME-Zarr (label images), a DynaCLR encoder (config + checkpoint), +and a linear classifier joblib (optionally with a separate StandardScaler), +this picks a few random cell tracks, scores the classifier probability along +each track over time, runs occlusion saliency per frame, and writes a report +(PNG + PDF): per-cell clean-phase row + occlusion-overlay row, plus a shared +P(target) vs time plot. + +Preprocessing matches DynaCLR training (validated against stored predict-zarr +embeddings): per-timepoint z-score normalization, per-timepoint focus z-slice +(no MIP for phase), and pixel-size rescale (crop the physical-area-matched +window then resize to the model's patch size). + +Not a CLI yet — edit ``CONFIG`` below and run: + uv run python applications/dynaclr/scripts/occlusion_infection_report.py +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import joblib +import numpy as np +import torch +import torch.nn.functional as F +import zarr +from iohub.ngff import open_ome_zarr +from scipy import ndimage as ndi + +from dynaclr.visualization.compare_pca_rgb_cli import _build_dynaclr +from dynaclr.visualization.occlusion_overlay import make_embed_fn +from viscy_utils.visualization.occlusion import occlusion_saliency, saliency_to_rgb + +# ── CONFIG — edit these ──────────────────────────────────────────────────────── +CONFIG: dict[str, Any] = { + # data + "data_zarr": "/hpc/projects/organelle_phenotyping/datasets/2026_04_10_A549_TOMM20_ZIKV/2026_04_10_A549_TOMM20_ZIKV.zarr", + "tracking_zarr": "/hpc/projects/organelle_phenotyping/datasets/2026_04_10_A549_TOMM20_ZIKV/tracking.zarr", + "wells": None, # None = all wells; or a list like ["B/2", "B/4"] to restrict + # model + "config_path": "/hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/config.yaml", + "ckpt_path": "/hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/DynaCLR-2D-MIP-BagOfChannels/jbrwhzr3/checkpoints/epoch=105-step=84800.ckpt", + # classifier (joblib is a sklearn estimator or a LinearClassifierPipeline) + "classifier_path": "/hpc/projects/organelle_phenotyping/models/linear_classifiers/DynaCLR-2D-MIP-BagOfChannels-single-marker-fix-shuffler/infectomics_epoch105_step84800/v3/remodeling_state_Phase3D_ZIKV_Mantisv2.joblib", + "scaler_path": "/hpc/mydata/soorya.pradeep/GitHub/dynaclr-organelle-paper/organelle_remodeling_paper/figures/label-free_classifier_infection/sec61b_timelapse_zikv/phase_scaler.joblib", # None if the joblib needs no external scaler + "target_class": 1, # class label whose probability is the saliency target + plotted P + # preprocessing — read from config.yaml by default; set here to override. + "image_channel": None, # None = derive from config (focus_channel / normalizations) + "final_patch": None, # None = data.final_yx_patch_size from config + "reference_pixel_size_xy_um": None, # None = data.reference_pixel_size_xy_um from config + "norm_level": None, # None = level from config normalizations (e.g. timepoint_statistics) + # occlusion + "occ_size": 8, + "stride": 4, + "distance": "signed_delta", # signed_delta (classifier) | l2 | cosine (embedding) + "cmap": "icefire", + "clip_value": 0.2, + # sampling + "n_cells": 4, # 3–5 random tracks + "min_track_len": 20, + "seed": 0, + # output + "out_dir": "applications/dynaclr/scripts/explainability/occlusion/output", + "out_stem": "occlusion_infection_report", + "n_show": 6, # montage columns per cell +} +# ─────────────────────────────────────────────────────────────────────────────── + + +def _read_model_config(config_path: str) -> dict[str, Any]: + """Pull preprocessing metadata from the training config.yaml ``data:`` block. + + Returns final_patch, reference_pixel_size_xy_um, norm_level, and image_channel. + These are the source of truth — they are NOT stored in the checkpoint. + """ + import yaml + + with open(config_path) as f: + cfg = yaml.safe_load(f) + data = cfg["data"]["init_args"] + final_yx = data.get("final_yx_patch_size", [160, 160]) + norms = data.get("normalizations") or [] + norm_level = None + for n in norms: + ia = n.get("init_args", {}) + if "level" in ia: + norm_level = ia["level"] + break + # image channel: prefer explicit focus_channel, else first normalization key + channel = data.get("focus_channel") + if channel is None and norms: + keys = norms[0].get("init_args", {}).get("keys") or [] + channel = keys[0] if keys else None + return { + "final_patch": int(final_yx[-1]), + "reference_pixel_size_xy_um": data.get("reference_pixel_size_xy_um"), + "norm_level": norm_level or "timepoint_statistics", + "image_channel": channel, + } + + +def _classifier_closure(clf_path: str, scaler_path: str | None, target_class, embed_fn): + """Return (fn: images -> (B,1) P(target_class), classes).""" + obj = joblib.load(clf_path) + # LinearClassifierPipeline has .classifier / .predict_proba; a bare sklearn + # estimator exposes predict_proba / classes_ directly. + estimator = getattr(obj, "classifier", obj) + classes = list(estimator.classes_) + tgt = classes.index(target_class) + scaler = joblib.load(scaler_path) if scaler_path else None + + def fn(x: torch.Tensor) -> torch.Tensor: + emb = embed_fn(x).detach().cpu().float().numpy() + if scaler is not None: + emb = scaler.transform(emb) + proba = ( + obj.predict_proba(emb) if scaler is None and hasattr(obj, "predict_proba") else estimator.predict_proba(emb) + ) + return torch.from_numpy(np.asarray(proba)[:, tgt : tgt + 1]).to(x.device, torch.float32) + + return fn, classes + + +def _pick_random_tracks(tracking_zarr: str, data_zarr: str, wells, n: int, min_len: int, half: int, seed: int): + """Return list of (well, fov, label_id). In-bounds, long-enough, random, ideally spread across wells.""" + rng = np.random.default_rng(seed) + g = zarr.open(tracking_zarr, mode="r") + with open_ome_zarr(data_zarr, mode="r") as plate: + data_fovs = {name for name, _ in plate.positions()} + candidates = [] + # tracking OME-Zarr is nested row/col/fov (e.g. "A/2/0000"). + for row in g.keys(): + for col in g[row].keys(): + well = f"{row}/{col}" + for fov_name in g[row][col].keys(): + fov = f"{well}/{fov_name}" + if fov not in data_fovs: + continue + if wells is not None and well not in wells and fov not in wells: + continue + arr = g[f"{fov}/0"] + T, H, W = arr.shape[0], arr.shape[3], arr.shape[4] + lbl0 = np.asarray(arr[0, 0, 0]) + lblL = np.asarray(arr[T - 1, 0, 0]) + common = np.intersect1d(np.unique(lbl0), np.unique(lblL)) + common = common[common > 0] + for lid in common: + present, ok = 0, True + for t in range(0, T, 6): + m = np.asarray(arr[t, 0, 0]) == lid + if m.any(): + present += 1 + cy, cx = ndi.center_of_mass(m) + if not (half <= cy < H - half and half <= cx < W - half): + ok = False + break + if ok and present >= max(min_len // 6, 3): + candidates.append((well, fov_name, int(lid))) + if not candidates: + raise RuntimeError("no in-bounds tracks found; loosen min_track_len or wells filter") + rng.shuffle(candidates) + # spread across distinct wells first + picked, seen_wells = [], set() + for c in candidates: + if c[0] not in seen_wells: + picked.append(c) + seen_wells.add(c[0]) + if len(picked) >= n: + break + for c in candidates: + if len(picked) >= n: + break + if c not in picked: + picked.append(c) + return picked[:n] + + +def _load_track(data_zarr, tracking_zarr, well, fov, label, image_channel, final, half, norm_level): + """Return (imgs (Tc,1,final,final) z-scored+resized, times). + + Note: this takes the per-timepoint FOCUS Z-SLICE, a validated approximation + (cosine >0.997 vs stored predict-zarr embeddings) of the datamodule's full + z_extraction_window + z_reduction. Exact for 2D / in_stack_depth=1 models + where the phase channel is a single focus/center slice. + """ + lbl_arr = zarr.open(tracking_zarr, mode="r")[f"{well}/{fov}/0"] + T = lbl_arr.shape[0] + crops, times = [], [] + with open_ome_zarr(f"{data_zarr}/{well}/{fov}", mode="r") as pos: + cidx = pos.channel_names.index(image_channel) + arr = pos["0"] + H, W = arr.shape[3], arr.shape[4] + nlevel = pos.zattrs["normalization"][image_channel][norm_level] + nmeta = nlevel if norm_level == "timepoint_statistics" else {"_": nlevel} + focus = pos.zattrs["focus_slice"][image_channel]["per_timepoint"] + for t in range(T): + m = np.asarray(lbl_arr[t, 0, 0]) == label + if not m.any(): + continue + cy, cx = (int(round(v)) for v in ndi.center_of_mass(m)) + cy = min(max(cy, half), H - half) + cx = min(max(cx, half), W - half) + z = int(focus[str(t)]) + patch = arr[t, cidx, z, cy - half : cy + half, cx - half : cx + half] + if float(np.std(patch)) < 1e-6 or not np.isfinite(patch).all(): + continue # skip empty/corrupt frames + st = nmeta[str(t)] if norm_level == "timepoint_statistics" else nmeta["_"] + mean, std = float(st["mean"]), max(float(st["std"]), 1e-8) + crops.append((torch.from_numpy(patch.copy()).float()[None] - mean) / std) + times.append(t) + imgs = torch.stack(crops, dim=0) + imgs = F.interpolate(imgs, size=(final, final), mode="bilinear", align_corners=False) + return imgs, times + + +def main(cfg: dict[str, Any]) -> None: + """Run the occlusion report: pick tracks, score + occlude, write PNG/PDF.""" + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + # Preprocessing metadata: read from the training config.yaml, CONFIG overrides. + meta = _read_model_config(cfg["config_path"]) + final = int(cfg["final_patch"] if cfg.get("final_patch") is not None else meta["final_patch"]) + ref_px = ( + cfg["reference_pixel_size_xy_um"] + if cfg.get("reference_pixel_size_xy_um") is not None + else meta["reference_pixel_size_xy_um"] + ) + norm_level = cfg["norm_level"] or meta["norm_level"] + image_channel = cfg["image_channel"] or meta["image_channel"] + print(f"from config: final_patch={final}, ref_px={ref_px}, norm_level={norm_level}, image_channel={image_channel}") + + # pixel-size rescale: crop physical-area-matched window then resize to `final`. + with open_ome_zarr(cfg["data_zarr"], mode="r") as _plate: + inf_px = float(next(iter(_plate.positions()))[1].scale[-1]) + if ref_px: + scale = float(ref_px) / inf_px + initial = max(2, 2 * round(final * scale / 2)) + else: + initial = final + half = initial // 2 + print(f"pixel rescale: ref={ref_px} inf={inf_px:.4f} -> crop {initial}px resize to {final}px") + + model = _build_dynaclr({"config_path": cfg["config_path"], "ckpt_path": cfg["ckpt_path"]}).to(device).eval() + embed_fn = make_embed_fn(model) + clf_fn, classes = _classifier_closure(cfg["classifier_path"], cfg.get("scaler_path"), cfg["target_class"], embed_fn) + print( + f"classifier classes={classes}, target={cfg['target_class']}, scaler={'yes' if cfg.get('scaler_path') else 'no'}" + ) + + picked = _pick_random_tracks( + cfg["tracking_zarr"], + cfg["data_zarr"], + cfg.get("wells"), + int(cfg["n_cells"]), + int(cfg["min_track_len"]), + half, + int(cfg["seed"]), + ) + print("picked tracks:", picked) + + tracks = [] + for well, fov, label in picked: + imgs, times = _load_track( + cfg["data_zarr"], cfg["tracking_zarr"], well, fov, label, image_channel, final, half, norm_level + ) + imgs = imgs.to(device) + proba = clf_fn(imgs).flatten().cpu().numpy() + sal = occlusion_saliency( + imgs, + clf_fn, + occ_size=int(cfg["occ_size"]), + stride=int(cfg["stride"]), + fill_value=0.0, + batch_size=64, + distance=cfg["distance"], + ) + rgb = saliency_to_rgb( + sal, cmap=cfg["cmap"], clip_value=cfg["clip_value"], upsample_to=(final, final), interp_mode="bilinear" + ) + tracks.append({"label": f"{well}/{fov} L{label}", "imgs": imgs, "times": times, "proba": proba, "rgb": rgb}) + print(f" {well}/{fov} L{label}: {len(times)} frames, P {proba.min():.3f}..{proba.max():.3f}") + + _plot(tracks, cfg) + + +def _plot(tracks, cfg): + import matplotlib.pyplot as plt + + n_show = int(cfg["n_show"]) + n_cells = len(tracks) + img_rows = 2 * n_cells + fig = plt.figure(figsize=(2.0 * n_show, 2.4 * img_rows + 3), dpi=150) + gs = fig.add_gridspec(img_rows + 1, n_show, height_ratios=[*([1.0] * img_rows), 1.6]) + for ci, tr in enumerate(tracks): + times = tr["times"] + idxs = np.linspace(0, len(times) - 1, n_show).round().astype(int) + for c, i in enumerate(idxs): + img = tr["imgs"][i, 0].cpu().numpy() + axc = fig.add_subplot(gs[2 * ci, c]) + axc.imshow(img, cmap="gray") + axc.set_xticks([]) + axc.set_yticks([]) + axc.set_title(f"t={times[i]} P={tr['proba'][i]:.2f}", fontsize=7) + if c == 0: + axc.set_ylabel(f"{tr['label']}\nphase", fontsize=7) + axo = fig.add_subplot(gs[2 * ci + 1, c]) + axo.imshow(img, cmap="gray") + axo.imshow(tr["rgb"][i].cpu().numpy().transpose(1, 2, 0), alpha=0.55, interpolation="nearest") + axo.set_xticks([]) + axo.set_yticks([]) + if c == 0: + axo.set_ylabel("+occlusion", fontsize=7) + axp = fig.add_subplot(gs[img_rows, :]) + for tr in tracks: + axp.plot(tr["times"], tr["proba"], "-o", ms=3, label=tr["label"]) + axp.set_ylim(-0.02, 1.02) + axp.axhline(0.5, color="grey", lw=0.7, ls="--") + axp.set_xlabel("timepoint") + axp.set_ylabel(f"P(class={cfg['target_class']})") + axp.legend(fontsize=7) + fig.suptitle( + f"Occlusion over time — {Path(cfg['data_zarr']).stem} | occ{cfg['occ_size']}/stride{cfg['stride']}", + fontsize=10, + y=0.995, + ) + fig.tight_layout(rect=(0, 0, 1, 0.99)) + + out_dir = Path(cfg["out_dir"]) + out_dir.mkdir(parents=True, exist_ok=True) + for ext in ("png", "pdf"): + path = out_dir / f"{cfg['out_stem']}.{ext}" + fig.savefig(path) + print("wrote", path) + + +if __name__ == "__main__": + main(CONFIG) From 8549598261edd5f0773803b5b533bd0b820756cb Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 09:29:09 -0700 Subject: [PATCH 39/89] feat(dynaclr): add clustering PoP eval configs (infection/remodel/velocity) Configs for the DynaCLR embedding clustering proof-of-principle: per-marker KNN + HDBSCAN (with control-vs-infected well enrichment selection), time-binned multi-channel sample montages, and PHATE+pseudotime+velocity maps. velocity config points at the epoch105_step84800 checkpoint. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../clustering/zikv_infection_pop.yml | 73 +++++++++++++++++++ .../clustering/zikv_remodel_pop.yml | 70 ++++++++++++++++++ .../evaluation/clustering/zikv_velocity.yml | 29 ++++++++ 3 files changed, 172 insertions(+) create mode 100644 applications/dynaclr/configs/evaluation/clustering/zikv_infection_pop.yml create mode 100644 applications/dynaclr/configs/evaluation/clustering/zikv_remodel_pop.yml create mode 100644 applications/dynaclr/configs/evaluation/clustering/zikv_velocity.yml diff --git a/applications/dynaclr/configs/evaluation/clustering/zikv_infection_pop.yml b/applications/dynaclr/configs/evaluation/clustering/zikv_infection_pop.yml new file mode 100644 index 000000000..ebcf1c28d --- /dev/null +++ b/applications/dynaclr/configs/evaluation/clustering/zikv_infection_pop.yml @@ -0,0 +1,73 @@ +# Proof-of-principle: recover uninfected-vs-infected cell state from DynaCLR +# embeddings per marker, with little/no manual annotation. +# +# Two annotation-frugal arms per marker: +# - KNN (semi-supervised): train on the sparse human `infection_state` labels +# (GroupKFold by fov_name for honest metrics), then propagate to all cells. +# - HDBSCAN (unsupervised): sweep {raw X, X_pca, umap2d, phate2d}, score vs labels. +# Plus time evidence (t / hours_post_perturbation) and qualitative image crops +# split into infected/ vs uninfected/. +# +# See .ed_planning/dynaclr/clustering/PLAN.md + +title: "Infection-state clustering — proof of principle" +# Ground-truth label column (human annotation, sparse) baked into embedding obs. +label_column: infection_state +positive_class: infected +negative_class: uninfected +# The existing linear-classifier column, used only as a baseline comparison. +baseline_column: predicted_infection_state +# Grouping column for leakage-free CV (no track spans train/val). +group_column: fov_name + +# Supervised KNN arm. +knn: + k_grid: [5, 15, 30] + n_splits: 5 + +# Unsupervised HDBSCAN arm. +hdbscan: + # Which representations to cluster. "X_pca" = precomputed 32-d obsm, + # "umap2d"/"phate2d" = computed here. Raw 768-d "X" is intentionally + # excluded: density clustering on 28k x 768 is O(n^2) and does not finish + # in reasonable time, and it yields only noise (see PLAN dead-ends). + spaces: [X_pca, umap2d, phate2d] + min_cluster_size: [50, 100, 200] + min_samples: [5, 25] + # The plotted/montaged clustering is picked by max well_separation (§5, + # control-vs-infected well enrichment) among configs that are DISPLAYABLE: + # noise fraction and cluster count capped so a 72-cluster / 80%-noise config + # can't win via many tiny pure clusters. + select_max_noise_frac: 0.5 + select_max_clusters: 20 + +# Qualitative single-cell image samples. +samples: + n_samples: 24 # per class (infected / uninfected) + patch_size: 160 # y/x crop side length (pixels) + z_window: 5 # +/- slices MIP'd for fluorescence markers + # Show the viral-sensor (mCherry) channel beside each marker crop so the + # infection reporter is always visible. Skipped when marker == this channel. + pair_channel: "raw mCherry EX561 EM600-37" + pair_mip: true + +random_seed: 42 + +output_dir: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated/clustering_pop + +# One entry per marker. `image_zarr` is the source pixel store; `image_channel` +# is the zarr channel to crop; `mip` = MIP over the z-window (fluorescence) vs +# single focus slice (Phase3D). +datasets: + - marker: viral_sensor + embeddings: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated/embeddings/2025_07_24_A549_viral_sensor_ZIKV.zarr + image_zarr: /hpc/projects/organelle_phenotyping/datasets/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV.zarr + # viral_sensor is the mCherry channel (GFP is the organelle markers). This + # was previously mis-set to GFP, which cropped the wrong channel. + image_channel: "raw mCherry EX561 EM600-37" + mip: true + - marker: Phase3D + embeddings: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated/embeddings/2025_07_24_A549_Phase3D_ZIKV.zarr + image_zarr: /hpc/projects/organelle_phenotyping/datasets/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV.zarr + image_channel: "Phase3D" + mip: false diff --git a/applications/dynaclr/configs/evaluation/clustering/zikv_remodel_pop.yml b/applications/dynaclr/configs/evaluation/clustering/zikv_remodel_pop.yml new file mode 100644 index 000000000..20c108126 --- /dev/null +++ b/applications/dynaclr/configs/evaluation/clustering/zikv_remodel_pop.yml @@ -0,0 +1,70 @@ +# Proof-of-principle: recover remodeled-vs-non-remodeled organelle state from +# DynaCLR embeddings per organelle marker, with little/no manual annotation. +# +# Mirrors zikv_infection_pop.yml but for `organelle_state` (remodel/noremodel). +# Each organelle is classified from ITS OWN marker's embeddings (G3BP1 remodeling +# from G3BP1 embeddings, ER remodeling from SEC61B embeddings) — the marker that +# images that organelle. TOMM20 is NOT included: it has no embedding zarr in this +# eval set (was not embedded as a single-marker file). +# +# Note the class imbalance: G3BP1 has only ~422 remodel / 9649 labeled (~4%); +# SEC61B is better at ~1834 / 5850 (~31%). Read balanced_accuracy + per-class F1, +# not raw accuracy. +# +# See .ed_planning/dynaclr/clustering/PLAN.md + +title: "Organelle-remodeling clustering — proof of principle" +# Ground-truth label column (human annotation, sparse) baked into embedding obs. +label_column: organelle_state +positive_class: remodel +negative_class: noremodel +# The existing linear-classifier column, used only as a baseline comparison. +baseline_column: predicted_organelle_state +# Grouping column for leakage-free CV (no track spans train/val). +group_column: fov_name + +# Supervised KNN arm. +knn: + k_grid: [5, 15, 30] + n_splits: 5 + +# Unsupervised HDBSCAN arm. +hdbscan: + # "X_pca" = precomputed 32-d obsm; "umap2d"/"phate2d" computed here. Raw 768-d + # "X" excluded (O(n^2), all-noise — see PLAN dead-ends). + spaces: [X_pca, umap2d, phate2d] + min_cluster_size: [50, 100, 200] + min_samples: [5, 25] + # Pick the plotted/montaged clustering by max well_separation (§5, control-vs- + # infected enrichment) among displayable configs (noise + cluster-count capped). + select_max_noise_frac: 0.5 + select_max_clusters: 20 + +# Qualitative single-cell image samples. +samples: + n_samples: 24 # per class (remodel / noremodel) + patch_size: 160 # y/x crop side length (pixels) + z_window: 5 # +/- slices MIP'd for fluorescence markers + # Show the viral-sensor (mCherry) channel beside each organelle-marker crop, + # so remodeling can be read against the infection reporter of the same cell. + pair_channel: "raw mCherry EX561 EM600-37" + pair_mip: true + +random_seed: 42 + +output_dir: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated/clustering_remodel_pop + +# One entry per organelle marker. Both G3BP1 and SEC61B are GFP-tagged reporters +# (imaged in different wells of the same plate); the well/FOV in obs disambiguates +# which organelle. Both are fluorescence -> MIP over the focus z-window. +datasets: + - marker: G3BP1 + embeddings: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated/embeddings/2025_07_24_A549_G3BP1_ZIKV.zarr + image_zarr: /hpc/projects/organelle_phenotyping/datasets/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV.zarr + image_channel: "raw GFP EX488 EM525-45" + mip: true + - marker: SEC61B + embeddings: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated/embeddings/2025_07_24_A549_SEC61_ZIKV.zarr + image_zarr: /hpc/projects/organelle_phenotyping/datasets/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV/2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV.zarr + image_channel: "raw GFP EX488 EM525-45" + mip: true diff --git a/applications/dynaclr/configs/evaluation/clustering/zikv_velocity.yml b/applications/dynaclr/configs/evaluation/clustering/zikv_velocity.yml new file mode 100644 index 000000000..d838c04ea --- /dev/null +++ b/applications/dynaclr/configs/evaluation/clustering/zikv_velocity.yml @@ -0,0 +1,29 @@ +# PHATE + diffusion-pseudotime + velocity maps for the phenotype-flow analysis (§6). +# +# Discover whether cells follow an ordered control -> remodel progression: +# - PHATE layout (preserves trajectory geometry, unlike UMAP which fragmented it), +# - diffusion pseudotime rooted at control-well cells (annotation-free), +# - measured velocity from single-cell tracks (UMAP(t+1)-UMAP(t) analog, but on PHATE), +# - validation: Spearman(pseudotime, real t) should be strongly positive. +# +# Runs the three markers requested: viral_sensor, Phase3D, G3BP1. +# See .ed_planning/dynaclr/clustering/PLAN.md §6. + +random_seed: 42 +output_dir: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated_epoch105_step84800/clustering_pop + +# control_ref subtracts the per-timepoint control-well centroid before differencing +# the velocity, removing background drift shared by all cells. Probed 2026-07-15: +# it HELPS the organelle channel (G3BP1 infected-only coherence 0.018 -> 0.052, ~3x) +# but slightly HURTS the reporter (viral_sensor 0.030 -> 0.021), whose motion is +# already mostly infection signal. So: on for organelle/label-free, off for reporter. +datasets: + - marker: viral_sensor + embeddings: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated_epoch105_step84800/embeddings/2025_07_24_A549_viral_sensor_ZIKV.zarr + control_ref: false + - marker: Phase3D + embeddings: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated_epoch105_step84800/embeddings/2025_07_24_A549_Phase3D_ZIKV.zarr + control_ref: true + - marker: G3BP1 + embeddings: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/evaluations/infectomics-annotated_epoch105_step84800/embeddings/2025_07_24_A549_G3BP1_ZIKV.zarr + control_ref: true From 5b18944fbb41d61d45b96c2fb781f2f0b0f15543 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 09:29:26 -0700 Subject: [PATCH 40/89] feat(dynaclr): add embedding clustering proof-of-principle scripts Standalone analysis scripts for recovering infection/remodeling state from DynaCLR embeddings: - cluster_cell_state.py: per-marker semi-supervised KNN (grouped-CV + control-well-negative mode) + unsupervised HDBSCAN sweep with control-vs- infected well-enrichment selection; time-binned multi-channel crop sampling. - make_sample_montage.py / cluster_montage.py: decoupled montage builders (class x time-bin; top-5 infection-enriched clusters), phase/organelle/sensor. - velocity_map.py: PHATE + control-rooted diffusion pseudotime + high-dim (scVelo-style) velocity field. - track_dynamics.py: per-track straightness/curvature + velocity-direction phenotype clustering (heterogeneity-aware trajectory probe). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../scripts/clustering/cluster_cell_state.py | 655 ++++++++++++++++++ .../scripts/clustering/cluster_montage.py | 111 +++ .../scripts/clustering/make_sample_montage.py | 116 ++++ .../scripts/clustering/track_dynamics.py | 257 +++++++ .../scripts/clustering/velocity_map.py | 273 ++++++++ 5 files changed, 1412 insertions(+) create mode 100644 applications/dynaclr/scripts/clustering/cluster_cell_state.py create mode 100644 applications/dynaclr/scripts/clustering/cluster_montage.py create mode 100644 applications/dynaclr/scripts/clustering/make_sample_montage.py create mode 100644 applications/dynaclr/scripts/clustering/track_dynamics.py create mode 100644 applications/dynaclr/scripts/clustering/velocity_map.py diff --git a/applications/dynaclr/scripts/clustering/cluster_cell_state.py b/applications/dynaclr/scripts/clustering/cluster_cell_state.py new file mode 100644 index 000000000..3b4471688 --- /dev/null +++ b/applications/dynaclr/scripts/clustering/cluster_cell_state.py @@ -0,0 +1,655 @@ +"""Proof-of-principle: recover a binary cell state from DynaCLR embeddings. + +The state is configurable (``label_column`` + ``positive_class`` / ``negative_class``): +e.g. ``infection_state`` (infected vs uninfected) or ``organelle_state`` (remodel vs +noremodel). Per marker, two annotation-frugal arms: + +- **KNN (semi-supervised):** train on the sparse human labels + (``GroupKFold`` by ``fov_name`` for honest metrics), then propagate to every cell. +- **HDBSCAN (unsupervised):** sweep several representations {``X_pca``, UMAP-2D, + PHATE-2D} and score clusters against the human labels (ARI / NMI / purity). + +Also colors low-dimensional embeddings by time (``t`` / ``hours_post_perturbation``) +and quantifies the time-vs-state trend, and saves qualitative single-cell image crops +split by predicted class into ``/`` and ``/`` for visual QC. Build +the combined montage from those crops with ``make_sample_montage.py``. + +Usage +----- +python cluster_cell_state.py -c zikv_infection_pop.yml +python cluster_cell_state.py -c zikv_remodel_pop.yml +""" + +import argparse +from pathlib import Path + +import anndata as ad +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import phate +import umap +from iohub import open_ome_zarr +from scipy.stats import pointbiserialr, spearmanr +from sklearn.cluster import HDBSCAN +from sklearn.metrics import ( + adjusted_rand_score, + balanced_accuracy_score, + f1_score, + normalized_mutual_info_score, + roc_auc_score, +) +from sklearn.model_selection import GroupKFold, cross_val_predict +from sklearn.neighbors import KNeighborsClassifier + +from viscy_utils.cli_utils import format_markdown_table, load_config + +# The two label values, set from config in main(); defaults are the infection task. +POSITIVE_CLASS = "infected" +NEGATIVE_CLASS = "uninfected" +# Time bins (equal-width over the full t range) used for image sampling. +BIN_LABELS = ("early", "early-mid", "mid", "mid-late", "late") + + +def _as_str_array(series: pd.Series) -> np.ndarray: + """Materialize an Arrow-backed obs column as a plain numpy object array. + + sklearn indexing raises ``TypeError: only integer scalar arrays can be + converted to a scalar index`` on Arrow-backed pandas columns, so convert + through a Python list first. + """ + return np.asarray(series.astype(str).tolist()) + + +def _labeled_mask(labels: np.ndarray) -> np.ndarray: + """Boolean mask of cells with a real (non-missing) human label.""" + return np.isin(labels, [POSITIVE_CLASS, NEGATIVE_CLASS]) + + +def run_knn( + embeddings: np.ndarray, + labels: np.ndarray, + groups: np.ndarray, + k_grid: list[int], + n_splits: int, +) -> tuple[list[dict], KNeighborsClassifier, int]: + """Evaluate KNN with FOV-grouped CV and return the best refit classifier. + + Parameters + ---------- + embeddings : np.ndarray + Embeddings of the labeled subset, shape (n_labeled, n_features). + labels : np.ndarray + Human labels of the labeled subset (``infected`` / ``uninfected``). + groups : np.ndarray + Group id per labeled cell (``fov_name``) for ``GroupKFold``. + k_grid : list[int] + Neighbor counts to sweep. + n_splits : int + Number of CV folds. + + Returns + ------- + tuple[list[dict], KNeighborsClassifier, int] + Per-k metric rows, the classifier refit on all labeled data at the best + k (by balanced accuracy), and that best k. + """ + y_pos = (labels == POSITIVE_CLASS).astype(int) + cv = GroupKFold(n_splits=n_splits) + rows = [] + best = None + for k in k_grid: + clf = KNeighborsClassifier(n_neighbors=k) + classes = clf.fit(embeddings, labels).classes_ + pos_idx = list(classes).index(POSITIVE_CLASS) + # Single CV pass: derive the predicted class from the out-of-fold proba. + proba = cross_val_predict(clf, embeddings, labels, cv=cv, groups=groups, method="predict_proba") + pred = classes[proba.argmax(axis=1)] + bal = balanced_accuracy_score(labels, pred) + row = { + "arm": "knn", + "config": f"k={k}", + "balanced_accuracy": bal, + "f1": f1_score(y_pos, (pred == POSITIVE_CLASS).astype(int)), + "roc_auc": roc_auc_score(y_pos, proba[:, pos_idx]), + "n_labeled": int(len(labels)), + } + rows.append(row) + if best is None or bal > best[0]: + best = (bal, k) + best_k = best[1] + refit = KNeighborsClassifier(n_neighbors=best_k).fit(embeddings, labels) + return rows, refit, best_k + + +def _compute_space(name: str, X: np.ndarray, X_pca: np.ndarray, seed: int) -> np.ndarray: + """Return the representation matrix for a named clustering space.""" + if name == "X": + return X + if name == "X_pca": + return X_pca + if name == "umap2d": + return umap.UMAP(n_components=2, random_state=seed).fit_transform(X) + if name == "phate2d": + return phate.PHATE(n_components=2, random_state=seed, verbose=False).fit_transform(X) + raise ValueError(f"Unknown clustering space: {name}") + + +def _cluster_purity(clusters: np.ndarray, labels: np.ndarray) -> float: + """Majority-vote purity of clusters against labels, ignoring noise (-1).""" + keep = clusters != -1 + if not keep.any(): + return 0.0 + correct = 0 + for c in np.unique(clusters[keep]): + members = labels[keep][clusters[keep] == c] + vals, counts = np.unique(members, return_counts=True) + correct += counts.max() + return correct / keep.sum() + + +def _well_separation(clusters: np.ndarray, well_infected: np.ndarray) -> tuple[float, float]: + """Score how well a clustering sorts cells by infected-vs-control WELL condition. + + Uses the experiment's well layout (``perturbation``) as annotation-free + biological structure: control wells are clean negatives, infected wells a + mixture. Returns: + + - ``well_separation``: size-weighted mean |cluster infected-well frac − global + frac| over non-noise clusters, normalized so 0 = no separation, 1 = every + cluster is well-pure. Rewards clusterings that split control from infected + at ANY granularity (not just the 2-cluster ARI winner). + - ``max_infected_frac``: the highest infected-well fraction of any cluster — + the most infection-enriched cluster found. + """ + keep = clusters != -1 + if not keep.any(): + return 0.0, 0.0 + c, w = clusters[keep], well_infected[keep] + global_frac = float(w.mean()) + denom = 2.0 * global_frac * (1.0 - global_frac) # max achievable weighted MAD + wmad, max_frac = 0.0, 0.0 + for cid in np.unique(c): + m = c == cid + frac = float(w[m].mean()) + wmad += (m.sum() / keep.sum()) * abs(frac - global_frac) + max_frac = max(max_frac, frac) + return (wmad / denom if denom > 0 else 0.0), max_frac + + +def run_hdbscan( + reps: dict[str, np.ndarray], + labeled_mask: np.ndarray, + labels_labeled: np.ndarray, + grid_mcs: list[int], + grid_ms: list[int], + well_infected: np.ndarray, +) -> list[dict]: + """Sweep HDBSCAN over representations and parameters; score vs labels + wells. + + Parameters + ---------- + reps : dict[str, np.ndarray] + Named representation matrices computed on all cells. + labeled_mask : np.ndarray + Boolean mask selecting the human-labeled cells (for label scoring only). + labels_labeled : np.ndarray + Human labels of the labeled subset. + grid_mcs, grid_ms : list[int] + ``min_cluster_size`` / ``min_samples`` values to sweep. + well_infected : np.ndarray + Per-cell bool: True if the cell is from an infected well (``perturbation``). + Annotation-free biological structure for cluster enrichment scoring. + + Returns + ------- + list[dict] + One metric row per (space, min_cluster_size, min_samples), including both + label-based (ari/nmi/purity) and well-based (well_separation, + max_infected_frac) scores. + """ + rows = [] + for space, mat in reps.items(): + for mcs in grid_mcs: + for ms in grid_ms: + clusters = HDBSCAN(min_cluster_size=mcs, min_samples=ms).fit_predict(mat) + cl_lab = clusters[labeled_mask] + n_clusters = int(len(set(clusters)) - (1 if -1 in clusters else 0)) + well_sep, max_inf = _well_separation(clusters, well_infected) + rows.append( + { + "arm": "hdbscan", + "config": f"{space} mcs={mcs} ms={ms}", + "ari": adjusted_rand_score(labels_labeled, cl_lab), + "nmi": normalized_mutual_info_score(labels_labeled, cl_lab), + "purity": _cluster_purity(cl_lab, labels_labeled), + "well_separation": well_sep, + "max_infected_frac": max_inf, + "n_clusters": n_clusters, + "noise_frac": float((clusters == -1).mean()), + } + ) + return rows + + +def time_correlations( + t: np.ndarray, + hpp: np.ndarray, + labeled_mask: np.ndarray, + labels_labeled: np.ndarray, + knn_proba_pos: np.ndarray, +) -> list[dict]: + """Correlate time against human label (labeled subset) and KNN proba (all cells).""" + y_pos = (labels_labeled == POSITIVE_CLASS).astype(int) + rows = [] + for tname, tvals in (("t", t), ("hours_post_perturbation", hpp)): + pb = pointbiserialr(y_pos, tvals[labeled_mask]) + sp = spearmanr(tvals, knn_proba_pos) + rows.append( + { + "time_col": tname, + "pointbiserial_vs_human_label": pb.statistic, + "spearman_vs_knn_proba": sp.statistic, + } + ) + return rows + + +def _category_color(category: str, index: int) -> str: + """Stable color for a category so a label reads the same across every panel. + + Positive class -> red, negative -> blue, missing/noise -> gray; anything else + (e.g. HDBSCAN cluster ids) falls back to tab10 by index. Reads the module + class globals so it tracks whatever ``main()`` bound from config. + """ + fixed = {POSITIVE_CLASS: "tab:red", NEGATIVE_CLASS: "tab:blue", "nan": "lightgray", "-1": "lightgray"} + if category in fixed: + return fixed[category] + return plt.cm.tab10(index % 10) + + +def plot_scatter(coords: np.ndarray, color_by: dict[str, np.ndarray], out_path: Path, title: str) -> None: + """Save a multi-panel 2D scatter, one panel per coloring. + + Categorical panels use a fixed label -> color map (``CLASS_COLORS``) so the + same class is colored identically in every panel regardless of how many + categories that panel happens to contain. + """ + n = len(color_by) + fig, axes = plt.subplots(1, n, figsize=(4 * n, 4), squeeze=False) + for ax, (name, values) in zip(axes[0], color_by.items(), strict=True): + if values.dtype.kind in "OU": + categories = sorted(set(values.tolist())) + color_map = {c: _category_color(c, i) for i, c in enumerate(categories)} + point_colors = [color_map[v] for v in values] + ax.scatter(coords[:, 0], coords[:, 1], c=point_colors, s=2, alpha=0.5) + handles = [plt.Line2D([], [], marker="o", ls="", color=color_map[c]) for c in categories] + ax.legend(handles, categories, fontsize=6, markerscale=1.5) + else: + sc = ax.scatter(coords[:, 0], coords[:, 1], c=values, cmap="viridis", s=2, alpha=0.5) + fig.colorbar(sc, ax=ax, shrink=0.7) + ax.set_title(name, fontsize=9) + ax.set_xticks([]) + ax.set_yticks([]) + fig.suptitle(title) + fig.tight_layout() + fig.savefig(out_path, dpi=150) + plt.close(fig) + + +# Channels shown for each sampled cell (biological context), matching the +# witness/prob-sample figures. (display label, zarr channel, reduction). Absent +# channels are skipped per dataset. Phase = per-FOV focus slice; fluor = full MIP. +COMPANION_CHANNELS = [ + ("phase", "Phase3D", "center"), + ("organelle", "raw GFP EX488 EM525-45", "mip"), + ("sensor", "raw mCherry EX561 EM600-37", "mip"), +] + + +def _focus_z(pos, n_z: int) -> int: + """Per-FOV focus slice from zattrs (``focus_slice..fov_statistics``), else mid-stack.""" + fs = dict(pos.zattrs).get("focus_slice", {}) + for ch in fs.values(): + mean = ch.get("fov_statistics", {}).get("z_focus_mean") + if mean is not None: + return int(np.clip(round(mean), 0, n_z - 1)) + return n_z // 2 + + +def _crop_channel(pos, ch_idx: int, t: int, y: int, x: int, half: int, reduction: str) -> np.ndarray: + """Crop patch_size around (y,x); ``center`` = per-FOV focus slice, else full-stack MIP.""" + arr = pos.data # (T, C, Z, Y, X) + ymax, xmax = arr.shape[-2], arr.shape[-1] + y0, x0 = max(0, y - half), max(0, x - half) + y1, x1 = min(ymax, y0 + 2 * half), min(xmax, x0 + 2 * half) + y0, x0 = y1 - 2 * half, x1 - 2 * half + stack = np.asarray(arr[t, ch_idx, :, y0:y1, x0:x1]) # (Z, Y, X) + if reduction == "center": + return stack[_focus_z(pos, stack.shape[0])] + return stack.max(axis=0) + + +def _norm01(a: np.ndarray) -> np.ndarray: + """Min-max normalize a crop to [0, 1] for display (per-crop contrast).""" + lo, hi = float(a.min()), float(a.max()) + return (a - lo) / (hi - lo) if hi > lo else np.zeros_like(a, dtype=float) + + +def save_image_samples( + obs: pd.DataFrame, + knn_pred: np.ndarray, + knn_proba_pos: np.ndarray, + image_zarr: str, + n_samples: int, + patch_size: int, + out_dir: Path, +) -> None: + """Crop and save top-confidence KNN-predicted cells across 5 time bins. + + For each class, the full ``t`` range is split into ``len(BIN_LABELS)`` bins; + within each bin the highest-confidence cells of that class are chosen, so the + montage spans early -> late. ``n_samples`` is divided evenly across bins. + + Each cell is saved once **per available companion channel** (phase / organelle + / sensor) under ``//``, filename prefixed + ``bin{idx}_{label}_`` so the montage step can group by (bin, channel). Phase = + per-FOV focus slice; fluorescence = full-stack MIP. Channels absent from the + zarr are skipped. The channel set comes from COMPANION_CHANNELS, so the + marker's own ``channel`` no longer needs to be passed. + """ + half = patch_size // 2 + n_bins = len(BIN_LABELS) + per_bin = max(1, n_samples // n_bins) + t_all = obs["t"].to_numpy(dtype=float) + edges = np.linspace(t_all.min(), t_all.max(), n_bins + 1) + # Right-closed on the last edge so the max-t cells fall in the final bin. + bin_of = np.clip(np.digitize(t_all, edges[1:-1]), 0, n_bins - 1) + with open_ome_zarr(image_zarr, mode="r") as plate: + available = set(plate.channel_names) + channels = [(lbl, ch, red) for (lbl, ch, red) in COMPANION_CHANNELS if ch in available] + for cls, want_pos in ((POSITIVE_CLASS, True), (NEGATIVE_CLASS, False)): + conf = knn_proba_pos if want_pos else 1.0 - knn_proba_pos + for b, label in enumerate(BIN_LABELS): + sel = np.flatnonzero((knn_pred == cls) & (bin_of == b)) + chosen = sel[np.argsort(conf[sel])[::-1][:per_bin]] + for i in chosen: + r = obs.iloc[int(i)] + fov = str(r["fov_name"]) + t = int(r["t"]) + y, x = int(r["y"]), int(r["x"]) + pos = plate[fov] + name = f"bin{b}_{label}_{fov.replace('/', '_')}_track{int(r['track_id'])}_t{t}.png" + for ch_lbl, ch, red in channels: + ch_dir = out_dir / cls / ch_lbl + ch_dir.mkdir(parents=True, exist_ok=True) + crop = _crop_channel(pos, plate.channel_names.index(ch), t, y, x, half, red) + plt.imsave(ch_dir / name, _norm01(crop), cmap="gray") + + +def save_cluster_samples( + obs: pd.DataFrame, + clusters: np.ndarray, + coords: np.ndarray, + well_infected: np.ndarray, + image_zarr: str, + n_samples: int, + patch_size: int, + out_dir: Path, + top_k: int = 5, +) -> None: + """Crop representative cells for the top-K most infection-enriched clusters. + + Clusters are ranked by **infected-well fraction** (§5, annotation-free) and the + ``top_k`` most enriched are kept — the candidate infected/remodeled phenotypes. + Within a cluster the cells closest to the cluster centroid in the clustering + space (``coords``) are the most representative. Each cell is saved once **per + available companion channel** (phase / organelle / sensor) under + ``rank{r}_cluster{id}_inf{frac}//`` so the montage can order clusters + by enrichment and stack channels. Noise (``-1``) is skipped. + """ + half = patch_size // 2 + ids = [c for c in sorted(set(clusters)) if c != -1] + enrich = {c: float(well_infected[clusters == c].mean()) for c in ids} + top = sorted(ids, key=lambda c: enrich[c], reverse=True)[:top_k] + with open_ome_zarr(image_zarr, mode="r") as plate: + available = set(plate.channel_names) + channels = [(lbl, ch, red) for (lbl, ch, red) in COMPANION_CHANNELS if ch in available] + for rank_c, cid in enumerate(top): + members = np.flatnonzero(clusters == cid) + centroid = coords[members].mean(axis=0) + order = np.argsort(((coords[members] - centroid) ** 2).sum(axis=1)) + chosen = members[order[:n_samples]] + cl_dir = out_dir / f"rank{rank_c}_cluster{cid}_inf{enrich[cid]:.2f}" + for i in chosen: + r = obs.iloc[int(i)] + fov = str(r["fov_name"]) + t = int(r["t"]) + y, x = int(r["y"]), int(r["x"]) + pos = plate[fov] + name = f"{fov.replace('/', '_')}_track{int(r['track_id'])}_t{t}.png" + for ch_lbl, ch, red in channels: + ch_dir = cl_dir / ch_lbl + ch_dir.mkdir(parents=True, exist_ok=True) + crop = _crop_channel(pos, plate.channel_names.index(ch), t, y, x, half, red) + plt.imsave(ch_dir / name, _norm01(crop), cmap="gray") + + +def process_marker(entry: dict, cfg: dict, out_root: Path) -> dict: + """Run both arms + plots + samples for one marker; return summary rows.""" + marker = entry["marker"] + out_dir = out_root / marker + out_dir.mkdir(parents=True, exist_ok=True) + seed = cfg["random_seed"] + + adata = ad.read_zarr(entry["embeddings"]) + adata.obs_names_make_unique() + obs = adata.obs + X = np.asarray(adata.X, dtype=np.float64) + X_pca = np.asarray(obs_pca) if (obs_pca := adata.obsm.get("X_pca")) is not None else X + + labels = _as_str_array(obs[cfg["label_column"]]) + groups = _as_str_array(obs[cfg["group_column"]]) + baseline = _as_str_array(obs[cfg["baseline_column"]]) + t = obs["t"].to_numpy(dtype=float) + hpp = obs["hours_post_perturbation"].to_numpy(dtype=float) + # Well condition (annotation-free biological structure, §5): control wells are + # clean negatives; infected wells a mixture. `perturbation` == "infected". + perturbation = _as_str_array(obs["perturbation"]) + well_infected = perturbation == "infected" + + lm = _labeled_mask(labels) + labels_lab = labels[lm] + print(f"### {marker}: {lm.sum()} / {len(labels)} human-labeled cells", flush=True) + + # --- Supervised KNN arm (metrics + propagation) --- + print(f"[{marker}] KNN grouped-CV ...", flush=True) + knn_rows, refit, best_k = run_knn(X[lm], labels_lab, groups[lm], cfg["knn"]["k_grid"], cfg["knn"]["n_splits"]) + knn_pred = refit.predict(X) + pos_idx = list(refit.classes_).index(POSITIVE_CLASS) + knn_proba_pos = refit.predict_proba(X)[:, pos_idx] + + # Well-stratified eval: accuracy of the propagated labels within control wells + # (where truth is ~all-negative) vs infected wells (the hard, mixed case). + ctrl = ~well_infected + knn_rows.append( + { + "arm": "knn_well_stratified", + "config": "control_well (frac predicted positive)", + "balanced_accuracy": float((knn_pred[ctrl] == POSITIVE_CLASS).mean()), + "n_labeled": int(ctrl.sum()), + } + ) + knn_rows.append( + { + "arm": "knn_well_stratified", + "config": "infected_well (frac predicted positive)", + "balanced_accuracy": float((knn_pred[well_infected] == POSITIVE_CLASS).mean()), + "n_labeled": int(well_infected.sum()), + } + ) + + # Control-negatives KNN (annotation-free): negatives = control-well cells, + # positives = human-positive cells. Grouped-CV, reported alongside the + # human-label KNN. Helps the imbalanced remodel task (few human positives). + cn_pos = well_infected & (labels == POSITIVE_CLASS) + cn_mask = ctrl | cn_pos + if cn_pos.sum() >= cfg["knn"]["n_splits"] and ctrl.sum() >= cfg["knn"]["n_splits"]: + cn_labels = np.where(cn_pos, POSITIVE_CLASS, NEGATIVE_CLASS)[cn_mask] + cn_rows, _, _ = run_knn(X[cn_mask], cn_labels, groups[cn_mask], cfg["knn"]["k_grid"], cfg["knn"]["n_splits"]) + for r in cn_rows: + r["arm"] = "knn_control_neg" + knn_rows.extend(cn_rows) + + # Baseline row: existing linear classifier vs human labels on the same subset. + baseline_row = { + "arm": "baseline_lc", + "config": cfg["baseline_column"], + "balanced_accuracy": balanced_accuracy_score(labels_lab, baseline[lm]), + "f1": f1_score((labels_lab == POSITIVE_CLASS).astype(int), (baseline[lm] == POSITIVE_CLASS).astype(int)), + "n_labeled": int(lm.sum()), + } + + # --- Unsupervised HDBSCAN arm --- + print(f"[{marker}] computing spaces {cfg['hdbscan']['spaces']} (UMAP/PHATE ~1-2 min each) ...", flush=True) + reps = {name: _compute_space(name, X, X_pca, seed) for name in cfg["hdbscan"]["spaces"]} + print(f"[{marker}] HDBSCAN sweep ...", flush=True) + hdb_rows = run_hdbscan( + reps, lm, labels_lab, cfg["hdbscan"]["min_cluster_size"], cfg["hdbscan"]["min_samples"], well_infected + ) + + # --- Time evidence --- + time_rows = time_correlations(t, hpp, lm, labels_lab, knn_proba_pos) + pd.DataFrame(time_rows).to_csv(out_dir / "time_correlation.csv", index=False) + + # --- Plots (UMAP + PHATE), colored by every signal --- + # Select the montaged/plotted clustering by WELL SEPARATION (annotation-free, + # §5) among USABLE configs — this avoids the ARI-vs-binary-label collapse to + # ~2 clusters and surfaces infection-enriched structure, while a noise cap and + # cluster-count cap keep the pick displayable (a 72-cluster / 80%-noise config + # can win raw well_separation via many tiny pure clusters). Progressive + # fallback: usable → any ≥2-cluster → best ARI. + sel_cfg = cfg.get("hdbscan", {}) + max_noise = sel_cfg.get("select_max_noise_frac", 0.5) + max_k = sel_cfg.get("select_max_clusters", 20) + usable = [r for r in hdb_rows if 2 <= r["n_clusters"] <= max_k and r["noise_frac"] <= max_noise] + multi = [r for r in hdb_rows if r["n_clusters"] >= 2] + if usable: + best_hdb = max(usable, key=lambda r: r["well_separation"]) + elif multi: + best_hdb = max(multi, key=lambda r: r["well_separation"]) + else: + best_hdb = max(hdb_rows, key=lambda r: r["ari"]) + best_space = best_hdb["config"].split()[0] + best_clusters = HDBSCAN( + min_cluster_size=int(best_hdb["config"].split("mcs=")[1].split()[0]), + min_samples=int(best_hdb["config"].split("ms=")[1]), + ).fit_predict(reps[best_space]) + for space in ("umap2d", "phate2d"): + coords = reps.get(space) + if coords is None: + coords = _compute_space(space, X, X_pca, seed) + color_by = { + "human_label": labels, + "predicted_lc": baseline, + "knn_propagated": knn_pred, + "hdbscan_cluster": best_clusters.astype(str), + "t": t, + "hours_post_perturbation": hpp, + } + plot_scatter(coords, color_by, out_dir / f"scatter_{space}.png", f"{marker} — {space}") + + # Persist the best HDBSCAN assignment per cell so downstream steps (e.g. the + # cluster montage) need not recompute clustering. Space recorded in the header. + pd.DataFrame( + { + "fov_name": obs["fov_name"].astype(str).to_numpy(), + "track_id": obs["track_id"].to_numpy(), + "t": obs["t"].to_numpy(), + "hdbscan_cluster": best_clusters, + "cluster_space": best_space, + } + ).to_csv(out_dir / "cluster_assignments.csv", index=False) + + # --- Qualitative image samples --- + print(f"[{marker}] cropping image samples ...", flush=True) + save_image_samples( + obs, + knn_pred, + knn_proba_pos, + entry["image_zarr"], + cfg["samples"]["n_samples"], + cfg["samples"]["patch_size"], + out_dir / "samples", + ) + + # Representative crops for the top-K most infection-enriched HDBSCAN clusters + # (multi-channel), so the candidate infected phenotypes can be inspected. + print(f"[{marker}] cropping cluster samples ...", flush=True) + save_cluster_samples( + obs, + best_clusters, + reps[best_space], + well_infected, + entry["image_zarr"], + cfg["samples"]["n_samples"], + cfg["samples"]["patch_size"], + out_dir / "cluster_samples", + top_k=cfg["samples"].get("top_clusters", 5), + ) + + for r in knn_rows + [baseline_row] + hdb_rows: + r["marker"] = marker + return {"metrics": knn_rows + [baseline_row] + hdb_rows, "knn_best_k": best_k, "best_hdb": best_hdb} + + +def main() -> None: + """Parse config, run every marker, and write the combined summary.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-c", "--config", required=True, type=Path) + args = parser.parse_args() + + cfg = load_config(args.config) + # Bind the two label values from config (defaults keep the infection task). + global POSITIVE_CLASS, NEGATIVE_CLASS + POSITIVE_CLASS = cfg.get("positive_class", POSITIVE_CLASS) + NEGATIVE_CLASS = cfg.get("negative_class", NEGATIVE_CLASS) + + out_root = Path(cfg["output_dir"]) + out_root.mkdir(parents=True, exist_ok=True) + + all_metrics = [] + for entry in cfg["datasets"]: + result = process_marker(entry, cfg, out_root) + all_metrics.extend(result["metrics"]) + + summary = pd.DataFrame(all_metrics) + summary.to_csv(out_root / "metrics_summary.csv", index=False) + + headers = [ + "marker", + "arm", + "config", + "balanced_accuracy", + "roc_auc", + "ari", + "nmi", + "purity", + "well_separation", + "max_infected_frac", + "n_clusters", + ] + md = format_markdown_table( + [{h: row.get(h, "") for h in headers} for row in all_metrics], + title=cfg.get("title", f"{cfg['label_column']} clustering — proof of principle"), + headers=headers, + ) + (out_root / "summary.md").write_text(md) + print(md) + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/scripts/clustering/cluster_montage.py b/applications/dynaclr/scripts/clustering/cluster_montage.py new file mode 100644 index 000000000..444d8c94c --- /dev/null +++ b/applications/dynaclr/scripts/clustering/cluster_montage.py @@ -0,0 +1,111 @@ +"""Build a top-K infection-enriched HDBSCAN-cluster montage from saved crops. + +Reads the representative crop PNGs written by ``cluster_cell_state.py`` under +``//cluster_samples/rank{r}_cluster{id}_inf{frac}//`` +and lays out one montage per marker: rows = **cluster x channel** (phase / +organelle / sensor stacked per cluster), columns = representative cells. Clusters +are already the top-K most infection-enriched (§5 well-condition scoring), ordered +by ``rank``. Runs independently of the clustering step (crops are on disk). + +Usage +----- +python cluster_montage.py -c zikv_remodel_pop.yml +""" + +import argparse +import re +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.image as mpimg +import matplotlib.pyplot as plt + +from viscy_utils.cli_utils import load_config + +# rank{r}_cluster{id}_inf{frac} — capture rank, cluster id, infected-well frac. +_CLUSTER_DIR = re.compile(r"^rank(\d+)_cluster(-?\d+)_inf([\d.]+)$") +CHANNEL_ORDER = ("phase", "organelle", "sensor") + + +def _cluster_dirs(cluster_root: Path) -> list[tuple[int, int, str, Path]]: + """List (rank, cluster id, infected-frac label, dir), ordered by rank.""" + found = [] + for d in cluster_root.glob("rank*_cluster*"): + m = _CLUSTER_DIR.match(d.name) + if d.is_dir() and m: + found.append((int(m.group(1)), int(m.group(2)), m.group(3), d)) + return sorted(found) + + +def make_cluster_montage(cluster_root: Path, out_path: Path, marker: str) -> None: + """Tile crops as rows = (cluster x channel), columns = cells. + + Parameters + ---------- + cluster_root : Path + Directory containing ``rank{r}_cluster{id}_inf{frac}//`` subdirs. + out_path : Path + Where to write the montage PNG. + marker : str + Marker name, used in the figure title. + """ + clusters = _cluster_dirs(cluster_root) + if not clusters: + raise FileNotFoundError(f"No rank*_cluster*/ crop dirs under {cluster_root}") + channels = [c for c in CHANNEL_ORDER if any((d / c).is_dir() for *_, d in clusters)] + # Shared cell filenames per cluster come from the first channel present. + cells = {} + for rank, cid, frac, d in clusters: + ref = next((c for c in channels if (d / c).is_dir()), None) + cells[(rank, cid, frac)] = sorted(p.name for p in (d / ref).glob("*.png")) if ref else [] + cols = max((len(v) for v in cells.values()), default=0) + if cols == 0: + raise FileNotFoundError(f"No crops under {cluster_root}") + + n_rows = len(clusters) * len(channels) + fig, axes = plt.subplots(n_rows, cols, figsize=(cols * 1.4, n_rows * 1.5), squeeze=False) + for ax in axes.flat: + ax.axis("off") + + for ci_c, (rank, cid, frac, d) in enumerate(clusters): + for chi, ch in enumerate(channels): + row = ci_c * len(channels) + chi + label = f"cluster {cid}\ninf={frac}\n{ch}" if chi == 0 else ch + axes[row][0].set_ylabel(label, rotation=0, ha="right", va="center", fontsize=8) + axes[row][0].axis("on") + axes[row][0].set_xticks([]) + axes[row][0].set_yticks([]) + for col, fname in enumerate(cells[(rank, cid, frac)]): + fpath = d / ch / fname + if fpath.exists(): + axes[row][col].imshow(mpimg.imread(fpath), cmap="gray") + + fig.suptitle( + f"{marker}\ntop-{len(clusters)} infection-enriched HDBSCAN clusters " + f"(cluster x channel: phase / organelle / sensor)", + fontsize=12, + ) + fig.tight_layout() + fig.savefig(out_path, dpi=130) + plt.close(fig) + print(f"wrote {out_path}") + + +def main() -> None: + """Build a top-K cluster montage for every marker in the config.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-c", "--config", required=True, type=Path) + args = parser.parse_args() + + cfg = load_config(args.config) + out_root = Path(cfg["output_dir"]) + for entry in cfg["datasets"]: + marker = entry["marker"] + cluster_root = out_root / marker / "cluster_samples" + make_cluster_montage(cluster_root, cluster_root / "montage_clusters.png", marker) + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/scripts/clustering/make_sample_montage.py b/applications/dynaclr/scripts/clustering/make_sample_montage.py new file mode 100644 index 000000000..d40a5a1e0 --- /dev/null +++ b/applications/dynaclr/scripts/clustering/make_sample_montage.py @@ -0,0 +1,116 @@ +"""Build multi-channel sample montages from saved per-channel crops. + +Reads the single-cell crop PNGs written by ``cluster_cell_state.py`` under +``//samples///`` (channels: phase / organelle +/ sensor) and lays out one montage per class per marker in the style of the +witness / prob-sample figures: rows = **time-bin x channel**, columns = distinct +cells. Only the first channel row of each bin carries the bin label. Runs +independently of the clustering step so montages regenerate without re-running. + +Usage +----- +python make_sample_montage.py -c zikv_infection_pop.yml +""" + +import argparse +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.image as mpimg +import matplotlib.pyplot as plt + +from viscy_utils.cli_utils import load_config + +# Class directory names, in (positive, negative) order; overridden from config. +DEFAULT_CLASSES = ("infected", "uninfected") +# Channel subdir order (top to bottom within a bin), matching cluster_cell_state. +CHANNEL_ORDER = ("phase", "organelle", "sensor") + + +def _bin_of(png: Path) -> tuple[int, str]: + """Parse (bin index, bin label) from a ``bin{idx}_{label}_...`` filename.""" + parts = png.name.split("_", 2) + return int(parts[0][3:]), parts[1] + + +def make_class_montage(class_dir: Path, out_path: Path, title: str) -> None: + """Lay out one class as rows = (bin x channel), columns = distinct cells. + + Parameters + ---------- + class_dir : Path + ``samples//`` containing one subfolder per channel. + out_path : Path + Where to write the montage PNG. + title : str + Figure title (marker + class). + """ + channels = [c for c in CHANNEL_ORDER if (class_dir / c).is_dir()] + if not channels: + raise FileNotFoundError(f"No channel subdirs under {class_dir}") + + # Index crops by (bin, cell filename) per channel; the filename (minus the + # bin prefix) is the shared cell key across channels. + ref = channels[0] + cells: dict[int, list[str]] = {} + bin_label: dict[int, str] = {} + for p in sorted((class_dir / ref).glob("bin*_*.png")): + b, label = _bin_of(p) + bin_label[b] = label + cells.setdefault(b, []).append(p.name) + bins = sorted(cells) + cols = max((len(v) for v in cells.values()), default=0) + if cols == 0: + raise FileNotFoundError(f"No bin-prefixed crops under {class_dir / ref}") + + n_rows = len(bins) * len(channels) + fig, axes = plt.subplots(n_rows, cols, figsize=(cols * 1.4, n_rows * 1.5), squeeze=False) + for ax in axes.flat: + ax.axis("off") + + for bi, b in enumerate(bins): + for ci, ch in enumerate(channels): + row = bi * len(channels) + ci + label = f"{bin_label[b]}\n{ch}" if ci == 0 else ch + axes[row][0].set_ylabel(label, rotation=0, ha="right", va="center", fontsize=8) + axes[row][0].axis("on") + axes[row][0].set_xticks([]) + axes[row][0].set_yticks([]) + for c, fname in enumerate(cells[b]): + fpath = class_dir / ch / fname + if fpath.exists(): + axes[row][c].imshow(mpimg.imread(fpath), cmap="gray") + + fig.suptitle(title, fontsize=12) + fig.tight_layout() + fig.savefig(out_path, dpi=130) + plt.close(fig) + print(f"wrote {out_path}") + + +def main() -> None: + """Build a per-class multi-channel montage for every marker in the config.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-c", "--config", required=True, type=Path) + args = parser.parse_args() + + cfg = load_config(args.config) + classes = (cfg.get("positive_class", DEFAULT_CLASSES[0]), cfg.get("negative_class", DEFAULT_CLASSES[1])) + out_root = Path(cfg["output_dir"]) + for entry in cfg["datasets"]: + marker = entry["marker"] + samples_dir = out_root / marker / "samples" + for cls in classes: + class_dir = samples_dir / cls + if class_dir.is_dir(): + make_class_montage( + class_dir, + samples_dir / f"montage_{cls}.png", + f"{marker} — {cls}\nsamples by time bin x channel (phase / organelle / sensor)", + ) + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/scripts/clustering/track_dynamics.py b/applications/dynaclr/scripts/clustering/track_dynamics.py new file mode 100644 index 000000000..c354239e1 --- /dev/null +++ b/applications/dynaclr/scripts/clustering/track_dynamics.py @@ -0,0 +1,257 @@ +"""Per-track / per-phenotype dynamics probe (heterogeneity-aware go/no-go for straightening). + +The population velocity map (``velocity_map.py``) found velocity ⊥ the *global* +pseudotime gradient — but that is exactly what **heterogeneous responses** predict: +if cells split into several asynchronous remodeling phenotypes, there is no single +population drift direction, so the pooled field cancels. That population null does +NOT bear on a *per-track* straightening objective. This script measures the +quantities that actually decide whether directed dynamics exist: + +1. **Per-track straightness** — net displacement / path length in the FULL 768-d + embedding, one value per (fov, track). Drift ≈ 1, random walk ≈ 0. Distribution, + NOT a pooled mean. This is the headroom question for straightening. +2. **Curvature vs stencil spacing τ** — ``1 − cos(v_t, v_{t+τ})`` for τ ∈ {1,2,3,5} + averaged per track. Falling with τ ⇒ directed drift the single step is too noisy + to show (build); flat/high at all τ ⇒ no drift (stop). +3. **Velocity-direction clustering (the "3–4 phenotypes" test)** — cluster tracks by + their mean unit velocity direction in 768-d; count coherent phenotype bundles and + report per-cluster straightness. +4. **Biology check** — do the velocity-direction clusters map to distinct human + ``infection_state``? (contingency + purity). A real phenotype bundle is enriched + for a fate. + +Reuses ``high_dim_velocity`` and ``diffusion_pseudotime`` from ``velocity_map.py``. + +Usage +----- +python track_dynamics.py -c zikv_velocity.yml +""" + +import argparse +from pathlib import Path + +import anndata as ad +import numpy as np +import pandas as pd +from sklearn.cluster import KMeans +from sklearn.metrics import silhouette_score +from velocity_map import high_dim_velocity + +from viscy_utils.cli_utils import format_markdown_table, load_config + +TAUS = (1, 2, 3, 5) +MIN_TRACK_LEN = 4 # need ≥4 frames for a τ=1 curvature triple + a net/path ratio +VELOCITY_CLUSTERS = range(2, 7) # sweep k for the velocity-direction phenotype count + + +def _track_key(obs: pd.DataFrame) -> np.ndarray: + """Stable per-track key ``fov_name|track_id`` (lineage_id is absent here).""" + return (obs["fov_name"].astype(str) + "|" + obs["track_id"].astype(str)).to_numpy() + + +def per_track_straightness(X: np.ndarray, obs: pd.DataFrame) -> pd.DataFrame: + """Net-displacement / path-length per track in the full embedding. + + For a track with ordered embeddings ``z_0..z_L``: ``net = ‖z_L − z_0‖``, + ``path = Σ ‖z_{k+1} − z_k‖``; straightness = net / path ∈ [0, 1]. Drift → 1, + random walk → ~1/√L. Also returns mean per-track step size for context. + """ + key = _track_key(obs) + t = obs["t"].to_numpy(dtype=int) + rows = [] + for k in pd.unique(key): + m = key == k + order = np.argsort(t[m]) + z = X[m][order] + if len(z) < MIN_TRACK_LEN: + continue + steps = np.linalg.norm(np.diff(z, axis=0), axis=1) + path = float(steps.sum()) + net = float(np.linalg.norm(z[-1] - z[0])) + rows.append( + { + "track": k, + "length": len(z), + "net": net, + "path": path, + "straightness": net / path if path > 0 else np.nan, + "mean_step": float(steps.mean()), + } + ) + return pd.DataFrame(rows) + + +def per_track_curvature(X: np.ndarray, obs: pd.DataFrame, taus=TAUS) -> pd.DataFrame: + """Mean ``1 − cos(v_t, v_{t+τ})`` per track, for each stencil spacing τ. + + ``v_t = z_{t+τ} − z_t`` on the ordered track; curvature is the mean over the + sliding pair of consecutive velocity vectors. Lower = straighter (more directed). + A drop as τ grows means directed drift is present but buried under single-step noise. + """ + key = _track_key(obs) + t = obs["t"].to_numpy(dtype=int) + rows = [] + for k in pd.unique(key): + m = key == k + order = np.argsort(t[m]) + z = X[m][order] + rec = {"track": k, "length": len(z)} + for tau in taus: + if len(z) < 2 * tau + 1: + rec[f"curv_tau{tau}"] = np.nan + continue + v = z[tau:] - z[:-tau] # velocity at spacing tau + v1, v2 = v[:-tau], v[tau:] # consecutive (non-overlapping) velocities + n1 = np.linalg.norm(v1, axis=1) + n2 = np.linalg.norm(v2, axis=1) + good = (n1 > 0) & (n2 > 0) + if not good.any(): + rec[f"curv_tau{tau}"] = np.nan + continue + cos = (v1[good] * v2[good]).sum(1) / (n1[good] * n2[good]) + rec[f"curv_tau{tau}"] = float(np.mean(1.0 - cos)) + rows.append(rec) + return pd.DataFrame(rows) + + +def velocity_direction_clusters(X: np.ndarray, obs: pd.DataFrame, seed: int) -> pd.DataFrame: + """Cluster tracks by their MEAN UNIT velocity direction in 768-d. + + One vector per track = the mean of its unit per-frame velocities (direction of + travel, magnitude-free — robust on the L2-normalized sphere). KMeans over a k + sweep; pick k by silhouette. Coherent bundles ⇒ heterogeneous directed + phenotypes; a single blob ⇒ no directional structure. + """ + src, v = high_dim_velocity(X, obs) + key = _track_key(obs) + # mean unit velocity per track + unit = v / (np.linalg.norm(v, axis=1, keepdims=True) + 1e-12) + by_track: dict[str, list] = {} + for i, s in enumerate(src): + by_track.setdefault(key[s], []).append(unit[i]) + tracks = [k for k, u in by_track.items() if len(u) >= MIN_TRACK_LEN - 1] + if len(tracks) < max(VELOCITY_CLUSTERS) + 1: + return pd.DataFrame(columns=["track", "vel_cluster"]) + D = np.vstack([np.mean(by_track[k], axis=0) for k in tracks]) + D = D / (np.linalg.norm(D, axis=1, keepdims=True) + 1e-12) # re-normalize mean direction + + best_k, best_sil, best_lbl = None, -1.0, None + for k in VELOCITY_CLUSTERS: + lbl = KMeans(n_clusters=k, random_state=seed, n_init=10).fit_predict(D) + sil = silhouette_score(D, lbl, metric="cosine") + if sil > best_sil: + best_k, best_sil, best_lbl = k, sil, lbl + print(f" velocity-direction clustering: best k={best_k} (cosine silhouette={best_sil:.3f})", flush=True) + return pd.DataFrame({"track": tracks, "vel_cluster": best_lbl}) + + +def biology_check(clusters: pd.DataFrame, straight: pd.DataFrame, obs: pd.DataFrame) -> pd.DataFrame: + """Per velocity-cluster: size, mean straightness, and dominant infection_state. + + Maps each track's velocity-direction cluster to the modal human ``infection_state`` + over that track's frames — tests whether a direction bundle is a real fate bundle. + """ + key = _track_key(obs) + inf = obs["infection_state"].astype(str).to_numpy() + labeled = inf != "nan" # "nan" is a STRING here (unlabeled), not real NaN + # modal infection_state per track over LABELED frames only; tracks with no + # labeled frame are "unlabeled" and excluded from purity. + track_state = {} + for k in pd.unique(key): + vals = pd.Series(inf[(key == k) & labeled]) + track_state[k] = vals.mode().iloc[0] if not vals.empty else "unlabeled" + df = clusters.merge(straight[["track", "straightness", "length"]], on="track", how="left") + df["infection_state"] = df["track"].map(track_state) + rows = [] + for c, g in df.groupby("vel_cluster"): + lab = g[g["infection_state"] != "unlabeled"] + counts = lab["infection_state"].value_counts() + rows.append( + { + "vel_cluster": int(c), + "n_tracks": len(g), + "n_labeled": int(len(lab)), + "mean_straightness": float(g["straightness"].mean()), + "dominant_state": counts.index[0] if len(counts) else "n/a", + "purity": float(counts.iloc[0] / counts.sum()) if len(counts) else np.nan, + "state_breakdown": ", ".join(f"{k}:{v}" for k, v in counts.items()) + if len(counts) + else "no labeled tracks", + } + ) + return pd.DataFrame(rows).sort_values("vel_cluster") + + +def analyze(entry: dict, cfg: dict, out_dir: Path) -> dict: + """Run the four probes for one marker; write CSVs; return summary metrics.""" + marker = entry["marker"] + seed = cfg.get("random_seed", 42) + print(f"[{marker}] reading {entry['embeddings']}", flush=True) + adata = ad.read_zarr(entry["embeddings"]) + adata.obs_names_make_unique() + obs = adata.obs.reset_index(drop=True) + X = np.asarray(adata.X, dtype=np.float64) + + out_dir.mkdir(parents=True, exist_ok=True) + + straight = per_track_straightness(X, obs) + curv = per_track_curvature(X, obs) + straight.to_csv(out_dir / f"{marker}_per_track_straightness.csv", index=False) + curv.to_csv(out_dir / f"{marker}_per_track_curvature.csv", index=False) + + clusters = velocity_direction_clusters(X, obs, seed) + bio = pd.DataFrame() + if not clusters.empty: + clusters.to_csv(out_dir / f"{marker}_velocity_clusters.csv", index=False) + bio = biology_check(clusters, straight, obs) + bio.to_csv(out_dir / f"{marker}_velocity_cluster_biology.csv", index=False) + + curv_means = {tau: float(curv[f"curv_tau{tau}"].mean(skipna=True)) for tau in TAUS} + summary = { + "marker": marker, + "n_tracks": len(straight), + "straightness_median": float(straight["straightness"].median()), + "straightness_p90": float(straight["straightness"].quantile(0.90)), + **{f"curv_tau{tau}": curv_means[tau] for tau in TAUS}, + "curv_drop_1to5": curv_means[1] - curv_means[5], + "n_vel_clusters": 0 if clusters.empty else int(clusters["vel_cluster"].nunique()), + "max_cluster_purity": float(bio["purity"].max()) if not bio.empty else np.nan, + } + print(f"[{marker}] {summary}", flush=True) + if not bio.empty: + print(format_markdown_table(bio.to_dict("records"), headers=list(bio.columns)), flush=True) + return summary + + +def main() -> None: + """Run the per-track / per-phenotype probe for every marker in the config.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-c", "--config", required=True, type=Path) + args = parser.parse_args() + cfg = load_config(args.config) + out_root = Path(cfg["output_dir"]) + summaries = [analyze(entry, cfg, out_root / entry["marker"] / "track_dynamics") for entry in cfg["datasets"]] + + print("\n" + "=" * 80) + print("PER-TRACK / PER-PHENOTYPE DYNAMICS SUMMARY") + print("=" * 80 + "\n") + cols = [ + "marker", + "n_tracks", + "straightness_median", + "straightness_p90", + "curv_tau1", + "curv_tau5", + "curv_drop_1to5", + "n_vel_clusters", + "max_cluster_purity", + ] + rows = [{c: s[c] for c in cols} for s in summaries] + print(format_markdown_table(rows, headers=cols)) + print("\n**Read:** straightness_median≈1/√L → random walk; curv falling from τ1→τ5 → directed drift") + print("buried under step noise; high max_cluster_purity → velocity bundles = real fate phenotypes.") + pd.DataFrame(summaries).to_csv(out_root / "track_dynamics_summary.csv", index=False) + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/scripts/clustering/velocity_map.py b/applications/dynaclr/scripts/clustering/velocity_map.py new file mode 100644 index 000000000..7b536bc02 --- /dev/null +++ b/applications/dynaclr/scripts/clustering/velocity_map.py @@ -0,0 +1,273 @@ +"""PHATE + diffusion-pseudotime + velocity map of phenotype flow over time. + +Tests whether cells follow an ordered control -> remodel progression (§6): + +- **PHATE** layout — preserves trajectory geometry (UMAP fragmented it, giving a + scrambled time gradient and artifact-dominated arrows; see PLAN §6 build log). +- **Diffusion pseudotime** rooted at the control-well centroid on the PHATE kNN + graph (annotation-free): per-cell distance-along-the-manifold from control. +- **Velocity** measured from single-cell tracks in the FULL 768-d embedding + (``X(t+1) - X(t)``), then projected to the PHATE layout scVelo-style (cosine of + the high-dim velocity against neighbor offsets) — NOT ``PHATE(t+1)-PHATE(t)``, + which measured layout jitter. Drawn as a grid-averaged streamplot. +- **Validation:** Spearman(pseudotime, real ``t``) — a real progression is + strongly positive. + +Renders per marker a 3-panel figure (perturbation + arrows / pseudotime + stream / +time) and writes pseudotime + velocity CSVs. + +Usage +----- +python velocity_map.py -c zikv_velocity.yml +""" + +import argparse +from pathlib import Path + +import anndata as ad +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import phate +from scipy.sparse.csgraph import dijkstra +from scipy.stats import spearmanr +from sklearn.neighbors import kneighbors_graph + +from viscy_utils.cli_utils import load_config + + +def _control_centroids(X: np.ndarray, obs: pd.DataFrame, min_cells: int = 5) -> dict[int, np.ndarray]: + """Per-timepoint mean embedding of control (uninfected-well) cells. + + Captures the background drift shared by all cells at each timepoint (imaging, + media, general time-of-experiment / cell-cycle effects). Subtracting it + isolates the infection-driven component of a cell's motion. + """ + ctrl = obs["perturbation"].astype(str).to_numpy() == "uninfected" + t = obs["t"].to_numpy(dtype=int) + cents = {} + for tt in np.unique(t[ctrl]): + m = ctrl & (t == tt) + if m.sum() >= min_cells: + cents[int(tt)] = X[m].mean(axis=0) + return cents + + +def high_dim_velocity(X: np.ndarray, obs: pd.DataFrame, control_ref: bool = False) -> tuple[np.ndarray, np.ndarray]: + """Per-cell velocity in the FULL embedding space, measured from tracks. + + ``v_i = X(cell, t+1) − X(cell, t)`` in the 768-d embedding (not the 2-D + layout) for every cell that has a next-frame in its track. Computing velocity + where the biology lives — then projecting to 2-D (``project_velocity``) — + avoids the layout-jitter that plain ``PHATE(t+1)−PHATE(t)`` suffered. + + If ``control_ref`` is True, each frame's embedding is first referenced to the + per-timepoint control centroid (``X - control_centroid(t)``) before + differencing, so shared background drift is removed and only the + infection-driven displacement remains. A segment is dropped if either of its + two timepoints lacks a control centroid. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + ``src`` (n_seg,) = row index of the origin cell; ``v`` (n_seg, n_dim) = + high-dim velocity for each such cell. + """ + cents = _control_centroids(X, obs) if control_ref else None + cell = obs["fov_name"].astype(str) + "|" + obs["track_id"].astype(str) + t = obs["t"].to_numpy(dtype=int) + idx = {(c, int(tt)): i for i, (c, tt) in enumerate(zip(cell, t, strict=True))} + src, v = [], [] + for (c, tt), i in idx.items(): + j = idx.get((c, tt + 1)) + if j is None: + continue + if cents is None: + src.append(i) + v.append(X[j] - X[i]) + elif tt in cents and (tt + 1) in cents: + src.append(i) + v.append((X[j] - cents[tt + 1]) - (X[i] - cents[tt])) + return np.asarray(src), np.asarray(v) + + +def project_velocity( + X: np.ndarray, coords: np.ndarray, src: np.ndarray, v: np.ndarray, n_neighbors: int = 30 +) -> tuple[np.ndarray, np.ndarray]: + """Project high-dim velocity onto the 2-D layout (scVelo ``velocity_embedding``). + + For each origin cell, its 2-D arrow is the neighbor-displacement-weighted mean + of directions to its kNN, where the weight is the cosine similarity between the + cell's HIGH-DIM velocity ``v_i`` and the high-dim offset to each neighbor + (mean-centered, softmax-free correlation kernel). The 2-D arrow thus points + toward the neighbors the cell is actually moving toward in 768-d — robust to + the L2-normalized sphere (direction-based, not magnitude-based). + + Returns ``origins`` (n_seg, 2) and 2-D ``arrows`` (n_seg, 2). + """ + knn = kneighbors_graph(X, n_neighbors=n_neighbors, mode="connectivity", include_self=False) + origins = coords[src] + arrows = np.zeros((len(src), 2)) + for k, i in enumerate(src): + nbr = knn[i].indices + dX = X[nbr] - X[i] # high-dim offsets to neighbors + dP = coords[nbr] - coords[i] # 2-D offsets to neighbors + # cosine(v_i, dX) — correlation kernel, mean-centered as in scVelo. + dX_n = dX / (np.linalg.norm(dX, axis=1, keepdims=True) + 1e-8) + cos = dX_n @ (v[k] / (np.linalg.norm(v[k]) + 1e-8)) + w = cos - cos.mean() + arrows[k] = w @ dP + # scale arrows to a readable common size relative to the layout + med = np.median(np.linalg.norm(arrows, axis=1)) + if med > 0: + span = np.hypot(np.ptp(coords[:, 0]), np.ptp(coords[:, 1])) + arrows *= 0.02 * span / med + return origins, arrows + + +def grid_field(origins: np.ndarray, vectors: np.ndarray, n_grid: int = 25): + """Average velocity vectors onto a regular grid for a streamplot. + + Returns (gx, gy, U, V) with NaN where a grid cell has too few segments. + """ + xmin, ymin = origins.min(axis=0) + xmax, ymax = origins.max(axis=0) + gx = np.linspace(xmin, xmax, n_grid) + gy = np.linspace(ymin, ymax, n_grid) + ix = np.clip(np.searchsorted(gx, origins[:, 0]) - 1, 0, n_grid - 1) + iy = np.clip(np.searchsorted(gy, origins[:, 1]) - 1, 0, n_grid - 1) + U = np.full((n_grid, n_grid), np.nan) + V = np.full((n_grid, n_grid), np.nan) + for a in range(n_grid): + for b in range(n_grid): + m = (ix == a) & (iy == b) + if m.sum() >= 3: # require a few segments per cell for a stable mean + U[b, a] = vectors[m, 0].mean() + V[b, a] = vectors[m, 1].mean() + return gx, gy, U, V + + +def diffusion_pseudotime(X: np.ndarray, root_mask: np.ndarray, n_neighbors: int = 15) -> np.ndarray: + """Graph geodesic distance from the root population, normalized to [0, 1]. + + Builds a kNN graph on the high-dim embedding, then takes the shortest-path + (Dijkstra) distance from every cell to the nearest root (control) cell. This + is an annotation-free pseudotime rooted at the controls — small = control-like, + large = far along the manifold from control. + """ + graph = kneighbors_graph(X, n_neighbors=n_neighbors, mode="distance", include_self=False) + graph = graph.maximum(graph.T) # symmetrize for an undirected geodesic + roots = np.flatnonzero(root_mask) + dist = dijkstra(graph, directed=False, indices=roots).min(axis=0) + finite = dist[np.isfinite(dist)] + if finite.size: # disconnected cells -> max finite distance + dist[~np.isfinite(dist)] = finite.max() + rng = dist.max() - dist.min() + return (dist - dist.min()) / rng if rng > 0 else np.zeros_like(dist) + + +def _clip_arrows(origins: np.ndarray, vectors: np.ndarray, pct: float = 99.0): + """Drop segments whose length exceeds the ``pct`` percentile (UMAP/PHATE jitter).""" + mag = np.hypot(vectors[:, 0], vectors[:, 1]) + keep = mag <= np.percentile(mag, pct) + return origins[keep], vectors[keep] + + +def make_velocity_map(entry: dict, cfg: dict, out_dir: Path) -> None: + """Compute + render the PHATE + pseudotime + velocity map for one marker.""" + marker = entry["marker"] + seed = cfg.get("random_seed", 42) + adata = ad.read_zarr(entry["embeddings"]) + adata.obs_names_make_unique() + obs = adata.obs.reset_index(drop=True) + X = np.asarray(adata.X, dtype=np.float64) + pert = obs["perturbation"].astype(str).to_numpy() + tvals = obs["t"].to_numpy(dtype=float) + + print(f"[{marker}] PHATE over {len(obs)} cells ...", flush=True) + coords = phate.PHATE(n_components=2, random_state=seed, verbose=False).fit_transform(X) + + print(f"[{marker}] diffusion pseudotime rooted at control ...", flush=True) + ptime = diffusion_pseudotime(X, pert == "uninfected") + rho, _ = spearmanr(ptime, tvals) + + # control_ref subtracts the per-timepoint control centroid before differencing, + # removing background drift shared by all cells. Helps entangled channels + # (organelle) more than the reporter — see PLAN §6 build log. + control_ref = entry.get("control_ref", cfg.get("control_ref", False)) + ref_tag = " (control-referenced)" if control_ref else "" + print(f"[{marker}] high-dim velocity{ref_tag} + scVelo projection to PHATE ...", flush=True) + src, v = high_dim_velocity(X, obs, control_ref=control_ref) + coherence = float(np.linalg.norm(v.mean(axis=0)) / (np.linalg.norm(v, axis=1).mean() + 1e-9)) + origins, vectors = project_velocity(X, coords, src, v) + origins, vectors = _clip_arrows(origins, vectors) + gx, gy, U, V = grid_field(origins, vectors) + print( + f"[{marker}] {len(origins)} segments | Spearman(ptime,t)={rho:.3f} | " + f"global velocity coherence={coherence:.3f}{ref_tag}", + flush=True, + ) + + fig, axes = plt.subplots(1, 3, figsize=(21, 6.5)) + + # Panel 1: perturbation (control = source) + velocity streamlines. + ax = axes[0] + for cond, color in (("uninfected", "tab:blue"), ("infected", "tab:red")): + m = pert == cond + ax.scatter(coords[m, 0], coords[m, 1], s=3, c=color, alpha=0.3, label=cond) + ax.streamplot(gx, gy, U, V, color="k", density=1.3, linewidth=0.8, arrowsize=0.9) + ax.legend(markerscale=3, fontsize=8) + ax.set_title("well condition + velocity field (control = source)") + + # Panel 2: diffusion pseudotime + velocity streamlines. + ax = axes[1] + sc = ax.scatter(coords[:, 0], coords[:, 1], s=4, c=ptime, cmap="viridis", alpha=0.6) + ax.streamplot(gx, gy, U, V, color="k", density=1.3, linewidth=0.8, arrowsize=0.9) + fig.colorbar(sc, ax=ax, shrink=0.7, label="pseudotime (from control)") + ax.set_title("diffusion pseudotime + velocity") + + # Panel 3: real time (validation: pseudotime should track t). + ax = axes[2] + sc = ax.scatter(coords[:, 0], coords[:, 1], s=4, c=tvals, cmap="plasma", alpha=0.6) + fig.colorbar(sc, ax=ax, shrink=0.7, label="t") + ax.set_title(f"real time t (Spearman ptime~t = {rho:.2f})") + + for ax in axes: + ax.set_xticks([]) + ax.set_yticks([]) + fig.suptitle(f"{marker} — PHATE phenotype flow (pseudotime rooted at control, velocity from tracks)", fontsize=13) + fig.tight_layout() + out_dir.mkdir(parents=True, exist_ok=True) + fig.savefig(out_dir / f"{marker}_velocity_map.png", dpi=150) + plt.close(fig) + + pd.DataFrame( + { + "fov_name": obs["fov_name"].astype(str), + "track_id": obs["track_id"], + "t": tvals, + "perturbation": pert, + "phate_0": coords[:, 0], + "phate_1": coords[:, 1], + "pseudotime": ptime, + } + ).to_csv(out_dir / f"{marker}_pseudotime.csv", index=False) + print(f"[{marker}] wrote velocity map + pseudotime CSV (Spearman={rho:.3f})", flush=True) + + +def main() -> None: + """Build velocity maps for every marker in the config.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-c", "--config", required=True, type=Path) + args = parser.parse_args() + cfg = load_config(args.config) + out_root = Path(cfg["output_dir"]) + for entry in cfg["datasets"]: + make_velocity_map(entry, cfg, out_root / entry["marker"] / "velocity") + + +if __name__ == "__main__": + main() From 7cf667498fea67aca95fc6a5af7885307c80c60f Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 10:38:43 -0700 Subject: [PATCH 41/89] feat(dynaclr): MMD significance gate before witness-GMM labeling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per perturbed condition, run an MMD permutation test (mmd_permutation_test) of the condition's cells against the control reference before fitting the GMM. If the separation is not significant (p > mmd_pvalue_threshold, default 0.05), the perturbation left no detectable signature and the condition is skipped — no labels manufactured from noise. A condition contributes positives only if it is BOTH MMD-significant AND GMM-bimodal (separated=True); the two guard different failure modes. - evaluate_config: add mmd_pvalue_threshold (0.05) + mmd_n_permutations (1000) to WitnessGmmLabelsConfig, with validation. Set threshold to 1.0 to disable. - witness_gmm_labels: gate each condition on the permutation-test p-value; log the skip reason (mmd_not_significant vs gmm_unimodal). - test: identical control/perturbed clouds -> gate skips -> None. - recipe + DAG: document the significance gate (config keys, Mermaid step, rationale in the reference-construction section). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../witness_gmm_labels_infectomics.yml | 5 ++++ .../docs/DAGs/witness_gmm_classifiers.md | 15 ++++++++-- .../src/dynaclr/evaluation/evaluate_config.py | 12 ++++++++ .../linear_classifiers/witness_gmm_labels.py | 22 ++++++++++++-- .../witness_gmm_labels_test.py | 30 +++++++++++++++++++ 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml index 68f4f94d6..c775ed1fd 100644 --- a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml +++ b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml @@ -36,6 +36,11 @@ witness_gmm_labels: label_column: infection_state class_map: {positive: infected, negative: uninfected} gmm_pos_threshold: 0.8 + # Significance gate: skip a condition whose MMD vs the control reference is not + # significant (no detectable perturbation signature). A condition is labeled only + # if BOTH the MMD is significant AND the GMM is bimodal. Set to 1.0 to disable. + mmd_pvalue_threshold: 0.05 + mmd_n_permutations: 1000 bandwidth: null # median heuristic on pooled (control, perturbed) max_reference_cells: 5000 condition_column: perturbation diff --git a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md index 558bea6a6..915eeeb27 100644 --- a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md +++ b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md @@ -24,9 +24,10 @@ flowchart TD A1["references from wells/filters:
X = control cells, Y = perturbed cells"] A2["bandwidth = median_heuristic(X, Y)
viscy_utils.evaluation.mmd"] A3["score every cell: w(z) = witness_function(z, X, Y, bw)
mmd"] - A4["per perturbed CONDITION: 2-component GMM on w[cond]
witness_gmm.fit_gmm_labels
remod = argmin(means); posterior ≥ gmm_pos_threshold → positive
negatives = ALL control-well cells; ambiguous → dropped
unimodal GMM (separated=False) → marker skipped"] + AG["per perturbed CONDITION: MMD permutation test (X vs cond)
mmd.mmd_permutation_test
p > mmd_pvalue_threshold → not significant → skip condition"] + A4["per perturbed CONDITION: 2-component GMM on w[cond]
witness_gmm.fit_gmm_labels
remod = argmin(means); posterior ≥ gmm_pos_threshold → positive
negatives = ALL control-well cells; ambiguous → dropped
unimodal GMM (separated=False) → condition skipped"] A5["map GMM ±1 → class_map vocabulary
(e.g. infected / uninfected)"] - A1 --> A2 --> A3 --> A4 --> A5 + A1 --> A2 --> A3 --> AG --> A4 --> A5 end LBL["ANNOTATION FILE .csv|parquet
key: fov_name + id (or fov_name + t + track_id) + experiment
named state column, e.g. infection_state ∈ {infected, uninfected}
hand-annotation format — producer-agnostic"] @@ -86,6 +87,14 @@ threshold, which is exactly what the GMM removes. The GMM is the principled repl the time gate: it finds the remodeled sub-population within the full mixture. Add a time gate only for a specific reason (e.g. debugging, or a marker with no clean late window). +**Significance gate (before the GMM).** Per condition, an MMD permutation test +(`mmd_permutation_test`, X vs the condition's cells) checks whether the two clouds are +*actually distinct*. If `p > mmd_pvalue_threshold` (default 0.05) the perturbation left no +detectable signature and the condition is skipped — no labels manufactured from noise. A +condition contributes positives only if it is **both** MMD-significant **and** GMM-bimodal +(`separated=True`); the two guard different failure modes (references differ vs. the +perturbed cloud splits cleanly). Set `mmd_pvalue_threshold: 1.0` to disable the gate. + ```mermaid flowchart LR subgraph refs["reference clouds (per marker, all timepoints)"] @@ -119,6 +128,8 @@ witness_gmm_labels: label_column: infection_state class_map: {positive: infected, negative: uninfected} gmm_pos_threshold: 0.8 + mmd_pvalue_threshold: 0.05 # skip a condition if MMD vs control is not significant + mmd_n_permutations: 1000 bandwidth: null # median heuristic max_reference_cells: 5000 condition_column: perturbation diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index d63207c8f..fa62f883c 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -297,6 +297,14 @@ class WitnessGmmLabelsConfig(BaseModel): gmm_pos_threshold : float GMM remodeled-component posterior at/above which a perturbed cell is a confident positive. Default: 0.8. + mmd_pvalue_threshold : float + Significance gate applied per perturbed condition before the GMM: the + condition's cloud is MMD-permutation-tested against the control reference, + and if the p-value exceeds this threshold the separation is not significant + and the condition is skipped (no positives). Set to 1.0 to disable the + gate. Default: 0.05. + mmd_n_permutations : int + Number of permutations for the MMD significance test. Default: 1000. bandwidth : float or None Gaussian RBF bandwidth for the witness kernel. None = median heuristic on the pooled (control, perturbed) reference. Default: None. @@ -314,6 +322,8 @@ class WitnessGmmLabelsConfig(BaseModel): marker_filters: list[str] | None = None condition_column: str = "perturbation" gmm_pos_threshold: float = 0.8 + mmd_pvalue_threshold: float = 0.05 + mmd_n_permutations: int = 1000 bandwidth: float | None = None max_reference_cells: int | None = 5000 random_seed: int = 42 @@ -327,6 +337,8 @@ def _validate(self) -> "WitnessGmmLabelsConfig": raise ValueError(f"class_map must define {sorted(missing)} (got keys {sorted(self.class_map)})") if not 0.0 < self.gmm_pos_threshold <= 1.0: raise ValueError(f"gmm_pos_threshold must be in (0, 1], got {self.gmm_pos_threshold}") + if not 0.0 < self.mmd_pvalue_threshold <= 1.0: + raise ValueError(f"mmd_pvalue_threshold must be in (0, 1], got {self.mmd_pvalue_threshold}") return self diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py index 8a2748677..2f521db59 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py @@ -36,7 +36,7 @@ class vocabulary — a file indistinguishable from a hand annotation. import pandas as pd from viscy_utils.cli_utils import load_config -from viscy_utils.evaluation.mmd import median_heuristic, subsample, witness_function +from viscy_utils.evaluation.mmd import median_heuristic, mmd_permutation_test, subsample, witness_function from viscy_utils.evaluation.witness_gmm import fit_gmm_labels if TYPE_CHECKING: @@ -227,9 +227,27 @@ def build_marker_annotation( cond_mask = perturbed_mask & (conditions == cond) if cond_mask.sum() < 5: continue + # Significance gate: is this condition's cloud actually distinct from the + # control reference? A non-significant MMD means the perturbation left no + # detectable signature — skip rather than manufacture labels from noise. + _mmd2, p_value, _null = mmd_permutation_test( + X_ref, + subsample(X_all[cond_mask], config.max_reference_cells, rng), + n_permutations=config.mmd_n_permutations, + bandwidth=bandwidth, + seed=config.random_seed, + ) + if p_value > config.mmd_pvalue_threshold: + _logger.warning( + "MMD not significant for condition %r (p=%.3g > %.3g); no positives labeled.", + cond, + p_value, + config.mmd_pvalue_threshold, + ) + continue res = fit_gmm_labels(scores[cond_mask], pos_threshold=config.gmm_pos_threshold, random_state=config.random_seed) if not res.separated: - _logger.warning("GMM unimodal for condition %r; no positives labeled.", cond) + _logger.warning("GMM unimodal for condition %r (p=%.3g); no positives labeled.", cond, p_value) continue any_separated = True idx = np.flatnonzero(cond_mask)[res.hard_label == 1] diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py index 823d4d43d..85d4c04dd 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py @@ -179,3 +179,33 @@ def test_build_marker_annotation_none_when_reference_missing(tmp_path): output_path=str(tmp_path / "labels.csv"), ) assert build_marker_annotation(adata, cfg.experiments, cfg) is None + + +def test_build_marker_annotation_mmd_gate_skips_nonsignificant(tmp_path): + """When control and perturbed clouds are indistinguishable, the MMD + significance gate skips the condition (no positives → None).""" + rng = np.random.default_rng(0) + n = 120 + wells = ["A/1"] * n + ["B/2"] * n + total = len(wells) + # Both wells drawn from the SAME distribution — no real separation. + X = rng.standard_normal((total, 16)).astype(np.float32) + well_to_pert = {"A/1": "uninfected", "B/2": "DENV"} + obs = pd.DataFrame( + { + "fov_name": [f"{w}/000000" for w in wells], + "id": list(range(total)), + "t": [i % 5 for i in range(total)], + "track_id": list(range(total)), + "experiment": ["exp_A"] * total, + "marker": ["viral_sensor"] * total, + "perturbation": [well_to_pert[w] for w in wells], + } + ) + for col in obs.select_dtypes("string").columns: + obs[col] = obs[col].astype(object) + obs.index = pd.Index([str(i) for i in range(total)], dtype=object) + adata = ad.AnnData(X=X, obs=obs, var=pd.DataFrame(index=[str(i) for i in range(16)])) + + cfg = _config(tmp_path / "labels.csv") + assert build_marker_annotation(adata, cfg.experiments, cfg) is None From f86b9e570b6fd91e5df0495a2a6e4eded52df5e4 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:07:56 -0700 Subject: [PATCH 42/89] feat(dynaclr): add canonical per-marker prediction paths --- applications/dynaclr/src/dynaclr/cli.py | 8 + .../dynaclr/src/dynaclr/evaluation/paths.py | 180 +++++++++ .../src/dynaclr/evaluation/predict_triplet.py | 382 ++++++++++++++++++ applications/dynaclr/tests/test_paths.py | 76 ++++ .../dynaclr/tests/test_predict_triplet.py | 105 +++++ .../viscy_utils/callbacks/embedding_writer.py | 12 +- .../tests/test_embedding_writer.py | 49 +++ 7 files changed, 811 insertions(+), 1 deletion(-) create mode 100644 applications/dynaclr/src/dynaclr/evaluation/paths.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/predict_triplet.py create mode 100644 applications/dynaclr/tests/test_paths.py create mode 100644 applications/dynaclr/tests/test_predict_triplet.py create mode 100644 packages/viscy-utils/tests/test_embedding_writer.py diff --git a/applications/dynaclr/src/dynaclr/cli.py b/applications/dynaclr/src/dynaclr/cli.py index 51d83fc93..c883e60ff 100644 --- a/applications/dynaclr/src/dynaclr/cli.py +++ b/applications/dynaclr/src/dynaclr/cli.py @@ -296,6 +296,14 @@ def dynaclr(): ) ) +dynaclr.add_command( + LazyCommand( + name="predict-triplet", + import_path="dynaclr.evaluation.predict_triplet.main", + short_help="Per-reporter triplet embedding inference from a collection + checkpoint", + ) +) + def main(): """Run the DynaCLR CLI. diff --git a/applications/dynaclr/src/dynaclr/evaluation/paths.py b/applications/dynaclr/src/dynaclr/evaluation/paths.py new file mode 100644 index 000000000..480717b53 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/paths.py @@ -0,0 +1,180 @@ +"""Canonical grammar for DynaCLR embedding artifact paths. + +Every per-marker embedding zarr is located by a single canonical tuple +``(dataset, model_family, run, ckpt_name, marker)``. The directory tree *is* the +index — there is no registry or manifest file. All producers and consumers +(``predict-triplet``, ``split-embeddings``, the embedding-consistency QC, MMD / +linear-classifier pooling, and the Nextflow ``eval_from_embeddings`` entry) +import these functions so the convention is defined in exactly one place. + +Convention +---------- + {datasets_root}/{dataset}/2-phenotyping/predictions/ + {model_family}/{run}/{ckpt_name}/{marker}.zarr + +The dataset is encoded in the *path*, so the filename is just ``{marker}.zarr``; +multiple markers of one physical dataset co-locate in the same +``{model_family}/{run}/{ckpt_name}/`` directory. "Everything computed for this +dataset" is its ``2-phenotyping/predictions/`` subtree; pooling across datasets +for one model/run/checkpoint is a single glob (:func:`iter_embeddings`). + +Roots are module-level constants that every function takes as a defaulted +argument — global by default, overridable per call (pass ``datasets_root`` in +tests). The root is a fixed project fact, so it is a constant here rather than an +environment variable. +""" + +from __future__ import annotations + +from pathlib import Path + +#: Canonical base for AI-ready datasets and their phenotyping artifacts. +DATASETS_ROOT = Path("/hpc/projects/intracellular_dashboard/organelle_dynamics") + +#: Per-dataset phenotyping subfolder (stage 2 of the dataset pipeline). +PHENOTYPING_DIR = "2-phenotyping" + +#: Prediction artifacts subfolder under the phenotyping dir. +PREDICTIONS_DIR = "predictions" + + +def dataset_name_from_data_path(data_path: str | Path) -> str: + """Derive the dataset name from an OME-Zarr ``data_path``. + + The dataset root is the parent of the ``{dataset}.zarr`` store, and the + dataset name is that parent's directory name — e.g. + ``.../organelle_dynamics/2026_07_01_A549/2026_07_01_A549.zarr`` → + ``2026_07_01_A549``. + + Parameters + ---------- + data_path : str or Path + Path to the dataset OME-Zarr store (``.../{dataset}/{dataset}.zarr``). + + Returns + ------- + str + The dataset (directory) name. + """ + return Path(data_path).parent.name + + +def dataset_root_from_data_path(data_path: str | Path) -> Path: + """Return the dataset folder (parent of the ``{dataset}.zarr`` store). + + Parameters + ---------- + data_path : str or Path + Path to the dataset OME-Zarr store. + + Returns + ------- + Path + The dataset root directory that owns the ``2-phenotyping/`` subtree. + """ + return Path(data_path).parent + + +def predictions_root(dataset: str, datasets_root: str | Path = DATASETS_ROOT) -> Path: + """Return the ``2-phenotyping/predictions`` root for a dataset. + + Parameters + ---------- + dataset : str + Dataset (directory) name. + datasets_root : str or Path, optional + Base under which datasets live. Defaults to :data:`DATASETS_ROOT`. + + Returns + ------- + Path + ``{datasets_root}/{dataset}/2-phenotyping/predictions``. + """ + return Path(datasets_root) / dataset / PHENOTYPING_DIR / PREDICTIONS_DIR + + +def prediction_dir( + dataset: str, + model_family: str, + run: str, + ckpt_name: str, + datasets_root: str | Path = DATASETS_ROOT, +) -> Path: + """Return the model/run/checkpoint-scoped directory holding per-marker zarrs. + + Parameters + ---------- + dataset : str + Dataset (directory) name. + model_family, run, ckpt_name : str + Provenance identity of the embedding-producing model. + datasets_root : str or Path, optional + Base under which datasets live. Defaults to :data:`DATASETS_ROOT`. + + Returns + ------- + Path + ``{predictions_root}/{model_family}/{run}/{ckpt_name}``. + """ + return predictions_root(dataset, datasets_root) / model_family / run / ckpt_name + + +def embedding_store( + dataset: str, + model_family: str, + run: str, + ckpt_name: str, + marker: str, + datasets_root: str | Path = DATASETS_ROOT, +) -> Path: + """Return the canonical per-marker embedding zarr path. + + Parameters + ---------- + dataset : str + Dataset (directory) name. + model_family, run, ckpt_name : str + Provenance identity of the embedding-producing model. + marker : str + Reporter/marker label; becomes the zarr filename. + datasets_root : str or Path, optional + Base under which datasets live. Defaults to :data:`DATASETS_ROOT`. + + Returns + ------- + Path + ``{prediction_dir}/{marker}.zarr``. + """ + return prediction_dir(dataset, model_family, run, ckpt_name, datasets_root) / f"{marker}.zarr" + + +def iter_embeddings( + model_family: str, + run: str, + ckpt_name: str, + marker: str | None = None, + datasets_root: str | Path = DATASETS_ROOT, +) -> list[Path]: + """Glob per-marker embedding zarrs for one model/run/checkpoint across datasets. + + This is the pooling entry point for downstream tasks (embedding-consistency + QC, MMD, linear classifiers): fix the model/run/checkpoint and optionally a + marker, and collect every dataset's matching zarr. + + Parameters + ---------- + model_family, run, ckpt_name : str + Provenance identity to pool over. + marker : str or None, optional + Restrict to one marker; ``None`` matches every marker. + datasets_root : str or Path, optional + Base under which datasets live. Defaults to :data:`DATASETS_ROOT`. + + Returns + ------- + list[Path] + Sorted matching zarr paths (one per dataset x marker). + """ + pattern = f"{marker}.zarr" if marker is not None else "*.zarr" + glob = f"*/{PHENOTYPING_DIR}/{PREDICTIONS_DIR}/{model_family}/{run}/{ckpt_name}/{pattern}" + return sorted(Path(datasets_root).glob(glob)) diff --git a/applications/dynaclr/src/dynaclr/evaluation/predict_triplet.py b/applications/dynaclr/src/dynaclr/evaluation/predict_triplet.py new file mode 100644 index 000000000..31034dfb6 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/predict_triplet.py @@ -0,0 +1,382 @@ +"""One-call per-reporter triplet embedding inference. + +Runs a trained ``ContrastiveModule`` checkpoint over an OME-Zarr + tracking +store (the triplet path; see ``docs/DAGs/inference_triplet.md``) and writes one +embeddings zarr per reporter. The collection YAML is the single source of truth: +each ``ChannelEntry`` carries a zarr ``name``, a ``marker`` label, and optional +``wells`` (empty = all wells). For bag-of-channels models (``in_channels=1``) +each channel is embedded as its own single-channel sample, so we run predict +once per channel, restricting to that channel's wells via ``fit_include_wells``. + +Outputs are dataset/model/run/checkpoint-scoped so every embeddings zarr traces +back to what produced it, lives under its own dataset, and two models/checkpoints +can coexist (see :mod:`dynaclr.evaluation.paths`): + + {dataset}/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr + +This retires the per-dataset ``generate_predict_configs.py`` copies that caused +inference code to fragment across dataset folders. + +Usage +----- +dynaclr predict-triplet \ + -c collection.yml \ + --checkpoint /path/to/epoch=105-step=84800.ckpt \ + --model-family DynaCLR-2D-MIP-BagOfChannels \ + --run 2d-mip-...-fix-shuffler \ + --ckpt-name epoch105-step84800 \ + --z-range 15 45 --z-reduction mip --reference-pixel-size 0.1494 \ + --no-labelfree # skip Phase3D / brightfield channels +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +import click + +from dynaclr.evaluation.paths import ( + DATASETS_ROOT, + dataset_name_from_data_path, + embedding_store, +) +from viscy_data.channel_utils import parse_channel_name +from viscy_data.collection import Collection, load_collection + + +@dataclass +class ReporterRun: + """One per-reporter predict run derived from a collection channel. + + Parameters + ---------- + experiment : str + Experiment (dataset) name. + marker : str + Reporter/marker label used in the output filename. + channel : str + Zarr channel name fed as the single ``source_channel``. + wells : list[str] | None + Wells to restrict predict to (``fit_include_wells``). ``None`` means + all wells (the channel is valid everywhere). + data_path : str + Resolved OME-Zarr store path. + tracks_path : str + Resolved tracking store path. + pixel_size_xy_um : float | None + Inference dataset pixel size, for the rescale log line. + output_path : Path + Destination embeddings zarr. + is_labelfree : bool + Whether the channel is label-free (phase/brightfield). + """ + + experiment: str + marker: str + channel: str + wells: list[str] | None + data_path: str + tracks_path: str + pixel_size_xy_um: float | None + output_path: Path + is_labelfree: bool + + +def _slug(marker: str) -> str: + """Filesystem-safe marker slug (kept readable, not lowercased).""" + return marker + + +def plan_predict_runs( + collection: Collection, + *, + model_family: str, + run: str, + ckpt_name: str, + datasets_root: str | Path = DATASETS_ROOT, + markers: list[str] | None = None, + include_labelfree: bool = True, +) -> list[ReporterRun]: + """Build the list of per-reporter predict runs from a collection. + + Pure planning logic (no I/O beyond path construction) so it can be unit + tested without a GPU or real zarr. + + Each embedding zarr lands in the dataset-centric tree + ``{dataset}/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr`` + (see :mod:`dynaclr.evaluation.paths`). The dataset is derived per experiment + from its ``data_path``, so multiple markers of one physical dataset + co-locate in the same ``{model_family}/{run}/{ckpt_name}/`` directory. + + Parameters + ---------- + collection : Collection + Loaded collection (``${datasets_root}`` already resolved). + model_family, run, ckpt_name : str + Provenance identity, keyed into the output path. + datasets_root : str or Path, optional + Base under which datasets live. Defaults to + :data:`dynaclr.evaluation.paths.DATASETS_ROOT`; each experiment's + dataset folder is derived from its ``data_path``, so this only matters + if that folder is not already under the canonical root. + markers : list[str] | None + If given, only emit runs for these marker labels. ``None`` = all. + include_labelfree : bool + If ``False``, skip label-free channels (phase/brightfield), resolved + by name via :func:`parse_channel_name`. + + Returns + ------- + list[ReporterRun] + One entry per (experiment, channel) to embed. + """ + runs: list[ReporterRun] = [] + for exp in collection.experiments: + dataset = dataset_name_from_data_path(exp.data_path) + for ch in exp.channels: + is_labelfree = parse_channel_name(ch.name)["channel_type"] == "labelfree" + if not include_labelfree and is_labelfree: + continue + if markers is not None and ch.marker not in markers: + continue + runs.append( + ReporterRun( + experiment=exp.name, + marker=ch.marker, + channel=ch.name, + wells=list(ch.wells) if ch.wells else None, + data_path=exp.data_path, + tracks_path=exp.tracks_path, + pixel_size_xy_um=exp.pixel_size_xy_um, + output_path=embedding_store( + dataset, + model_family, + run, + ckpt_name, + _slug(ch.marker), + datasets_root=datasets_root, + ), + is_labelfree=is_labelfree, + ) + ) + if not runs: + raise ValueError( + "No reporter runs planned. Check that the collection has channels " + "matching --markers / --no-labelfree filters." + ) + return runs + + +def _run_predict( + entry: ReporterRun, + *, + checkpoint: str, + encoder_kwargs: dict, + example_input_array_shape: list[int], + z_range: tuple[int, int], + z_reduction: str | None, + reference_pixel_size: float | None, + yx_patch_size: tuple[int, int], + batch_size: int, + num_workers: int, + uns_metadata: dict | None = None, +) -> None: + """Load the checkpoint and run Lightning predict for one reporter.""" + import torch + from lightning.pytorch import Trainer, seed_everything + + from dynaclr.engine import ContrastiveModule + from viscy_data.triplet import TripletDataModule + from viscy_models.contrastive import ContrastiveEncoder + from viscy_transforms import NormalizeSampled + from viscy_utils.callbacks.embedding_writer import EmbeddingWriter + + seed_everything(42) + + encoder = ContrastiveEncoder(**encoder_kwargs) + module = ContrastiveModule(encoder=encoder, example_input_array_shape=example_input_array_shape) + ckpt = torch.load(checkpoint, map_location="cpu", weights_only=True) + module.load_state_dict(ckpt["state_dict"]) + + datamodule = TripletDataModule( + data_path=entry.data_path, + tracks_path=entry.tracks_path, + source_channel=[entry.channel], + z_range=list(z_range), + z_reduction=z_reduction, + reference_pixel_size=reference_pixel_size, + initial_yx_patch_size=list(yx_patch_size), + final_yx_patch_size=list(yx_patch_size), + batch_size=batch_size, + num_workers=num_workers, + fit_include_wells=entry.wells, + normalizations=[ + NormalizeSampled( + keys=[entry.channel], + level="fov_statistics", + subtrahend="mean", + divisor="std", + ) + ], + ) + + writer = EmbeddingWriter( + output_path=entry.output_path, + embedding_key="features", + overwrite=True, + pca_kwargs=None, + phate_kwargs=None, + umap_kwargs=None, + uns_metadata=uns_metadata, + ) + trainer = Trainer( + accelerator="gpu", + devices=1, + precision="32-true", + callbacks=[writer], + inference_mode=True, + logger=False, + ) + entry.output_path.parent.mkdir(parents=True, exist_ok=True) + trainer.predict(module, datamodule=datamodule, return_predictions=False) + + +# ConvNeXt-tiny bag-of-channels encoder — matches DynaCLR-2D-MIP-BagOfChannels. +_DEFAULT_ENCODER_KWARGS = { + "backbone": "convnext_tiny", + "in_channels": 1, + "in_stack_depth": 1, + "stem_kernel_size": [1, 4, 4], + "stem_stride": [1, 4, 4], + "embedding_dim": 768, + "projection_dim": 32, + "drop_path_rate": 0.0, +} + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "-c", + "--collection", + "collection_path", + type=click.Path(exists=True, path_type=Path), + required=True, + help="Collection YAML (experiments with channels: name/marker/wells).", +) +@click.option( + "--checkpoint", + type=click.Path(exists=True, path_type=Path), + required=True, + help="Trained ContrastiveModule .ckpt.", +) +@click.option("--model-family", required=True, help="Model family (top output folder).") +@click.option("--run", required=True, help="Training run/version (second output folder).") +@click.option("--ckpt-name", required=True, help="Checkpoint label, e.g. epoch105-step84800.") +@click.option( + "--datasets-root", + type=click.Path(path_type=Path), + default=DATASETS_ROOT, + show_default=True, + help="Base under which datasets live; each embedding lands in " + "/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr.", +) +@click.option( + "--markers", + default=None, + help="Comma-separated marker subset to embed (default: all channels).", +) +@click.option( + "--no-labelfree", + "include_labelfree", + is_flag=True, + default=True, + flag_value=False, + help="Skip label-free (phase/brightfield) channels.", +) +@click.option("--z-range", nargs=2, type=int, default=(15, 45), show_default=True, help="Z window (start stop).") +@click.option( + "--z-reduction", + type=click.Choice(["mip", "center"]), + default="mip", + show_default=True, + help="Collapse z_range to one slice for a 2D model.", +) +@click.option( + "--reference-pixel-size", + type=float, + default=0.1494, + show_default=True, + help="Model training pixel size (µm/px) for patch rescaling.", +) +@click.option("--yx-patch-size", nargs=2, type=int, default=(160, 160), show_default=True, help="Final YX patch.") +@click.option("--batch-size", type=int, default=32, show_default=True) +@click.option("--num-workers", type=int, default=0, show_default=True, help="Must be 0 for predict (zarr-fork).") +def main( + collection_path: Path, + checkpoint: Path, + model_family: str, + run: str, + ckpt_name: str, + datasets_root: Path, + markers: str | None, + include_labelfree: bool, + z_range: tuple[int, int], + z_reduction: str, + reference_pixel_size: float, + yx_patch_size: tuple[int, int], + batch_size: int, + num_workers: int, +) -> None: + """Run per-reporter triplet embedding inference from a collection + checkpoint.""" + collection = load_collection(collection_path) + marker_list = [m.strip() for m in markers.split(",")] if markers else None + runs = plan_predict_runs( + collection, + model_family=model_family, + run=run, + ckpt_name=ckpt_name, + datasets_root=datasets_root, + markers=marker_list, + include_labelfree=include_labelfree, + ) + + click.echo("=== Provenance ===") + click.echo(f"model_family : {model_family}") + click.echo(f"run : {run}") + click.echo(f"checkpoint : {ckpt_name} ({checkpoint})") + click.echo(f"planned runs : {len(runs)}") + for r in runs: + wells = "all wells" if r.wells is None else ", ".join(r.wells) + click.echo(f" - {r.experiment} / {r.marker} ({r.channel}) [{wells}] -> {r.output_path}") + + example_input_array_shape = [1, 1, 1, yx_patch_size[0], yx_patch_size[1]] + for r in runs: + click.echo(f"\n=== Predicting: {r.experiment} / {r.marker} ===") + provenance = { + "model_family": model_family, + "run": run, + "ckpt_name": ckpt_name, + "checkpoint": str(checkpoint), + "collection_path": str(collection_path), + "marker": r.marker, + "channel": r.channel, + } + _run_predict( + r, + checkpoint=str(checkpoint), + encoder_kwargs=_DEFAULT_ENCODER_KWARGS, + example_input_array_shape=example_input_array_shape, + z_range=z_range, + z_reduction=z_reduction, + reference_pixel_size=reference_pixel_size, + yx_patch_size=yx_patch_size, + batch_size=batch_size, + num_workers=num_workers, + uns_metadata=provenance, + ) + click.echo(f"\nWrote {len(runs)} per-reporter embeddings zarrs.") + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/tests/test_paths.py b/applications/dynaclr/tests/test_paths.py new file mode 100644 index 000000000..c33f23652 --- /dev/null +++ b/applications/dynaclr/tests/test_paths.py @@ -0,0 +1,76 @@ +"""Unit tests for the canonical embedding path grammar. + +Covers :mod:`dynaclr.evaluation.paths` — pure path construction and the pooling +glob. No real zarr required; :func:`iter_embeddings` is exercised against a +temporary directory tree. +""" + +from pathlib import Path + +from dynaclr.evaluation.paths import ( + DATASETS_ROOT, + dataset_name_from_data_path, + dataset_root_from_data_path, + embedding_store, + iter_embeddings, + prediction_dir, + predictions_root, +) + +MF = "DynaCLR-2D-MIP-BagOfChannels" +RUN = "2d-mip-fix-shuffler" +CKPT = "epoch105-step84800" + + +def test_dataset_name_from_data_path(): + assert dataset_name_from_data_path("/data/2026_07_01_ZIKV/2026_07_01_ZIKV.zarr") == "2026_07_01_ZIKV" + + +def test_dataset_root_from_data_path(): + assert dataset_root_from_data_path("/data/2026_07_01_ZIKV/2026_07_01_ZIKV.zarr") == Path("/data/2026_07_01_ZIKV") + + +def test_predictions_root_default_and_override(): + assert predictions_root("ds") == DATASETS_ROOT / "ds" / "2-phenotyping" / "predictions" + assert predictions_root("ds", datasets_root="/base") == Path("/base/ds/2-phenotyping/predictions") + + +def test_prediction_dir(): + assert prediction_dir("ds", MF, RUN, CKPT, datasets_root="/base") == Path( + f"/base/ds/2-phenotyping/predictions/{MF}/{RUN}/{CKPT}" + ) + + +def test_embedding_store(): + assert embedding_store("ds", MF, RUN, CKPT, "SEC61B", datasets_root="/base") == Path( + f"/base/ds/2-phenotyping/predictions/{MF}/{RUN}/{CKPT}/SEC61B.zarr" + ) + + +def _make_tree(root: Path, dataset: str, markers: list[str]) -> None: + d = root / dataset / "2-phenotyping" / "predictions" / MF / RUN / CKPT + d.mkdir(parents=True, exist_ok=True) + for m in markers: + (d / f"{m}.zarr").mkdir() + + +def test_iter_embeddings_pools_across_datasets(tmp_path): + """One marker across multiple datasets is collected by a single glob.""" + _make_tree(tmp_path, "ds_a", ["SEC61B", "TOMM20"]) + _make_tree(tmp_path, "ds_b", ["SEC61B"]) + found = iter_embeddings(MF, RUN, CKPT, marker="SEC61B", datasets_root=tmp_path) + assert [p.name for p in found] == ["SEC61B.zarr", "SEC61B.zarr"] + assert {p.parents[5].name for p in found} == {"ds_a", "ds_b"} + + +def test_iter_embeddings_all_markers(tmp_path): + """marker=None matches every marker.""" + _make_tree(tmp_path, "ds_a", ["SEC61B", "TOMM20"]) + found = iter_embeddings(MF, RUN, CKPT, datasets_root=tmp_path) + assert sorted(p.name for p in found) == ["SEC61B.zarr", "TOMM20.zarr"] + + +def test_iter_embeddings_respects_run_scope(tmp_path): + """A different run is not matched.""" + _make_tree(tmp_path, "ds_a", ["SEC61B"]) + assert iter_embeddings(MF, "other-run", CKPT, datasets_root=tmp_path) == [] diff --git a/applications/dynaclr/tests/test_predict_triplet.py b/applications/dynaclr/tests/test_predict_triplet.py new file mode 100644 index 000000000..2a5fd6603 --- /dev/null +++ b/applications/dynaclr/tests/test_predict_triplet.py @@ -0,0 +1,105 @@ +"""Unit tests for the predict-triplet planning logic. + +Covers ``plan_predict_runs`` — the pure per-reporter run planner that turns a +collection's channels into model/run/checkpoint-scoped predict jobs. No GPU or +real zarr required; the predict execution itself is exercised by +``test_inference_reproducibility.py`` (hpc_integration). +""" + +from pathlib import Path + +import pytest + +from dynaclr.evaluation.predict_triplet import plan_predict_runs +from viscy_data.collection import ChannelEntry, Collection, ExperimentEntry + +MODEL_FAMILY = "DynaCLR-2D-MIP-BagOfChannels" +RUN = "2d-mip-fix-shuffler" +CKPT = "epoch105-step84800" + + +def _box_collection() -> Collection: + """Multi-organelle box plate: GFP reporter varies by column, mCherry uniform, plus phase.""" + exp = ExperimentEntry( + name="2026_07_01_ZIKV", + data_path="/data/2026_07_01_ZIKV/2026_07_01_ZIKV.zarr", + tracks_path="/data/2026_07_01_ZIKV/tracking.zarr", + channels=[ + ChannelEntry(name="raw GFP EX488 EM525-45", marker="SEC61B", wells=["A/2", "B/2"]), + ChannelEntry(name="raw GFP EX488 EM525-45", marker="TOMM20", wells=["A/3", "B/3"]), + ChannelEntry(name="raw GFP EX488 EM525-45", marker="G3BP1", wells=["A/4", "B/4"]), + ChannelEntry(name="raw mCherry EX561 EM600-37", marker="pAL17"), # empty wells = all + ChannelEntry(name="Phase3D", marker="Phase3D"), + ], + perturbation_wells={"uninfected": ["A/2"], "ZIKV": ["B/2"]}, + pixel_size_xy_um=0.1133, + ) + return Collection(name="box", experiments=[exp]) + + +def _plan(**kwargs): + return plan_predict_runs( + _box_collection(), + model_family=MODEL_FAMILY, + run=RUN, + ckpt_name=CKPT, + datasets_root="/data", + **kwargs, + ) + + +def test_one_run_per_channel_entry(): + """Each channel entry (incl. repeated GFP) becomes its own run.""" + runs = _plan() + assert [r.marker for r in runs] == ["SEC61B", "TOMM20", "G3BP1", "pAL17", "Phase3D"] + + +def test_per_reporter_well_subset(): + """Wells carry through as fit_include_wells; empty -> None (all wells).""" + runs = {r.marker: r for r in _plan()} + assert runs["SEC61B"].wells == ["A/2", "B/2"] + assert runs["TOMM20"].wells == ["A/3", "B/3"] + assert runs["pAL17"].wells is None # empty list -> all wells + + +def test_output_path_is_dataset_centric(): + """Output tree is /2-phenotyping/predictions/{model}/{run}/{ckpt}/{marker}.zarr.""" + run = next(r for r in _plan() if r.marker == "SEC61B") + assert run.output_path == Path( + "/data/2026_07_01_ZIKV/2-phenotyping/predictions/" + "DynaCLR-2D-MIP-BagOfChannels/2d-mip-fix-shuffler/epoch105-step84800/SEC61B.zarr" + ) + + +def test_markers_of_one_dataset_colocate(): + """Multiple markers of one physical dataset share the {model}/{run}/{ckpt} dir.""" + runs = {r.marker: r for r in _plan()} + assert runs["SEC61B"].output_path.parent == runs["TOMM20"].output_path.parent + assert runs["SEC61B"].output_path.name == "SEC61B.zarr" + assert runs["TOMM20"].output_path.name == "TOMM20.zarr" + + +def test_no_labelfree_drops_phase(): + """--no-labelfree removes phase/brightfield channels, keeps fluorescence.""" + markers = [r.marker for r in _plan(include_labelfree=False)] + assert "Phase3D" not in markers + assert markers == ["SEC61B", "TOMM20", "G3BP1", "pAL17"] + + +def test_labelfree_flag_marks_phase(): + """Phase3D is flagged label-free; fluorescence reporters are not.""" + runs = {r.marker: r for r in _plan()} + assert runs["Phase3D"].is_labelfree is True + assert runs["SEC61B"].is_labelfree is False + + +def test_markers_subset(): + """--markers restricts to the requested marker labels.""" + markers = [r.marker for r in _plan(markers=["SEC61B", "pAL17"])] + assert markers == ["SEC61B", "pAL17"] + + +def test_empty_plan_raises(): + """A filter that matches nothing is an error, not a silent no-op.""" + with pytest.raises(ValueError, match="No reporter runs planned"): + _plan(markers=["does-not-exist"]) diff --git a/packages/viscy-utils/src/viscy_utils/callbacks/embedding_writer.py b/packages/viscy-utils/src/viscy_utils/callbacks/embedding_writer.py index 373507b8f..24c2006cb 100644 --- a/packages/viscy-utils/src/viscy_utils/callbacks/embedding_writer.py +++ b/packages/viscy-utils/src/viscy_utils/callbacks/embedding_writer.py @@ -237,6 +237,10 @@ class EmbeddingWriter(BasePredictionWriter): Keyword arguments passed to PCA, by default None. overwrite : bool, optional Whether to overwrite existing output, by default False. + uns_metadata : dict, optional + Extra provenance stored in ``adata.uns``, merged with the auto-collected + data/tracks paths. Use to stamp model/run/checkpoint/collection identity + so a moved zarr stays self-describing. By default None. """ def __init__( @@ -248,6 +252,7 @@ def __init__( phate_kwargs: dict | None = None, pca_kwargs: dict | None = None, overwrite: bool = False, + uns_metadata: dict | None = None, ): super().__init__(write_interval) self.output_path = Path(output_path) @@ -256,6 +261,7 @@ def __init__( self.phate_kwargs = phate_kwargs self.pca_kwargs = pca_kwargs self.overwrite = overwrite + self.uns_metadata = uns_metadata def on_predict_start(self, trainer: Trainer, pl_module: LightningModule) -> None: """Check output path before prediction starts.""" @@ -275,6 +281,10 @@ def write_on_epoch_end( projections = _move_and_stack_embeddings(predictions, "projections") ultrack_indices = pd.concat([pd.DataFrame(p["index"]) for p in predictions]) + uns_metadata = collect_data_provenance(trainer) + if self.uns_metadata: + uns_metadata.update(self.uns_metadata) + write_embedding_dataset( output_path=self.output_path, features=features, @@ -285,5 +295,5 @@ def write_on_epoch_end( phate_kwargs=self.phate_kwargs, pca_kwargs=self.pca_kwargs, overwrite=self.overwrite, - uns_metadata=collect_data_provenance(trainer), + uns_metadata=uns_metadata, ) diff --git a/packages/viscy-utils/tests/test_embedding_writer.py b/packages/viscy-utils/tests/test_embedding_writer.py new file mode 100644 index 000000000..7f2399677 --- /dev/null +++ b/packages/viscy-utils/tests/test_embedding_writer.py @@ -0,0 +1,49 @@ +"""Tests for embedding writer provenance stamping.""" + +import anndata as ad +import numpy as np +import pandas as pd + +from viscy_utils.callbacks.embedding_writer import write_embedding_dataset + + +def _index_df(n: int) -> pd.DataFrame: + return pd.DataFrame( + { + "fov_name": [f"A/1/{i}" for i in range(n)], + "track_id": list(range(n)), + "t": [0] * n, + } + ) + + +def test_uns_metadata_round_trips(tmp_path): + """Provenance passed as uns_metadata lands in adata.uns on disk.""" + out = tmp_path / "emb.zarr" + provenance = { + "model_family": "DynaCLR-2D-MIP-BagOfChannels", + "run": "2d-mip-fix-shuffler", + "ckpt_name": "epoch105-step84800", + "collection_path": "/configs/collections/foo.yml", + "marker": "SEC61B", + } + write_embedding_dataset( + output_path=out, + features=np.random.default_rng(0).random((3, 4), dtype=np.float32), + index_df=_index_df(3), + uns_metadata=provenance, + ) + uns = ad.read_zarr(out).uns + for key, value in provenance.items(): + assert uns[key] == value + + +def test_no_uns_metadata_is_fine(tmp_path): + """Writing without provenance still works (backward compatible).""" + out = tmp_path / "emb.zarr" + write_embedding_dataset( + output_path=out, + features=np.random.default_rng(1).random((2, 4), dtype=np.float32), + index_df=_index_df(2), + ) + assert ad.read_zarr(out).n_obs == 2 From b28e38ae3419ecb433fc56a94262365fd4c44b18 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:08:12 -0700 Subject: [PATCH 43/89] feat(dynaclr): evaluate reusable dataset-scoped embeddings --- applications/dynaclr/nextflow/main.nf | 24 ++ applications/dynaclr/nextflow/nextflow.config | 3 + .../dynaclr/nextflow/workflows/_downstream.nf | 268 ++++++++++++++++++ .../workflows/eval_from_embeddings.nf | 42 +++ .../dynaclr/nextflow/workflows/evaluation.nf | 251 +--------------- .../dynaclr/evaluation/split_embeddings.py | 180 ++++++++++-- .../dynaclr/tests/test_split_embeddings.py | 102 +++++++ 7 files changed, 596 insertions(+), 274 deletions(-) create mode 100644 applications/dynaclr/nextflow/workflows/_downstream.nf create mode 100644 applications/dynaclr/nextflow/workflows/eval_from_embeddings.nf create mode 100644 applications/dynaclr/tests/test_split_embeddings.py diff --git a/applications/dynaclr/nextflow/main.nf b/applications/dynaclr/nextflow/main.nf index dfacbec75..ee1f8f40e 100644 --- a/applications/dynaclr/nextflow/main.nf +++ b/applications/dynaclr/nextflow/main.nf @@ -13,6 +13,13 @@ // --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ // -resume // +// # Evaluation from pre-computed embeddings (skips predict/split) +// nextflow run applications/dynaclr/nextflow/main.nf -entry eval_from_embeddings \ +// --eval_config /path/to/eval_config.yaml \ +// --embeddings_glob '/hpc/projects/intracellular_dashboard/organelle_dynamics/*/2-phenotyping/predictions/MODEL/RUN/CKPT/*.zarr' \ +// --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ +// -resume +// // # Training preprocessing (collection → parquet) // nextflow run applications/dynaclr/nextflow/main.nf -entry training_preprocessing \ // --collection_yaml /path/to/collection.yml \ @@ -26,6 +33,7 @@ nextflow.enable.dsl = 2 include { EVALUATION } from './workflows/evaluation' +include { EVAL_FROM_EMBEDDINGS } from './workflows/eval_from_embeddings' include { TRAINING_PREPROCESSING } from './workflows/training_preprocessing' @@ -36,6 +44,7 @@ workflow { Use one of: -entry evaluation (requires --eval_config) + -entry eval_from_embeddings (requires --eval_config, --embeddings_glob) -entry training_preprocessing (requires --collection_yaml, --parquet_out) """.stripIndent() } @@ -53,6 +62,21 @@ workflow evaluation { } +workflow eval_from_embeddings { + if (!params.eval_config) { + error "ERROR: --eval_config is required for -entry eval_from_embeddings" + } + if (!params.embeddings_glob) { + error "ERROR: --embeddings_glob is required for -entry eval_from_embeddings" + } + EVAL_FROM_EMBEDDINGS( + file(params.eval_config), + params.embeddings_glob, + params.workspace_dir + ) +} + + workflow training_preprocessing { if (!params.collection_yaml) { error "ERROR: --collection_yaml is required for -entry training_preprocessing" diff --git a/applications/dynaclr/nextflow/nextflow.config b/applications/dynaclr/nextflow/nextflow.config index 3920fa2ed..b552b7e1c 100644 --- a/applications/dynaclr/nextflow/nextflow.config +++ b/applications/dynaclr/nextflow/nextflow.config @@ -19,6 +19,9 @@ params { // evaluation entry eval_config = null // Required for -entry evaluation + // eval_from_embeddings entry + embeddings_glob = null // Glob of pre-computed per-marker embedding zarrs + // training_preprocessing entry collection_yaml = null // Required for -entry training_preprocessing parquet_out = null // Required for -entry training_preprocessing diff --git a/applications/dynaclr/nextflow/workflows/_downstream.nf b/applications/dynaclr/nextflow/workflows/_downstream.nf new file mode 100644 index 000000000..7b0d52bd1 --- /dev/null +++ b/applications/dynaclr/nextflow/workflows/_downstream.nf @@ -0,0 +1,268 @@ +// DynaCLR Downstream Sub-workflow +// +// Shared downstream DAG factored out of the evaluation workflow so it can be +// reused from multiple entry points (evaluation, eval_from_embeddings). +// Consumes per-experiment .zarr paths + the parsed JSON manifest and runs +// (reduce / smoothness / mmd / classifiers / plots). Barrier semantics and +// per-experiment-zarr write-order guarantees are identical to the original +// evaluation workflow. + +include { REDUCE } from '../modules/evaluation/reduce' +include { REDUCE_COMBINED } from '../modules/evaluation/reduce_combined' +include { PLOT } from '../modules/evaluation/plot' +include { PLOT_COMBINED } from '../modules/evaluation/plot_combined' +include { SMOOTHNESS } from '../modules/evaluation/smoothness' +include { MMD } from '../modules/evaluation/mmd' +include { MMD_COMBINED } from '../modules/evaluation/mmd_combined' +include { MMD_PLOT_HEATMAP } from '../modules/evaluation/mmd_plot_heatmap' +include { SMOOTHNESS_GATHER } from '../modules/evaluation/smoothness_gather' +include { LINEAR_CLASSIFIERS } from '../modules/evaluation/linear_classifiers' +include { APPEND_ANNOTATIONS } from '../modules/evaluation/append_annotations' +include { APPEND_PREDICTIONS } from '../modules/evaluation/append_predictions' + + +workflow DOWNSTREAM { + take: + per_exp_zarrs_ch // channel of per-experiment .zarr path strings + manifest_ch // parsed JSON manifest (the map from PREPARE_CONFIGS) + workspace_dir + + main: + split_done_ch = per_exp_zarrs_ch.collect().map { 'done' } + + // ----------------------------------------------------------------------- + // Step 4a: Per-experiment dim reduction (scatter) — after split + // ----------------------------------------------------------------------- + reduce_yaml_ch = manifest_ch + .filter { it.containsKey('reduce') } + .map { it.reduce } + + reduce_inputs_ch = per_exp_zarrs_ch.combine(reduce_yaml_ch) + + REDUCE( + reduce_inputs_ch.map { zarr, yaml -> zarr }, + reduce_inputs_ch.map { zarr, yaml -> yaml }, + workspace_dir + ) + + // ----------------------------------------------------------------------- + // Step 4b: Combined dim reduction (gather) — after all REDUCE finish + // ----------------------------------------------------------------------- + reduce_combined_yaml_ch = manifest_ch + .filter { it.containsKey('reduce_combined') } + .map { it.reduce_combined } + + REDUCE_COMBINED( + REDUCE.out.zarr_path.collect(), + reduce_combined_yaml_ch, + workspace_dir + ) + + // Barrier: per-experiment zarr writes (X_pca_combined / X_phate_combined) + // must finish before APPEND_ANNOTATIONS / APPEND_PREDICTIONS start writing + // to the same zarrs. ifEmpty('skip') keeps the chain alive when + // reduce_combined isn't in steps. + reduce_combined_done_ch = REDUCE_COMBINED.out.zarr_paths + .ifEmpty('skip') + .first() + + // ----------------------------------------------------------------------- + // Step 5: Smoothness (scatter, depends only on split) + // ----------------------------------------------------------------------- + smoothness_yaml_ch = manifest_ch + .filter { it.containsKey('smoothness') } + .map { it.smoothness } + + smoothness_inputs_ch = per_exp_zarrs_ch.combine(smoothness_yaml_ch) + + SMOOTHNESS( + smoothness_inputs_ch.map { zarr, yaml -> zarr }, + smoothness_inputs_ch.map { zarr, yaml -> yaml }, + workspace_dir + ) + + smoothness_dir_ch = manifest_ch.map { "${it.output_dir}/smoothness" } + smoothness_done_ch = SMOOTHNESS.out.zarr_path.collect().map { 'done' } + SMOOTHNESS_GATHER(smoothness_dir_ch, smoothness_done_ch) + + // ----------------------------------------------------------------------- + // Step 6: MMD per-experiment (scatter, depends only on split) + // ----------------------------------------------------------------------- + mmd_block_inputs_ch = manifest_ch + .filter { it.containsKey('mmd_blocks') && it.mmd_blocks.size() > 0 } + .flatMap { manifest -> + manifest.mmd_blocks.collect { block_name -> + [block_name, manifest["mmd_${block_name}"]] + } + } + + mmd_per_exp_ch = per_exp_zarrs_ch + .combine(mmd_block_inputs_ch) + .map { zarr, block_name, mmd_yaml -> tuple(zarr, block_name, mmd_yaml) } + + MMD(mmd_per_exp_ch, workspace_dir) + + mmd_heatmap_dirs_ch = manifest_ch + .filter { it.containsKey('mmd_blocks') && it.mmd_blocks.size() > 0 } + .flatMap { manifest -> + manifest.mmd_blocks.collect { block_name -> manifest["mmd_${block_name}_dir"] } + } + + MMD.out.zarr_path.collect() + .combine(mmd_heatmap_dirs_ch) + .map { items -> items[-1] } + | MMD_PLOT_HEATMAP + + // ----------------------------------------------------------------------- + // Step 6b: MMD combined (gather per block, depends only on split) + // ----------------------------------------------------------------------- + mmd_combined_inputs_ch = manifest_ch + .filter { it.containsKey('mmd_combined_blocks') && it.mmd_combined_blocks.size() > 0 } + .flatMap { manifest -> + manifest.mmd_combined_blocks.collect { block_name -> + [block_name, manifest["mmd_${block_name}_cross_exp"]] + } + } + + mmd_combined_zarrs_str_ch = per_exp_zarrs_ch.collect().map { zarrs -> zarrs.join('\n') } + + mmd_combined_ch = mmd_combined_zarrs_str_ch + .combine(mmd_combined_inputs_ch) + .map { zarrs_str, block_name, mmd_yaml -> tuple(zarrs_str, block_name, mmd_yaml) } + + MMD_COMBINED(mmd_combined_ch, workspace_dir) + + // ----------------------------------------------------------------------- + // Step 7: Append annotations — must run AFTER reduce_combined (both + // mutate per-experiment zarrs), and after split (zarrs must exist). + // ----------------------------------------------------------------------- + // Concurrency invariant: per-experiment zarrs have one writer at a time. + // Pre-write order: SPLIT -> REDUCE -> REDUCE_COMBINED -> APPEND_ANNOTATIONS + // -> LINEAR_CLASSIFIERS (reads only) -> APPEND_PREDICTIONS -> PLOT (reads). + aa_yaml_ch = manifest_ch + .filter { it.containsKey('append_annotations') } + .map { it.append_annotations } + + // Extract annotation CSV paths from the YAML so Nextflow content-hashes + // them as task inputs. Without this the resume cache only sees the YAML + // path string and misses content changes in the referenced CSVs. + // Dedupe by absolute path because the recipe references the same CSV + // file from multiple experiments (one annotation file feeds many channels); + // staging would otherwise fail with basename collisions. + aa_csv_ch = aa_yaml_ch.map { yaml_path -> + def yaml_data = new org.yaml.snakeyaml.Yaml().load(new File(yaml_path.toString()).text) + def paths = (yaml_data.annotations ?: []).collect { it.path }.unique() + return paths.collect { file(it) } + } + + aa_ready_ch = split_done_ch.mix(reduce_combined_done_ch).collect().map { 'ready' } + + APPEND_ANNOTATIONS(aa_ready_ch, aa_yaml_ch, aa_csv_ch, workspace_dir) + + aa_done_ch = APPEND_ANNOTATIONS.out.done + .ifEmpty('skip') + .first() + + // ----------------------------------------------------------------------- + // Step 8: Linear classifiers — after append_annotations + // ----------------------------------------------------------------------- + lc_yaml_ch = manifest_ch + .filter { it.containsKey('linear_classifiers') } + .map { it.linear_classifiers } + + // Same annotation-CSV staging as APPEND_ANNOTATIONS so LC cache keys + // also depend on the actual CSV contents. Dedupe (see comment above). + lc_csv_ch = lc_yaml_ch.map { yaml_path -> + def yaml_data = new org.yaml.snakeyaml.Yaml().load(new File(yaml_path.toString()).text) + def paths = (yaml_data.annotations ?: []).collect { it.path }.unique() + return paths.collect { file(it) } + } + + LINEAR_CLASSIFIERS(aa_done_ch, lc_yaml_ch, lc_csv_ch, workspace_dir) + + // ----------------------------------------------------------------------- + // Step 9: Append predictions — after linear classifiers AND split + // ----------------------------------------------------------------------- + // append_predictions reads per-experiment zarrs (produced by SPLIT) and + // writes predicted_* columns to obs. It must wait on BOTH: + // - LINEAR_CLASSIFIERS (when present in steps): pipelines must exist + // - SPLIT: the zarrs to predict on must exist + // For Wave-2 evaluations that fetch pipelines from an external registry + // (no LINEAR_CLASSIFIERS in steps), lc_done is 'skip' immediately and + // only the split dependency keeps APPEND_PREDICTIONS gated. + ap_yaml_ch = manifest_ch + .filter { it.containsKey('append_predictions') } + .map { it.append_predictions } + + lc_done_ch = LINEAR_CLASSIFIERS.out.done + .ifEmpty('skip') + .first() + + // Combine the two upstream signals into one barrier value. + ap_ready_ch = lc_done_ch.mix(split_done_ch).collect().map { 'ready' } + + // Stage the LC pipeline files so Nextflow content-hashes them. The pipelines + // are produced by LINEAR_CLASSIFIERS just above, so the channel must wait + // for lc_done before reading the pipelines_dir glob (otherwise the dir + // doesn't exist yet on first run). Falls back to an empty list if no + // pipelines_dir is configured (Wave-2 external-registry use case). + ap_pipelines_ch = ap_ready_ch.combine(ap_yaml_ch).map { _ready, yaml_path -> + def yaml_data = new org.yaml.snakeyaml.Yaml().load(new File(yaml_path.toString()).text) + def pipelines_dir = yaml_data.pipelines_dir + if (pipelines_dir == null || !new File(pipelines_dir.toString()).exists()) { + return [] + } + return file("${pipelines_dir}/*.joblib") + } + + APPEND_PREDICTIONS(ap_ready_ch, ap_yaml_ch, ap_pipelines_ch, workspace_dir) + + ap_done_ch = APPEND_PREDICTIONS.out.done + .ifEmpty('skip') + .first() + + // ----------------------------------------------------------------------- + // Step 10a: Per-experiment plots — after reduce_combined + enrichment + // ----------------------------------------------------------------------- + plot_yaml_ch = manifest_ch + .filter { it.containsKey('plot') } + .map { it.plot } + + plots_dir_ch = manifest_ch.map { "${it.output_dir}/plots" } + + enrichment_done_ch = aa_done_ch.mix(ap_done_ch).collect().map { 'ready' } + + reduce_zarrs_str_ch = REDUCE_COMBINED.out.zarr_paths + .map { zarrs -> zarrs.join('\n') } + + post_reduce_zarrs_ch = reduce_zarrs_str_ch + .combine(enrichment_done_ch) + .map { zarrs_str, _ready -> zarrs_str.split('\n').toList() } + .flatten() + + plot_inputs_ch = post_reduce_zarrs_ch.combine(plot_yaml_ch).combine(plots_dir_ch) + + PLOT( + plot_inputs_ch.map { zarr, yaml, dir -> zarr }, + plot_inputs_ch.map { zarr, yaml, dir -> yaml }, + plot_inputs_ch.map { zarr, yaml, dir -> dir }, + workspace_dir + ) + + // ----------------------------------------------------------------------- + // Step 10b: Combined plots (gather) — after reduce_combined + enrichment + // ----------------------------------------------------------------------- + plot_combined_yaml_ch = manifest_ch + .filter { it.containsKey('plot_combined') } + .map { it.plot_combined } + + plot_combined_input_ch = reduce_zarrs_str_ch + .combine(enrichment_done_ch) + .map { zarrs_str, _ready -> zarrs_str.split('\n').toList() } + + PLOT_COMBINED( + plot_combined_input_ch, + plot_combined_yaml_ch, + workspace_dir + ) +} diff --git a/applications/dynaclr/nextflow/workflows/eval_from_embeddings.nf b/applications/dynaclr/nextflow/workflows/eval_from_embeddings.nf new file mode 100644 index 000000000..863942e0d --- /dev/null +++ b/applications/dynaclr/nextflow/workflows/eval_from_embeddings.nf @@ -0,0 +1,42 @@ +// DynaCLR Eval-From-Embeddings Workflow +// +// Named sub-workflow invoked via `-entry eval_from_embeddings` from main.nf. +// Decouples inference from evaluation: instead of running PREDICT / SPLIT, it +// sources pre-computed per-experiment embedding zarrs from a glob and runs the +// same shared DOWNSTREAM DAG as the evaluation workflow. + +include { PREPARE_CONFIGS } from '../modules/evaluation/prepare_configs' +include { DOWNSTREAM } from './_downstream' + + +workflow EVAL_FROM_EMBEDDINGS { + take: + eval_config + embeddings_glob + workspace_dir + + main: + // ----------------------------------------------------------------------- + // Step 1: Generate per-step YAML configs → JSON manifest. + // Only the per-step configs are consumed here; the manifest's + // embeddings_dir is not used to source zarrs — the glob does that. + // ----------------------------------------------------------------------- + PREPARE_CONFIGS(eval_config, workspace_dir) + + manifest_ch = PREPARE_CONFIGS.out.manifest + .map { f -> new groovy.json.JsonSlurper().parse(f) } + + // ----------------------------------------------------------------------- + // Step 2: Source per-experiment embedding zarrs from the glob (directories) + // instead of running PREDICT / SPLIT. + // ----------------------------------------------------------------------- + per_exp_zarrs_ch = Channel.fromPath(embeddings_glob, type: 'dir') + .map { it.toString() } + .filter { it.endsWith('.zarr') } + + // ----------------------------------------------------------------------- + // Steps 4-10: Shared downstream DAG (reduce / smoothness / mmd / + // classifiers / plots) with all per-experiment-zarr write-order barriers. + // ----------------------------------------------------------------------- + DOWNSTREAM(per_exp_zarrs_ch, manifest_ch, workspace_dir) +} diff --git a/applications/dynaclr/nextflow/workflows/evaluation.nf b/applications/dynaclr/nextflow/workflows/evaluation.nf index f6fef3e81..e1d7d7e38 100644 --- a/applications/dynaclr/nextflow/workflows/evaluation.nf +++ b/applications/dynaclr/nextflow/workflows/evaluation.nf @@ -7,18 +7,7 @@ include { PREPARE_CONFIGS } from '../modules/evaluation/prepare_configs' include { PREDICT } from '../modules/evaluation/predict' include { SPLIT } from '../modules/evaluation/split' -include { REDUCE } from '../modules/evaluation/reduce' -include { REDUCE_COMBINED } from '../modules/evaluation/reduce_combined' -include { PLOT } from '../modules/evaluation/plot' -include { PLOT_COMBINED } from '../modules/evaluation/plot_combined' -include { SMOOTHNESS } from '../modules/evaluation/smoothness' -include { MMD } from '../modules/evaluation/mmd' -include { MMD_COMBINED } from '../modules/evaluation/mmd_combined' -include { MMD_PLOT_HEATMAP } from '../modules/evaluation/mmd_plot_heatmap' -include { SMOOTHNESS_GATHER } from '../modules/evaluation/smoothness_gather' -include { LINEAR_CLASSIFIERS } from '../modules/evaluation/linear_classifiers' -include { APPEND_ANNOTATIONS } from '../modules/evaluation/append_annotations' -include { APPEND_PREDICTIONS } from '../modules/evaluation/append_predictions' +include { DOWNSTREAM } from './_downstream' workflow EVALUATION { @@ -65,241 +54,9 @@ workflow EVALUATION { .map { it.trim() } .filter { it.endsWith('.zarr') } - split_done_ch = per_exp_zarrs_ch.collect().map { 'done' } - - // ----------------------------------------------------------------------- - // Step 4a: Per-experiment dim reduction (scatter) — after split - // ----------------------------------------------------------------------- - reduce_yaml_ch = manifest_ch - .filter { it.containsKey('reduce') } - .map { it.reduce } - - reduce_inputs_ch = per_exp_zarrs_ch.combine(reduce_yaml_ch) - - REDUCE( - reduce_inputs_ch.map { zarr, yaml -> zarr }, - reduce_inputs_ch.map { zarr, yaml -> yaml }, - workspace_dir - ) - - // ----------------------------------------------------------------------- - // Step 4b: Combined dim reduction (gather) — after all REDUCE finish - // ----------------------------------------------------------------------- - reduce_combined_yaml_ch = manifest_ch - .filter { it.containsKey('reduce_combined') } - .map { it.reduce_combined } - - REDUCE_COMBINED( - REDUCE.out.zarr_path.collect(), - reduce_combined_yaml_ch, - workspace_dir - ) - - // Barrier: per-experiment zarr writes (X_pca_combined / X_phate_combined) - // must finish before APPEND_ANNOTATIONS / APPEND_PREDICTIONS start writing - // to the same zarrs. ifEmpty('skip') keeps the chain alive when - // reduce_combined isn't in steps. - reduce_combined_done_ch = REDUCE_COMBINED.out.zarr_paths - .ifEmpty('skip') - .first() - - // ----------------------------------------------------------------------- - // Step 5: Smoothness (scatter, depends only on split) - // ----------------------------------------------------------------------- - smoothness_yaml_ch = manifest_ch - .filter { it.containsKey('smoothness') } - .map { it.smoothness } - - smoothness_inputs_ch = per_exp_zarrs_ch.combine(smoothness_yaml_ch) - - SMOOTHNESS( - smoothness_inputs_ch.map { zarr, yaml -> zarr }, - smoothness_inputs_ch.map { zarr, yaml -> yaml }, - workspace_dir - ) - - smoothness_dir_ch = manifest_ch.map { "${it.output_dir}/smoothness" } - smoothness_done_ch = SMOOTHNESS.out.zarr_path.collect().map { 'done' } - SMOOTHNESS_GATHER(smoothness_dir_ch, smoothness_done_ch) - - // ----------------------------------------------------------------------- - // Step 6: MMD per-experiment (scatter, depends only on split) - // ----------------------------------------------------------------------- - mmd_block_inputs_ch = manifest_ch - .filter { it.containsKey('mmd_blocks') && it.mmd_blocks.size() > 0 } - .flatMap { manifest -> - manifest.mmd_blocks.collect { block_name -> - [block_name, manifest["mmd_${block_name}"]] - } - } - - mmd_per_exp_ch = per_exp_zarrs_ch - .combine(mmd_block_inputs_ch) - .map { zarr, block_name, mmd_yaml -> tuple(zarr, block_name, mmd_yaml) } - - MMD(mmd_per_exp_ch, workspace_dir) - - mmd_heatmap_dirs_ch = manifest_ch - .filter { it.containsKey('mmd_blocks') && it.mmd_blocks.size() > 0 } - .flatMap { manifest -> - manifest.mmd_blocks.collect { block_name -> manifest["mmd_${block_name}_dir"] } - } - - MMD.out.zarr_path.collect() - .combine(mmd_heatmap_dirs_ch) - .map { items -> items[-1] } - | MMD_PLOT_HEATMAP - - // ----------------------------------------------------------------------- - // Step 6b: MMD combined (gather per block, depends only on split) - // ----------------------------------------------------------------------- - mmd_combined_inputs_ch = manifest_ch - .filter { it.containsKey('mmd_combined_blocks') && it.mmd_combined_blocks.size() > 0 } - .flatMap { manifest -> - manifest.mmd_combined_blocks.collect { block_name -> - [block_name, manifest["mmd_${block_name}_cross_exp"]] - } - } - - mmd_combined_zarrs_str_ch = per_exp_zarrs_ch.collect().map { zarrs -> zarrs.join('\n') } - - mmd_combined_ch = mmd_combined_zarrs_str_ch - .combine(mmd_combined_inputs_ch) - .map { zarrs_str, block_name, mmd_yaml -> tuple(zarrs_str, block_name, mmd_yaml) } - - MMD_COMBINED(mmd_combined_ch, workspace_dir) - - // ----------------------------------------------------------------------- - // Step 7: Append annotations — must run AFTER reduce_combined (both - // mutate per-experiment zarrs), and after split (zarrs must exist). - // ----------------------------------------------------------------------- - // Concurrency invariant: per-experiment zarrs have one writer at a time. - // Pre-write order: SPLIT -> REDUCE -> REDUCE_COMBINED -> APPEND_ANNOTATIONS - // -> LINEAR_CLASSIFIERS (reads only) -> APPEND_PREDICTIONS -> PLOT (reads). - aa_yaml_ch = manifest_ch - .filter { it.containsKey('append_annotations') } - .map { it.append_annotations } - - // Extract annotation CSV paths from the YAML so Nextflow content-hashes - // them as task inputs. Without this the resume cache only sees the YAML - // path string and misses content changes in the referenced CSVs. - // Dedupe by absolute path because the recipe references the same CSV - // file from multiple experiments (one annotation file feeds many channels); - // staging would otherwise fail with basename collisions. - aa_csv_ch = aa_yaml_ch.map { yaml_path -> - def yaml_data = new org.yaml.snakeyaml.Yaml().load(new File(yaml_path.toString()).text) - def paths = (yaml_data.annotations ?: []).collect { it.path }.unique() - return paths.collect { file(it) } - } - - aa_ready_ch = split_done_ch.mix(reduce_combined_done_ch).collect().map { 'ready' } - - APPEND_ANNOTATIONS(aa_ready_ch, aa_yaml_ch, aa_csv_ch, workspace_dir) - - aa_done_ch = APPEND_ANNOTATIONS.out.done - .ifEmpty('skip') - .first() - - // ----------------------------------------------------------------------- - // Step 8: Linear classifiers — after append_annotations // ----------------------------------------------------------------------- - lc_yaml_ch = manifest_ch - .filter { it.containsKey('linear_classifiers') } - .map { it.linear_classifiers } - - // Same annotation-CSV staging as APPEND_ANNOTATIONS so LC cache keys - // also depend on the actual CSV contents. Dedupe (see comment above). - lc_csv_ch = lc_yaml_ch.map { yaml_path -> - def yaml_data = new org.yaml.snakeyaml.Yaml().load(new File(yaml_path.toString()).text) - def paths = (yaml_data.annotations ?: []).collect { it.path }.unique() - return paths.collect { file(it) } - } - - LINEAR_CLASSIFIERS(aa_done_ch, lc_yaml_ch, lc_csv_ch, workspace_dir) - + // Steps 4-10: Shared downstream DAG (reduce / smoothness / mmd / + // classifiers / plots) with all per-experiment-zarr write-order barriers. // ----------------------------------------------------------------------- - // Step 9: Append predictions — after linear classifiers AND split - // ----------------------------------------------------------------------- - // append_predictions reads per-experiment zarrs (produced by SPLIT) and - // writes predicted_* columns to obs. It must wait on BOTH: - // - LINEAR_CLASSIFIERS (when present in steps): pipelines must exist - // - SPLIT: the zarrs to predict on must exist - // For Wave-2 evaluations that fetch pipelines from an external registry - // (no LINEAR_CLASSIFIERS in steps), lc_done is 'skip' immediately and - // only the split dependency keeps APPEND_PREDICTIONS gated. - ap_yaml_ch = manifest_ch - .filter { it.containsKey('append_predictions') } - .map { it.append_predictions } - - lc_done_ch = LINEAR_CLASSIFIERS.out.done - .ifEmpty('skip') - .first() - - // Combine the two upstream signals into one barrier value. - ap_ready_ch = lc_done_ch.mix(split_done_ch).collect().map { 'ready' } - - // Stage the LC pipeline files so Nextflow content-hashes them. The pipelines - // are produced by LINEAR_CLASSIFIERS just above, so the channel must wait - // for lc_done before reading the pipelines_dir glob (otherwise the dir - // doesn't exist yet on first run). Falls back to an empty list if no - // pipelines_dir is configured (Wave-2 external-registry use case). - ap_pipelines_ch = ap_ready_ch.combine(ap_yaml_ch).map { _ready, yaml_path -> - def yaml_data = new org.yaml.snakeyaml.Yaml().load(new File(yaml_path.toString()).text) - def pipelines_dir = yaml_data.pipelines_dir - if (pipelines_dir == null || !new File(pipelines_dir.toString()).exists()) { - return [] - } - return file("${pipelines_dir}/*.joblib") - } - - APPEND_PREDICTIONS(ap_ready_ch, ap_yaml_ch, ap_pipelines_ch, workspace_dir) - - ap_done_ch = APPEND_PREDICTIONS.out.done - .ifEmpty('skip') - .first() - - // ----------------------------------------------------------------------- - // Step 10a: Per-experiment plots — after reduce_combined + enrichment - // ----------------------------------------------------------------------- - plot_yaml_ch = manifest_ch - .filter { it.containsKey('plot') } - .map { it.plot } - - plots_dir_ch = manifest_ch.map { "${it.output_dir}/plots" } - - enrichment_done_ch = aa_done_ch.mix(ap_done_ch).collect().map { 'ready' } - - reduce_zarrs_str_ch = REDUCE_COMBINED.out.zarr_paths - .map { zarrs -> zarrs.join('\n') } - - post_reduce_zarrs_ch = reduce_zarrs_str_ch - .combine(enrichment_done_ch) - .map { zarrs_str, _ready -> zarrs_str.split('\n').toList() } - .flatten() - - plot_inputs_ch = post_reduce_zarrs_ch.combine(plot_yaml_ch).combine(plots_dir_ch) - - PLOT( - plot_inputs_ch.map { zarr, yaml, dir -> zarr }, - plot_inputs_ch.map { zarr, yaml, dir -> yaml }, - plot_inputs_ch.map { zarr, yaml, dir -> dir }, - workspace_dir - ) - - // ----------------------------------------------------------------------- - // Step 10b: Combined plots (gather) — after reduce_combined + enrichment - // ----------------------------------------------------------------------- - plot_combined_yaml_ch = manifest_ch - .filter { it.containsKey('plot_combined') } - .map { it.plot_combined } - - plot_combined_input_ch = reduce_zarrs_str_ch - .combine(enrichment_done_ch) - .map { zarrs_str, _ready -> zarrs_str.split('\n').toList() } - - PLOT_COMBINED( - plot_combined_input_ch, - plot_combined_yaml_ch, - workspace_dir - ) + DOWNSTREAM(per_exp_zarrs_ch, manifest_ch, workspace_dir) } diff --git a/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py b/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py index 651247b55..0c14ec372 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py +++ b/applications/dynaclr/src/dynaclr/evaluation/split_embeddings.py @@ -2,8 +2,7 @@ Reads the combined embeddings.zarr produced by the predict step, groups rows by obs[group_by] (``experiment`` by default), and writes one AnnData zarr per -group under output_dir/{group}.zarr. The combined zarr is removed after -splitting. +group under output_dir/{group}.zarr. Usage ----- @@ -16,6 +15,11 @@ Split by a different column (e.g. one zarr per marker): dynaclr split-embeddings --input /path/to/embeddings.zarr --output-dir /path/to/embeddings/ --group-by marker + +Route into the dataset-centric tree (parquet spine → same layout as predict-triplet): + +dynaclr split-embeddings --input /path/to/embeddings.zarr --route-by-dataset \\ + --model-family M --run R --ckpt-name C [--keep-combined] """ from __future__ import annotations @@ -24,32 +28,59 @@ import click +from dynaclr.evaluation.paths import DATASETS_ROOT, embedding_store + def split_embeddings( input_path: Path, - output_dir: Path, + output_dir: Path | None = None, group_by: str = "experiment", prefix_by: str | None = None, + *, + route_by_dataset: bool = False, + model_family: str | None = None, + run: str | None = None, + ckpt_name: str | None = None, + datasets_root: str | Path = DATASETS_ROOT, + keep_combined: bool = False, ) -> list[Path]: """Split combined embeddings zarr into one zarr per group. + Two output layouts: + + - **Flat** (default): one zarr per ``group_by`` value under ``output_dir``, + named ``{group}.zarr`` or ``{prefix}_{group}.zarr`` when ``prefix_by`` is + set. + - **Dataset-centric** (``route_by_dataset=True``): group by + ``experiment`` x ``marker`` and route each into the canonical tree + ``{dataset}/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr`` + via :func:`dynaclr.evaluation.paths.embedding_store`, so the parquet spine + lands in the same layout as ``predict-triplet``. + Parameters ---------- input_path : Path Path to the combined embeddings zarr (AnnData format). - Must have the ``group_by`` (and ``prefix_by``, if set) column in obs. - output_dir : Path - Directory to write per-group zarrs. - Each group value is written to ``output_dir/{group}.zarr``, or - ``output_dir/{prefix}_{group}.zarr`` when ``prefix_by`` is set. + output_dir : Path or None + Directory for flat output. Required unless ``route_by_dataset`` is set. group_by : str, optional - obs column to group rows by. By default ``"experiment"``. + obs column to group rows by (flat mode). By default ``"experiment"``. prefix_by : str or None, optional - obs column whose value prefixes each output filename as - ``{prefix}_{group}.zarr`` (e.g. ``prefix_by="experiment"`` with - ``group_by="marker"`` yields ``{dataset}_{marker}.zarr``). The prefix - column must be constant within each group. By default ``None`` (no - prefix). + obs column whose value prefixes each flat filename as + ``{prefix}_{group}.zarr``. Must be constant within each group. + route_by_dataset : bool, optional + If ``True``, ignore ``output_dir``/``group_by``/``prefix_by`` and write + the dataset-centric tree keyed by obs ``experiment`` and ``marker``. + model_family, run, ckpt_name : str or None + Provenance identity for the dataset-centric tree. Required when + ``route_by_dataset`` is set. + datasets_root : str or Path, optional + Base under which datasets live (dataset-centric mode). Defaults to + :data:`dynaclr.evaluation.paths.DATASETS_ROOT`. + keep_combined : bool, optional + If ``False`` (default), remove the combined ``input_path`` after + splitting. Set ``True`` to keep it so downstream eval can re-run + without re-predicting. Returns ------- @@ -68,7 +99,34 @@ def split_embeddings( adata = ad.read_zarr(input_path) click.echo(f" {adata.n_obs} cells, {adata.n_vars} features") - for col in filter(None, [group_by, prefix_by]): + if route_by_dataset: + written = _write_dataset_centric( + adata, + model_family=model_family, + run=run, + ckpt_name=ckpt_name, + datasets_root=datasets_root, + ) + else: + if output_dir is None: + raise ValueError("--output-dir is required unless --route-by-dataset is set.") + written = _write_flat(adata, output_dir, group_by=group_by, prefix_by=prefix_by) + + if keep_combined: + click.echo(f"\nKeeping combined zarr: {input_path}") + else: + click.echo(f"\nRemoving combined zarr: {input_path}") + import shutil + + shutil.rmtree(input_path) + + click.echo(f"\nWrote {len(written)} zarrs") + return written + + +def _require_obs_columns(adata, columns: list[str]) -> None: + """Raise if any required obs column is missing.""" + for col in columns: if col not in adata.obs.columns: raise ValueError( f"embeddings zarr obs is missing '{col}' column. " @@ -76,15 +134,18 @@ def split_embeddings( "Re-run the predict step with the updated pipeline to include metadata." ) + +def _write_flat(adata, output_dir: Path, *, group_by: str, prefix_by: str | None) -> list[Path]: + """Write one zarr per ``group_by`` value under ``output_dir`` (flat layout).""" + _require_obs_columns(adata, [c for c in (group_by, prefix_by) if c]) + groups = adata.obs[group_by].unique().tolist() click.echo(f" {len(groups)} {group_by} groups: {groups}") output_dir.mkdir(parents=True, exist_ok=True) written: list[Path] = [] - for group in groups: - mask = adata.obs[group_by] == group - adata_group = adata[mask].copy() + adata_group = adata[adata.obs[group_by] == group].copy() if prefix_by is not None: prefixes = adata_group.obs[prefix_by].unique().tolist() if len(prefixes) != 1: @@ -99,13 +160,32 @@ def split_embeddings( click.echo(f" Writing {name}: {adata_group.n_obs} cells → {out_path}") adata_group.write_zarr(out_path) written.append(out_path) + return written - click.echo(f"\nRemoving combined zarr: {input_path}") - import shutil - shutil.rmtree(input_path) +def _write_dataset_centric( + adata, + *, + model_family: str | None, + run: str | None, + ckpt_name: str | None, + datasets_root: str | Path, +) -> list[Path]: + """Route each (experiment, marker) group into the dataset-centric tree.""" + if not (model_family and run and ckpt_name): + raise ValueError("--route-by-dataset requires --model-family, --run, and --ckpt-name.") + _require_obs_columns(adata, ["experiment", "marker"]) - click.echo(f"\nWrote {len(written)} per-{group_by} zarrs to {output_dir}") + written: list[Path] = [] + pairs = adata.obs[["experiment", "marker"]].drop_duplicates().itertuples(index=False) + for dataset, marker in pairs: + mask = (adata.obs["experiment"] == dataset) & (adata.obs["marker"] == marker) + adata_group = adata[mask].copy() + out_path = embedding_store(str(dataset), model_family, run, ckpt_name, str(marker), datasets_root=datasets_root) + out_path.parent.mkdir(parents=True, exist_ok=True) + click.echo(f" Writing {dataset}/{marker}: {adata_group.n_obs} cells → {out_path}") + adata_group.write_zarr(out_path) + written.append(out_path) return written @@ -120,14 +200,14 @@ def split_embeddings( @click.option( "--output-dir", type=click.Path(path_type=Path), - required=True, - help="Directory to write per-group zarrs", + default=None, + help="Directory to write per-group zarrs (flat layout; required unless --route-by-dataset).", ) @click.option( "--group-by", default="experiment", show_default=True, - help="obs column to group rows by (e.g. 'marker' for one zarr per marker)", + help="obs column to group rows by (flat layout; e.g. 'marker' for one zarr per marker)", ) @click.option( "--prefix-by", @@ -135,9 +215,55 @@ def split_embeddings( help="obs column to prefix filenames as {prefix}_{group}.zarr " "(e.g. 'experiment' with --group-by marker gives {dataset}_{marker}.zarr)", ) -def main(input_path: Path, output_dir: Path, group_by: str, prefix_by: str | None) -> None: +@click.option( + "--route-by-dataset", + is_flag=True, + default=False, + help="Write the dataset-centric tree " + "/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr " + "keyed by obs experiment x marker (ignores --output-dir/--group-by/--prefix-by).", +) +@click.option("--model-family", default=None, help="Model family (required with --route-by-dataset).") +@click.option("--run", default=None, help="Training run/version (required with --route-by-dataset).") +@click.option("--ckpt-name", default=None, help="Checkpoint label (required with --route-by-dataset).") +@click.option( + "--datasets-root", + type=click.Path(path_type=Path), + default=DATASETS_ROOT, + show_default=True, + help="Base under which datasets live (with --route-by-dataset).", +) +@click.option( + "--keep-combined", + is_flag=True, + default=False, + help="Keep the combined input zarr instead of deleting it after splitting.", +) +def main( + input_path: Path, + output_dir: Path | None, + group_by: str, + prefix_by: str | None, + route_by_dataset: bool, + model_family: str | None, + run: str | None, + ckpt_name: str | None, + datasets_root: Path, + keep_combined: bool, +) -> None: """Split a combined embeddings zarr into one zarr per group.""" - split_embeddings(input_path, output_dir, group_by=group_by, prefix_by=prefix_by) + split_embeddings( + input_path, + output_dir, + group_by=group_by, + prefix_by=prefix_by, + route_by_dataset=route_by_dataset, + model_family=model_family, + run=run, + ckpt_name=ckpt_name, + datasets_root=datasets_root, + keep_combined=keep_combined, + ) if __name__ == "__main__": diff --git a/applications/dynaclr/tests/test_split_embeddings.py b/applications/dynaclr/tests/test_split_embeddings.py new file mode 100644 index 000000000..6d0c48c96 --- /dev/null +++ b/applications/dynaclr/tests/test_split_embeddings.py @@ -0,0 +1,102 @@ +"""Integration tests for split-embeddings layouts. + +Builds a small combined AnnData, writes it to zarr, and runs the real +:func:`split_embeddings` in both the flat and dataset-centric layouts. No GPU or +model required. +""" + +import anndata as ad +import numpy as np +import pandas as pd +import pytest + +from dynaclr.evaluation.paths import embedding_store +from dynaclr.evaluation.split_embeddings import split_embeddings + +MF = "DynaCLR-2D-MIP-BagOfChannels" +RUN = "2d-mip-fix-shuffler" +CKPT = "epoch105-step84800" + + +def _combined_zarr(tmp_path): + """Two datasets; ds_a has SEC61B+TOMM20, ds_b has SEC61B (6 cells total).""" + rows = [ + ("ds_a", "SEC61B"), + ("ds_a", "SEC61B"), + ("ds_a", "TOMM20"), + ("ds_b", "SEC61B"), + ("ds_b", "SEC61B"), + ("ds_b", "SEC61B"), + ] + obs = pd.DataFrame( + {"experiment": [r[0] for r in rows], "marker": [r[1] for r in rows]}, + index=[str(i) for i in range(len(rows))], + ) + adata = ad.AnnData(X=np.random.default_rng(0).random((len(rows), 4), dtype=np.float32), obs=obs) + path = tmp_path / "embeddings.zarr" + adata.write_zarr(path) + return path + + +def test_route_by_dataset_writes_canonical_tree(tmp_path): + combined = _combined_zarr(tmp_path) + written = split_embeddings( + combined, + route_by_dataset=True, + model_family=MF, + run=RUN, + ckpt_name=CKPT, + datasets_root=tmp_path, + keep_combined=True, + ) + # one zarr per (experiment, marker) pair + assert len(written) == 3 + assert embedding_store("ds_a", MF, RUN, CKPT, "SEC61B", datasets_root=tmp_path) in written + assert embedding_store("ds_a", MF, RUN, CKPT, "TOMM20", datasets_root=tmp_path) in written + assert embedding_store("ds_b", MF, RUN, CKPT, "SEC61B", datasets_root=tmp_path) in written + # markers of one dataset co-locate + a_sec = embedding_store("ds_a", MF, RUN, CKPT, "SEC61B", datasets_root=tmp_path) + a_tom = embedding_store("ds_a", MF, RUN, CKPT, "TOMM20", datasets_root=tmp_path) + assert a_sec.parent == a_tom.parent + # cells partitioned correctly + assert ad.read_zarr(a_sec).n_obs == 2 + assert ad.read_zarr(a_tom).n_obs == 1 + + +def test_keep_combined_preserves_input(tmp_path): + combined = _combined_zarr(tmp_path) + split_embeddings( + combined, + route_by_dataset=True, + model_family=MF, + run=RUN, + ckpt_name=CKPT, + datasets_root=tmp_path, + keep_combined=True, + ) + assert combined.exists() + + +def test_default_deletes_combined(tmp_path): + combined = _combined_zarr(tmp_path) + split_embeddings(combined, output_dir=tmp_path / "out", group_by="experiment") + assert not combined.exists() + + +def test_flat_layout_still_works(tmp_path): + combined = _combined_zarr(tmp_path) + out = tmp_path / "out" + written = split_embeddings(combined, output_dir=out, group_by="experiment", keep_combined=True) + assert {p.name for p in written} == {"ds_a.zarr", "ds_b.zarr"} + + +def test_route_by_dataset_requires_provenance(tmp_path): + combined = _combined_zarr(tmp_path) + with pytest.raises(ValueError, match="requires --model-family"): + split_embeddings(combined, route_by_dataset=True, datasets_root=tmp_path, keep_combined=True) + + +def test_flat_requires_output_dir(tmp_path): + combined = _combined_zarr(tmp_path) + with pytest.raises(ValueError, match="output-dir is required"): + split_embeddings(combined, keep_combined=True) From 85374c6732fe141051f73bdf2acd188cb3898068 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:08:39 -0700 Subject: [PATCH 44/89] feat(dynaclr): add train-predict-eval model matrix launcher --- .../dynaclr/configs/matrix/example.yml | 40 +++ applications/dynaclr/src/dynaclr/cli.py | 24 ++ .../evaluation/orchestration/__init__.py | 1 + .../evaluation/orchestration/eval_launch.py | 177 ++++++++++ .../evaluation/orchestration/matrix.py | 328 ++++++++++++++++++ .../evaluation/orchestration/predict_batch.py | 323 +++++++++++++++++ .../dynaclr/tests/test_submit_eval.py | 45 +++ .../dynaclr/tests/test_submit_matrix.py | 208 +++++++++++ .../dynaclr/tests/test_submit_predict.py | 59 ++++ .../tests/test_submit_predict_preflight.py | 81 +++++ applications/dynaclr/tools/README.md | 91 +++++ applications/dynaclr/tools/eval.sbatch | 39 +++ applications/dynaclr/tools/predict.sbatch | 57 +++ 13 files changed, 1473 insertions(+) create mode 100644 applications/dynaclr/configs/matrix/example.yml create mode 100644 applications/dynaclr/src/dynaclr/evaluation/orchestration/__init__.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/orchestration/eval_launch.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/orchestration/matrix.py create mode 100644 applications/dynaclr/src/dynaclr/evaluation/orchestration/predict_batch.py create mode 100644 applications/dynaclr/tests/test_submit_eval.py create mode 100644 applications/dynaclr/tests/test_submit_matrix.py create mode 100644 applications/dynaclr/tests/test_submit_predict.py create mode 100644 applications/dynaclr/tests/test_submit_predict_preflight.py create mode 100644 applications/dynaclr/tools/README.md create mode 100644 applications/dynaclr/tools/eval.sbatch create mode 100644 applications/dynaclr/tools/predict.sbatch diff --git a/applications/dynaclr/configs/matrix/example.yml b/applications/dynaclr/configs/matrix/example.yml new file mode 100644 index 000000000..82d0639bb --- /dev/null +++ b/applications/dynaclr/configs/matrix/example.yml @@ -0,0 +1,40 @@ +# Example model matrix — run many models in parallel through train → predict → eval. +# +# `dynaclr run-matrix` reads this, resolves each model, and submits three SLURM jobs +# per model chained by --dependency=afterok (train → predict → eval). Models run +# in parallel; stages chain within a model. +# +# uv run dynaclr run-matrix -c --dry-run +# +# `defaults:` holds fields shared across the sweep. Each `models:` entry gives +# just its training .sh; family / run / train_configs are parsed from the .sh's +# `export PROJECT= / RUN_NAME= / CONFIGS=` lines (override by setting them here). + +defaults: + ckpt_name: last # → checkpoints/last.ckpt (pin an epoch with e.g. epoch105-step84800) + collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml + eval_config: applications/dynaclr/configs/evaluation/DynaCLR-2D-MIP-BagOfChannels/infectomics-annotated.yaml + datasets_root: /hpc/projects/intracellular_dashboard/organelle_dynamics + predict_flags: + z_range: [15, 45] + z_reduction: mip + reference_pixel_size: 0.1494 + +models: + # One line per model — the training .sh is the identity + config source. + # For the train→predict chain, ckpt_name: last resolves {run}/checkpoints/last.ckpt. + - train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/DynaCLR-2D-MIP-BagOfChannels-single-marker.sh + - train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/DynaCLR-2D-MIP-BagOfChannels.sh + + # markers: run exactly these (extras in the collection ignored); omit = all channels. + # ckpt_names: sweep multiple checkpoints — one train, then a predict→eval chain per ckpt. + # - train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/DynaCLR-2D-MIP-BagOfChannels-single-marker.sh + # markers: [SEC61B, Phase3D] + # ckpt_names: [last, epoch105-step84800] + + # Predict/eval an ALREADY-trained model: Lightning nests checkpoints under a + # wandb-run-id subdir, which isn't derivable from the identity — give the + # explicit `checkpoint:` path (overrides the derived {run}/checkpoints/ default). + # - train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/DynaCLR-2D-MIP-BagOfChannels-single-marker.sh + # ckpt_name: epoch105-step84800 + # checkpoint: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/DynaCLR-2D-MIP-BagOfChannels/jbrwhzr3/checkpoints/epoch=105-step=84800.ckpt diff --git a/applications/dynaclr/src/dynaclr/cli.py b/applications/dynaclr/src/dynaclr/cli.py index c883e60ff..938465e03 100644 --- a/applications/dynaclr/src/dynaclr/cli.py +++ b/applications/dynaclr/src/dynaclr/cli.py @@ -272,6 +272,30 @@ def dynaclr(): ) +dynaclr.add_command( + LazyCommand( + name="run-matrix", + import_path="dynaclr.evaluation.orchestration.matrix.main", + short_help="Run many models through train→predict→eval in parallel (SLURM afterok chain)", + ) +) + +dynaclr.add_command( + LazyCommand( + name="predict-batch", + import_path="dynaclr.evaluation.orchestration.predict_batch.main", + short_help="Batch predict embeddings for one model over a collection (+ AI-ready preflight)", + ) +) + +dynaclr.add_command( + LazyCommand( + name="eval", + import_path="dynaclr.evaluation.orchestration.eval_launch.main", + short_help="Launch eval_from_embeddings over a model/run/ckpt's embeddings", + ) +) + dynaclr.add_command( LazyCommand( name="plot-embeddings", diff --git a/applications/dynaclr/src/dynaclr/evaluation/orchestration/__init__.py b/applications/dynaclr/src/dynaclr/evaluation/orchestration/__init__.py new file mode 100644 index 000000000..81cb682c5 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/orchestration/__init__.py @@ -0,0 +1 @@ +"""Orchestration launchers for the DynaCLR model matrix (train → predict → eval).""" diff --git a/applications/dynaclr/src/dynaclr/evaluation/orchestration/eval_launch.py b/applications/dynaclr/src/dynaclr/evaluation/orchestration/eval_launch.py new file mode 100644 index 000000000..9f4cd8f09 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/orchestration/eval_launch.py @@ -0,0 +1,177 @@ +r"""Run evaluation over pre-computed embeddings for a model/run/checkpoint. + +The DynaCLR analog of dynacell's ``submit_evaluation_batch.py``: turn a +``(model_family, run, ckpt_name)`` identity into the ``--embeddings_glob`` that +selects a cohort of already-written embedding zarrs, and launch the Nextflow +``eval_from_embeddings`` entry over them. No GPU / no re-prediction — evaluation +reads the frozen embeddings written by ``predict-triplet`` (or the parquet spine) +under the canonical tree +``/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr``. + +Dataset selection is the glob: with no ``--datasets`` every dataset present in +the tree for the given identity is included (the progressive default); pass +``--datasets`` to pin a reproducible subset (brace-expanded into the glob). + +Output: + * ``--print-cmd``: print the ``nextflow run`` command (one token per line). + * default: execute it via ``subprocess.run``. + +Usage:: + + dynaclr eval \ + --eval-config /path/to/eval.yaml \ + --model-family DynaCLR-2D-MIP-BagOfChannels \ + --run 2d-mip-fix-shuffler \ + --ckpt-name epoch105-step84800 \ + [--marker SEC61B] [--datasets ds_a ds_b] \ + [--print-cmd] +""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import click + +from dynaclr.evaluation.paths import DATASETS_ROOT, PHENOTYPING_DIR, PREDICTIONS_DIR + +# Repo-relative path to the Nextflow router. +_MAIN_NF = Path("applications/dynaclr/nextflow/main.nf") + + +def build_embeddings_glob( + model_family: str, + run: str, + ckpt_name: str, + marker: str | None = None, + datasets: list[str] | None = None, + datasets_root: str | Path = DATASETS_ROOT, +) -> str: + """Build the ``--embeddings_glob`` selecting a cohort of embedding zarrs. + + Parameters + ---------- + model_family, run, ckpt_name : str + Provenance identity pinning which embeddings to evaluate. + marker : str or None, optional + Restrict to one marker (``{marker}.zarr``); ``None`` matches all + markers (``*.zarr``). + datasets : list[str] or None, optional + Explicit dataset subset, brace-expanded into the dataset slot. ``None`` + globs every dataset (``*``) present for this identity. + datasets_root : str or Path, optional + Base under which datasets live. Defaults to + :data:`dynaclr.evaluation.paths.DATASETS_ROOT`. + + Returns + ------- + str + A shell glob string (brace expansion resolved by the shell / Nextflow). + """ + if datasets: + ds_slot = "{" + ",".join(datasets) + "}" if len(datasets) > 1 else datasets[0] + else: + ds_slot = "*" + marker_slot = f"{marker}.zarr" if marker else "*.zarr" + return str( + Path(datasets_root) / ds_slot / PHENOTYPING_DIR / PREDICTIONS_DIR / model_family / run / ckpt_name / marker_slot + ) + + +def build_nextflow_cmd( + eval_config: Path, + embeddings_glob: str, + workspace_dir: str, + resume: bool, +) -> list[str]: + """Build the ``nextflow run ... -entry eval_from_embeddings`` command.""" + cmd = [ + "nextflow", + "run", + str(_MAIN_NF), + "-entry", + "eval_from_embeddings", + "--eval_config", + str(eval_config), + "--embeddings_glob", + embeddings_glob, + "--workspace_dir", + workspace_dir, + ] + if resume: + cmd.append("-resume") + return cmd + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "--eval-config", + type=click.Path(path_type=Path), + required=True, + help="Evaluation config YAML (what steps to run).", +) +@click.option("--model-family", required=True, help="Model family (embedding-tree key).") +@click.option("--run", required=True, help="Training run/version (embedding-tree key).") +@click.option("--ckpt-name", required=True, help="Checkpoint label (embedding-tree key).") +@click.option("--marker", default=None, help="Restrict to one marker (default: all markers).") +@click.option( + "--datasets", + multiple=True, + default=None, + help="Explicit dataset subset (default: all datasets present for this model/run/ckpt).", +) +@click.option( + "--datasets-root", + type=click.Path(path_type=Path), + default=DATASETS_ROOT, + show_default=True, + help="Base under which datasets live.", +) +@click.option( + "--workspace-dir", + default="/hpc/mydata/eduardo.hirata/repos/viscy", + help="uv workspace dir passed to the Nextflow run.", +) +@click.option("--no-resume", is_flag=True, help="Do not pass -resume to Nextflow.") +@click.option("--print-cmd", is_flag=True, help="Print the command instead of executing it.") +def main( + eval_config: Path, + model_family: str, + run: str, + ckpt_name: str, + marker: str | None, + datasets: tuple[str, ...], + datasets_root: Path, + workspace_dir: str, + no_resume: bool, + print_cmd: bool, +) -> None: + """Launch eval_from_embeddings over a model/run/ckpt's embeddings.""" + embeddings_glob = build_embeddings_glob( + model_family, + run, + ckpt_name, + marker=marker, + datasets=list(datasets) if datasets else None, + datasets_root=datasets_root, + ) + cmd = build_nextflow_cmd( + eval_config, + embeddings_glob, + workspace_dir, + resume=not no_resume, + ) + + if print_cmd: + print("\n".join(cmd)) + return + + print(f"embeddings_glob: {embeddings_glob}", file=sys.stderr) + print(f"launching: {' '.join(cmd)}", file=sys.stderr) + subprocess.run(cmd, check=True) + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/src/dynaclr/evaluation/orchestration/matrix.py b/applications/dynaclr/src/dynaclr/evaluation/orchestration/matrix.py new file mode 100644 index 000000000..41fe19aa7 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/orchestration/matrix.py @@ -0,0 +1,328 @@ +r"""Run many DynaCLR models in parallel through train → predict → eval. + +Reads a matrix YAML and, per model, submits three SLURM jobs chained by +``--dependency=afterok`` (train → predict → eval). Models run fully in parallel; +within a model the stages chain. The eval side reuses ``dynaclr eval`` / +the Nextflow ``eval_from_embeddings`` entry; predict reuses ``dynaclr predict-batch``. + +Matrix YAML — ``defaults:`` block + per-model ``train_sbatch`` (the ``.sh``): + + defaults: + ckpt_name: last + collection: applications/dynaclr/configs/collections/<...>.yml + eval_config: applications/dynaclr/configs/evaluation/<...>.yaml + datasets_root: /hpc/projects/intracellular_dashboard/organelle_dynamics + predict_flags: {z_range: [15, 45], z_reduction: mip, reference_pixel_size: 0.1494} + models: + - train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/<...>.sh + - train_sbatch: applications/dynaclr/configs/training/DynaCLR-3D/<...>.sh + +``family`` / ``run`` / ``train_configs`` are parsed from each ``train_sbatch``'s +``export PROJECT=`` / ``RUN_NAME=`` / ``CONFIGS=`` lines (override by giving the +fields explicitly on the model entry). Shared fields come from ``defaults``. + +Output: + * ``--print-cmd``: print the chained sbatch commands (no submission). + * ``--dry-run``: alias for ``--print-cmd``. + * default: submit; capture each job id and wire ``afterok`` dependencies. + +Usage:: + + dynaclr run-matrix \ + -c applications/dynaclr/configs/matrix/.yml \ + [--stages train,predict,eval] [--dry-run] +""" + +from __future__ import annotations + +import re +import subprocess +import sys +from pathlib import Path + +import click +import yaml + +from dynaclr.evaluation.orchestration.predict_batch import check_ai_ready + +# Wrapper sbatch scripts the launcher submits for the predict / eval links. +_PREDICT_SBATCH = Path("applications/dynaclr/tools/predict.sbatch") +_EVAL_SBATCH = Path("applications/dynaclr/tools/eval.sbatch") + +_ALL_STAGES = ("train", "predict", "eval") + + +def matrix_preflight(models: list[dict]) -> None: + """Report AI-readiness for every model's collection before submitting anything. + + Runs once upfront (only relevant when the predict stage is included) so all + non-AI-ready datasets are surfaced before any job queues. Raises if any + dataset is missing ``focus_slice`` (needs manual QC) or ``normalization``. + """ + seen: set[str] = set() + missing_focus: list[str] = [] + missing_norm: list[str] = [] + for model in models: + collection = model.get("collection") + if not collection or str(collection) in seen: + continue + seen.add(str(collection)) + for r in check_ai_ready(Path(collection)): + if r["missing_focus_slice"]: + missing_focus.append(f"{r['name']} ({r['data_path']})") + if r["missing_normalization"]: + missing_norm.append(f"{r['name']} ({r['data_path']})") + + if missing_norm: + print("[preflight] datasets missing normalization (run `viscy preprocess` — safe/auto):", file=sys.stderr) + for m in missing_norm: + print(f" - {m}", file=sys.stderr) + if missing_focus: + lines = "\n".join(f" - {m}" for m in missing_focus) + raise ValueError( + "[preflight] datasets missing focus_slice — run QC manually before the matrix " + "(z-focus needs per-dataset physics params):\n" + f"{lines}\n" + "Run `qc run -c /qc_config.yml` for each, then retry." + ) + if missing_norm: + raise ValueError( + "[preflight] datasets missing normalization (see above). Run " + "`viscy preprocess --data_path --channel_names=-1` for each, then retry." + ) + print("[preflight] all datasets AI-ready.", file=sys.stderr) + + +# `export NAME="value"` or `export NAME=value` (value may be quoted). +_EXPORT_RE = re.compile(r'^\s*export\s+(\w+)=(?:"([^"]*)"|\'([^\']*)\'|(\S+))', re.MULTILINE) + + +def parse_train_sbatch(sbatch_path: Path) -> dict: + """Parse PROJECT / RUN_NAME / CONFIGS out of a training ``.sh``. + + Parameters + ---------- + sbatch_path : Path + A DynaCLR training script that exports ``PROJECT``, ``RUN_NAME``, and + ``CONFIGS`` (space-separated config paths). + + Returns + ------- + dict + ``{"family": PROJECT, "run": RUN_NAME, "train_configs": [..]}``. + + Raises + ------ + ValueError + If any of the three exports is missing (fail loud — a malformed script + must not silently mis-resolve). + """ + text = Path(sbatch_path).read_text() + exports: dict[str, str] = {} + for m in _EXPORT_RE.finditer(text): + name = m.group(1) + value = next(g for g in m.groups()[1:] if g is not None) + exports[name] = value + missing = [k for k in ("PROJECT", "RUN_NAME", "CONFIGS") if k not in exports] + if missing: + raise ValueError(f"{sbatch_path} is missing required export(s): {', '.join(missing)}") + return { + "family": exports["PROJECT"], + "run": exports["RUN_NAME"], + "train_configs": exports["CONFIGS"].split(), + } + + +def resolve_model(model: dict, defaults: dict) -> dict: + """Merge defaults into a model entry and resolve identity from its ``.sh``. + + ``{**defaults, **model}`` (model wins); ``predict_flags`` is shallow-merged. + ``family`` / ``run`` / ``train_configs`` are parsed from ``train_sbatch`` + unless the entry provides them explicitly (override). + """ + merged = {**defaults, **model} + merged["predict_flags"] = {**defaults.get("predict_flags", {}), **model.get("predict_flags", {})} + + if "train_sbatch" not in merged: + raise ValueError(f"model entry has no 'train_sbatch' (and no explicit identity): {model}") + parsed = parse_train_sbatch(Path(merged["train_sbatch"])) + # Explicit fields on the entry override the parsed values. + for key in ("family", "run", "train_configs"): + merged.setdefault(key, parsed[key]) + + for required in ("family", "run"): + if required not in merged: + raise ValueError(f"model missing required field '{required}' after resolve: {merged.get('train_sbatch')}") + if "ckpt_name" not in merged and "ckpt_names" not in merged: + raise ValueError(f"model needs 'ckpt_name' or 'ckpt_names' after resolve: {merged.get('train_sbatch')}") + return merged + + +def load_matrix(path: Path) -> list[dict]: + """Load the matrix YAML and return the list of fully-resolved model dicts.""" + doc = yaml.safe_load(Path(path).read_text()) + defaults = doc.get("defaults", {}) + models = doc.get("models", []) + if not models: + raise ValueError(f"matrix {path} has no 'models'.") + return [resolve_model(m, defaults) for m in models] + + +def resolve_checkpoints(model: dict) -> list[tuple[str, str]]: + """Return the (ckpt_name, checkpoint_path) units for a model's sweep. + + Supports a checkpoint sweep via ``ckpt_names: [..]`` (labels) and/or + ``checkpoints: [..]`` (explicit paths), falling back to the scalar + ``ckpt_name`` / ``checkpoint``. When both lists are given they are zipped + (must be equal length); a list of labels with no explicit paths yields empty + paths (derived downstream). One train job feeds every unit. + """ + names = model.get("ckpt_names") or [model.get("ckpt_name", "last")] + paths = model.get("checkpoints") + if paths is None: + scalar = model.get("checkpoint") + paths = [scalar] * len(names) if scalar else [""] * len(names) + if len(paths) != len(names): + raise ValueError( + f"ckpt_names ({len(names)}) and checkpoints ({len(paths)}) length mismatch for {model.get('train_sbatch')}" + ) + return [(str(n), str(p or "")) for n, p in zip(names, paths)] + + +def build_train_cmd(model: dict) -> list[str]: + """The train sbatch command (the model's own .sh; env baked in).""" + return ["sbatch", str(model["train_sbatch"])] + + +def build_predict_cmd(model: dict, ckpt_name: str, checkpoint: str) -> list[str]: + """The predict sbatch command for one checkpoint of a model. + + ``markers`` (optional list in the matrix row) is passed as a comma-joined 8th + positional; empty = all channels. + """ + markers = ",".join(model["markers"]) if model.get("markers") else "" + return [ + "sbatch", + str(_PREDICT_SBATCH), + model["collection"], + model["family"], + model["run"], + ckpt_name, + str(model["datasets_root"]), + checkpoint, # empty = derive from run dir + markers, # empty = all channels + ] + + +def build_eval_cmd(model: dict, ckpt_name: str) -> list[str]: + """The eval sbatch command for one checkpoint of a model.""" + return ["sbatch", str(_EVAL_SBATCH), model["eval_config"], model["family"], model["run"], ckpt_name] + + +def build_stage_cmds(model: dict, stages: tuple[str, ...]) -> list[tuple[str, list[str]]]: + """Flat (stage, command) list for the FIRST checkpoint — used by tests / simple linear view. + + The full sweep fan-out (train once → predict→eval per checkpoint) is built in + :func:`run_model`. This helper keeps the single-checkpoint shape that unit + tests assert against. + """ + ckpt_name, checkpoint = resolve_checkpoints(model)[0] + m = {**model, "ckpt_name": ckpt_name, "checkpoint": checkpoint} + cmds: list[tuple[str, list[str]]] = [] + if "train" in stages: + cmds.append(("train", build_train_cmd(m))) + if "predict" in stages: + cmds.append(("predict", build_predict_cmd(m, ckpt_name, checkpoint))) + if "eval" in stages: + cmds.append(("eval", build_eval_cmd(m, ckpt_name))) + return cmds + + +def _submit(cmd: list[str]) -> str: + """Submit an sbatch command and return its job id (parsed from stdout).""" + out = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout + # sbatch prints "Submitted batch job 12345" + return out.strip().split()[-1] + + +def _emit(stage: str, label: str, cmd: list[str], dep_jid: str | None, print_only: bool) -> str | None: + """Print or submit one stage command with an optional afterok dependency. + + Returns the submitted job id (or None when printing). + """ + dep = f"--dependency=afterok:{dep_jid if not print_only else ''}" if dep_jid else None + full = cmd[:1] + ([dep] if dep else []) + cmd[1:] + if print_only: + print(f"# {label} [{stage}]") + print(" ".join(full)) + return None + jid = _submit(full) + print(f"{label} [{stage}] -> job {jid}", file=sys.stderr) + return jid + + +def run_model(model: dict, stages: tuple[str, ...], print_only: bool) -> None: + """Submit/print a model's jobs: train ONCE, then predict→eval per checkpoint. + + A model may sweep multiple checkpoints (``ckpt_names`` / ``checkpoints``). + Train runs once; each checkpoint gets its own predict→eval chain, both + ``--dependency=afterok`` on the shared train job (models and per-checkpoint + chains otherwise run in parallel via normal SLURM scheduling). + """ + base = f"{model['family']}/{model['run']}" + train_jid: str | None = None + if "train" in stages: + train_jid = _emit("train", base, build_train_cmd(model), None, print_only) + + for ckpt_name, checkpoint in resolve_checkpoints(model): + label = f"{base}/{ckpt_name}" + prev = train_jid # predict depends on the shared train (if any) + if "predict" in stages: + prev = _emit("predict", label, build_predict_cmd(model, ckpt_name, checkpoint), prev, print_only) + if "eval" in stages: + _emit("eval", label, build_eval_cmd(model, ckpt_name), prev, print_only) + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "-c", + "--matrix", + "matrix", + type=click.Path(path_type=Path), + required=True, + help="Matrix YAML (defaults + models).", +) +@click.option( + "--stages", + default=",".join(_ALL_STAGES), + help=f"Comma-separated subset of {_ALL_STAGES} to run (default: all).", +) +@click.option("--dry-run", is_flag=True, help="Print the chained commands, submit nothing.") +@click.option("--print-cmd", is_flag=True, help="Alias for --dry-run.") +@click.option( + "--skip-preflight", + is_flag=True, + help="Skip the upfront AI-ready (normalization/focus_slice) check across all datasets.", +) +def main(matrix: Path, stages: str, dry_run: bool, print_cmd: bool, skip_preflight: bool) -> None: + """Run many models through train→predict→eval in parallel (SLURM afterok chain).""" + stage_tuple = tuple(s.strip() for s in stages.split(",") if s.strip()) + bad = [s for s in stage_tuple if s not in _ALL_STAGES] + if bad: + raise click.BadParameter(f"unknown stage(s): {bad}; valid: {_ALL_STAGES}", param_hint="--stages") + + models = load_matrix(matrix) + print_only = dry_run or print_cmd + + # Upfront preflight: only meaningful when we will predict, and only on a real + # submission (dry-run must stay side-effect-free / offline). + if "predict" in stage_tuple and not print_only and not skip_preflight: + matrix_preflight(models) + + print(f"{len(models)} model(s); stages={stage_tuple}; {'DRY-RUN' if print_only else 'SUBMITTING'}", file=sys.stderr) + for model in models: + run_model(model, stage_tuple, print_only) + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/src/dynaclr/evaluation/orchestration/predict_batch.py b/applications/dynaclr/src/dynaclr/evaluation/orchestration/predict_batch.py new file mode 100644 index 000000000..9b0150f3e --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/orchestration/predict_batch.py @@ -0,0 +1,323 @@ +r"""Batch predict embeddings for one model over a collection's datasets. + +Wraps ``dynaclr predict-triplet`` — the predict stage of the model matrix. Given +a model identity ``(model_family, run, ckpt_name)`` and a collection, it builds +the checkpoint path (from the training run dir) and the ``predict-triplet`` +command that writes per-marker embeddings into the dataset-centric tree +``/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr``. + +Used standalone and by ``run-matrix`` (the predict link of the chain). + +Checkpoint selection is manual via ``--ckpt-name``: + * ``last`` → ``/checkpoints/last.ckpt`` + * ``epoch105-step84800`` → ``/checkpoints/epoch=105-step=84800.ckpt`` + +Output: + * ``--print-cmd``: print the ``dynaclr predict-triplet`` command (one token per line). + * default: execute it via ``subprocess.run``. + +Usage:: + + dynaclr predict-batch \ + -c applications/dynaclr/configs/collections/<...>/.yml \ + --model-family DynaCLR-2D-MIP-BagOfChannels \ + --run 2d-mip-fix-shuffler --ckpt-name last \ + --datasets-root /hpc/projects/intracellular_dashboard/organelle_dynamics \ + [--markers SEC61B] [--no-labelfree] [--print-cmd] +""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import click + +from dynaclr.evaluation.paths import DATASETS_ROOT + +#: Default training-model root (where {model_family}/{run}/checkpoints/ live). +MODELS_ROOT = Path("/hpc/projects/organelle_phenotyping/models") + + +def check_ai_ready(collection: Path) -> list[dict]: + """Report which of a collection's datasets are not AI-ready. + + ``predict-triplet`` reads ``normalization`` and ``focus_slice`` from each + FOV's zattrs (written upstream). This inspects the first FOV of every + experiment's ``data_path`` and returns, per dataset, which of the two are + missing — so the caller can flag them (focus_slice needs manual z-focus + physics params, so it is never auto-run) or auto-run the safe normalization. + + Parameters + ---------- + collection : Path + Collection YAML; ``${datasets_root}`` is resolved by ``load_collection``. + + Returns + ------- + list[dict] + One entry per experiment: ``{name, data_path, missing_normalization, + missing_focus_slice}``. + """ + from iohub import open_ome_zarr + + from viscy_data.collection import load_collection + + coll = load_collection(collection) + report: list[dict] = [] + for exp in coll.experiments: + with open_ome_zarr(exp.data_path, mode="r") as plate: + _, pos = next(plate.positions()) + keys = set(pos.zattrs) + report.append( + { + "name": exp.name, + "data_path": exp.data_path, + "missing_normalization": "normalization" not in keys, + "missing_focus_slice": "focus_slice" not in keys, + } + ) + return report + + +def run_normalization(data_path: str, workspace_dir: str, num_workers: int = 32, block_size: int = 32) -> None: + """Auto-run ``viscy preprocess`` (normalization stats) on all channels. + + Safe to run unattended — it takes no dataset-specific physics params + (unlike QC/focus_slice). Writes ``normalization`` zattrs into ``data_path``. + """ + cmd = [ + "uv", + "run", + "--project", + workspace_dir, + "--package", + "dynaclr", + "viscy", + "preprocess", + "--data_path", + data_path, + "--channel_names=-1", + "--num_workers", + str(num_workers), + "--block_size", + str(block_size), + ] + print(f" auto-running normalization: {' '.join(cmd)}", file=sys.stderr) + subprocess.run(cmd, check=True) + + +def preflight(collection: Path, workspace_dir: str, auto_normalize: bool) -> None: + """Report non-AI-ready datasets; optionally auto-run normalization. + + focus_slice is only **flagged** (never auto-run — it needs manual z-focus + params). If any dataset is missing focus_slice, raise so the user runs QC + themselves (``qc run -c qc_config.yml``). + """ + report = check_ai_ready(collection) + needs_focus = [r for r in report if r["missing_focus_slice"]] + needs_norm = [r for r in report if r["missing_normalization"]] + + for r in needs_norm: + if auto_normalize: + run_normalization(r["data_path"], workspace_dir) + else: + print(f" [flag] {r['name']}: missing normalization ({r['data_path']})", file=sys.stderr) + + if needs_focus: + lines = "\n".join(f" - {r['name']}: {r['data_path']}" for r in needs_focus) + raise ValueError( + "These datasets are missing focus_slice zattrs and need QC run manually " + "(focus finding needs per-dataset z-focus physics params — NA, wavelength, " + "pixel size — so it is not auto-run):\n" + f"{lines}\n" + "Run `qc run -c /qc_config.yml` for each, then retry." + ) + if needs_norm and not auto_normalize: + raise ValueError( + "Datasets missing normalization zattrs (rerun with --auto-normalize to compute " + "them, or run `viscy preprocess --data_path --channel_names=-1` yourself)." + ) + + +def checkpoint_path( + model_family: str, + run: str, + ckpt_name: str, + models_root: str | Path = MODELS_ROOT, +) -> Path: + """Resolve the checkpoint file from the model identity + manual label. + + Parameters + ---------- + model_family, run : str + Training ``PROJECT`` / ``RUN_NAME`` — the run dir is + ``{models_root}/{model_family}/{run}/checkpoints``. + ckpt_name : str + ``last`` → ``last.ckpt``; otherwise a ``epochN-stepM`` label mapped to + ``epoch=N-step=M.ckpt``. + models_root : str or Path, optional + Base under which trained models live. Defaults to :data:`MODELS_ROOT`. + + Returns + ------- + Path + The checkpoint file path. + """ + ckpt_dir = Path(models_root) / model_family / run / "checkpoints" + if ckpt_name == "last": + filename = "last.ckpt" + else: + # epoch105-step84800 -> epoch=105-step=84800.ckpt + filename = ckpt_name.replace("epoch", "epoch=").replace("-step", "-step=") + ".ckpt" + return ckpt_dir / filename + + +def build_predict_cmd( + collection: Path, + checkpoint: Path, + model_family: str, + run: str, + ckpt_name: str, + datasets_root: str | Path, + predict_flags: dict | None = None, + markers: list[str] | None = None, + no_labelfree: bool = False, + num_workers: int = 0, +) -> list[str]: + """Build the ``dynaclr predict-triplet`` command.""" + cmd = [ + "dynaclr", + "predict-triplet", + "-c", + str(collection), + "--checkpoint", + str(checkpoint), + "--model-family", + model_family, + "--run", + run, + "--ckpt-name", + ckpt_name, + "--datasets-root", + str(datasets_root), + "--num-workers", + str(num_workers), + ] + flags = predict_flags or {} + if "z_range" in flags: + cmd += ["--z-range", str(flags["z_range"][0]), str(flags["z_range"][1])] + if "z_reduction" in flags: + cmd += ["--z-reduction", str(flags["z_reduction"])] + if "reference_pixel_size" in flags: + cmd += ["--reference-pixel-size", str(flags["reference_pixel_size"])] + if "batch_size" in flags: + cmd += ["--batch-size", str(flags["batch_size"])] + if markers: + cmd += ["--markers", ",".join(markers)] + if no_labelfree: + cmd.append("--no-labelfree") + return cmd + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "-c", + "--collection", + type=click.Path(path_type=Path), + required=True, + help="Collection YAML (predict-triplet input).", +) +@click.option("--model-family", required=True, help="Model family (PROJECT / embedding-tree key).") +@click.option("--run", required=True, help="Training run/version (RUN_NAME / embedding-tree key).") +@click.option("--ckpt-name", default="last", help="Checkpoint label: 'last' or 'epochN-stepM' (default: last).") +@click.option( + "--checkpoint", + type=click.Path(path_type=Path), + default=None, + help="Explicit checkpoint path — overrides derivation from --models-root/--run/--ckpt-name. " + "Use when the checkpoint is under a wandb-run-id subdir (Lightning's default nesting).", +) +@click.option( + "--models-root", + type=click.Path(path_type=Path), + default=MODELS_ROOT, + show_default=True, + help="Base under which trained models live.", +) +@click.option( + "--datasets-root", + type=click.Path(path_type=Path), + default=DATASETS_ROOT, + show_default=True, + help="Base under which datasets live.", +) +@click.option("--markers", multiple=True, default=None, help="Marker subset (default: all channels).") +@click.option("--no-labelfree", is_flag=True, help="Skip label-free (phase/brightfield) channels.") +@click.option("--num-workers", type=int, default=0, help="Predict dataloader workers (must be 0).") +@click.option("--print-cmd", is_flag=True, help="Print the command instead of executing it.") +@click.option( + "--auto-normalize", + is_flag=True, + help="Auto-run `viscy preprocess` for datasets missing normalization zattrs (safe, no manual params). " + "focus_slice is always only flagged, never auto-run.", +) +@click.option( + "--skip-preflight", + is_flag=True, + help="Skip the AI-ready (normalization/focus_slice zattrs) preflight entirely.", +) +@click.option( + "--workspace-dir", + default="/hpc/mydata/eduardo.hirata/repos/viscy", + help="uv workspace dir (for the auto-normalize preprocess call).", +) +def main( + collection: Path, + model_family: str, + run: str, + ckpt_name: str, + checkpoint: Path | None, + models_root: Path, + datasets_root: Path, + markers: tuple[str, ...], + no_labelfree: bool, + num_workers: int, + print_cmd: bool, + auto_normalize: bool, + skip_preflight: bool, + workspace_dir: str, +) -> None: + """Batch predict embeddings for one model over a collection (+ AI-ready preflight). + + ``--markers`` runs exactly the named markers (extras in the collection are + ignored); omit it to embed all channels. + """ + if not print_cmd and not skip_preflight: + preflight(collection, workspace_dir, auto_normalize=auto_normalize) + + resolved_checkpoint = checkpoint or checkpoint_path(model_family, run, ckpt_name, models_root=models_root) + cmd = build_predict_cmd( + collection, + resolved_checkpoint, + model_family, + run, + ckpt_name, + datasets_root, + markers=list(markers) if markers else None, + no_labelfree=no_labelfree, + num_workers=num_workers, + ) + + if print_cmd: + print("\n".join(cmd)) + return + + print(f"checkpoint: {resolved_checkpoint}", file=sys.stderr) + print(f"launching: {' '.join(cmd)}", file=sys.stderr) + subprocess.run(cmd, check=True) + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/tests/test_submit_eval.py b/applications/dynaclr/tests/test_submit_eval.py new file mode 100644 index 000000000..07813e326 --- /dev/null +++ b/applications/dynaclr/tests/test_submit_eval.py @@ -0,0 +1,45 @@ +"""Unit tests for submit_eval glob + command construction (pure logic).""" + +from pathlib import Path + +from dynaclr.evaluation.orchestration import eval_launch as submit_eval + +MF = "DynaCLR-2D-MIP-BagOfChannels" +RUN = "2d-mip-fix-shuffler" +CKPT = "epoch105-step84800" +ROOT = "/base" + + +def test_glob_all_datasets_all_markers(): + g = submit_eval.build_embeddings_glob(MF, RUN, CKPT, datasets_root=ROOT) + assert g == f"/base/*/2-phenotyping/predictions/{MF}/{RUN}/{CKPT}/*.zarr" + + +def test_glob_one_marker(): + g = submit_eval.build_embeddings_glob(MF, RUN, CKPT, marker="SEC61B", datasets_root=ROOT) + assert g.endswith(f"/{CKPT}/SEC61B.zarr") + assert "/*/2-phenotyping/" in g # still all datasets + + +def test_glob_single_dataset_no_braces(): + g = submit_eval.build_embeddings_glob(MF, RUN, CKPT, datasets=["ds_a"], datasets_root=ROOT) + assert "/base/ds_a/2-phenotyping/" in g + assert "{" not in g + + +def test_glob_multi_dataset_brace_expansion(): + g = submit_eval.build_embeddings_glob(MF, RUN, CKPT, datasets=["ds_a", "ds_b"], datasets_root=ROOT) + assert "/base/{ds_a,ds_b}/2-phenotyping/" in g + + +def test_nextflow_cmd_has_entry_and_glob(): + cmd = submit_eval.build_nextflow_cmd(Path("eval.yaml"), "/base/*/x/*.zarr", "/ws", resume=True) + assert "eval_from_embeddings" in cmd + assert "--embeddings_glob" in cmd + assert cmd[cmd.index("--embeddings_glob") + 1] == "/base/*/x/*.zarr" + assert "-resume" in cmd + + +def test_nextflow_cmd_no_resume(): + cmd = submit_eval.build_nextflow_cmd(Path("eval.yaml"), "g", "/ws", resume=False) + assert "-resume" not in cmd diff --git a/applications/dynaclr/tests/test_submit_matrix.py b/applications/dynaclr/tests/test_submit_matrix.py new file mode 100644 index 000000000..3b6c8ac59 --- /dev/null +++ b/applications/dynaclr/tests/test_submit_matrix.py @@ -0,0 +1,208 @@ +"""Unit tests for submit_matrix: .sh parsing, defaults merge, chained commands.""" + +from pathlib import Path + +import pytest +import yaml + +from dynaclr.evaluation.orchestration import matrix as submit_matrix + +_SH = """#!/bin/bash +#SBATCH --job-name=foo +export PROJECT="DynaCLR-2D-MIP-BagOfChannels" +export RUN_NAME="2d-mip-fix-shuffler" +export CONFIGS="DynaCLR-2D/base.yml DynaCLR-2D/single-marker.yml" +source train.sh +""" + + +def _write_sh(tmp_path, text=_SH) -> Path: + p = tmp_path / "model.sh" + p.write_text(text) + return p + + +def test_parse_train_sbatch(tmp_path): + got = submit_matrix.parse_train_sbatch(_write_sh(tmp_path)) + assert got["family"] == "DynaCLR-2D-MIP-BagOfChannels" + assert got["run"] == "2d-mip-fix-shuffler" + assert got["train_configs"] == ["DynaCLR-2D/base.yml", "DynaCLR-2D/single-marker.yml"] + + +def test_parse_missing_export_raises(tmp_path): + p = tmp_path / "bad.sh" + p.write_text('export PROJECT="X"\nexport RUN_NAME="y"\n') # no CONFIGS + with pytest.raises(ValueError, match="CONFIGS"): + submit_matrix.parse_train_sbatch(p) + + +def test_resolve_model_merges_defaults_and_parses(tmp_path): + sh = _write_sh(tmp_path) + defaults = {"ckpt_name": "last", "collection": "c.yml", "predict_flags": {"z_reduction": "mip"}} + model = {"train_sbatch": str(sh)} + r = submit_matrix.resolve_model(model, defaults) + assert r["family"] == "DynaCLR-2D-MIP-BagOfChannels" + assert r["run"] == "2d-mip-fix-shuffler" + assert r["ckpt_name"] == "last" # from defaults + assert r["collection"] == "c.yml" # from defaults + assert r["predict_flags"]["z_reduction"] == "mip" + + +def test_resolve_model_explicit_overrides_parsed(tmp_path): + sh = _write_sh(tmp_path) + r = submit_matrix.resolve_model({"train_sbatch": str(sh), "run": "custom-run"}, {"ckpt_name": "last"}) + assert r["run"] == "custom-run" # explicit wins over parsed RUN_NAME + + +def test_load_matrix_and_chain(tmp_path): + sh_a = _write_sh(tmp_path) + sh_b = tmp_path / "b.sh" + sh_b.write_text(_SH.replace("fix-shuffler", "vits-boc")) + matrix = tmp_path / "matrix.yml" + matrix.write_text( + yaml.safe_dump( + { + "defaults": { + "ckpt_name": "last", + "collection": "c.yml", + "eval_config": "e.yaml", + "datasets_root": "/d", + }, + "models": [{"train_sbatch": str(sh_a)}, {"train_sbatch": str(sh_b)}], + } + ) + ) + models = submit_matrix.load_matrix(matrix) + assert len(models) == 2 + + cmds = submit_matrix.build_stage_cmds(models[0], ("train", "predict", "eval")) + assert [stage for stage, _ in cmds] == ["train", "predict", "eval"] + # predict stage carries the resolved identity + collection + datasets_root + predict_cmd = dict(cmds)["predict"] + assert "DynaCLR-2D-MIP-BagOfChannels" in predict_cmd + assert "c.yml" in predict_cmd + assert "/d" in predict_cmd + + +def test_stages_subset_skips_train(tmp_path): + sh = _write_sh(tmp_path) + model = submit_matrix.resolve_model( + {"train_sbatch": str(sh)}, + {"ckpt_name": "last", "collection": "c.yml", "eval_config": "e.yaml", "datasets_root": "/d"}, + ) + cmds = submit_matrix.build_stage_cmds(model, ("predict", "eval")) + assert [stage for stage, _ in cmds] == ["predict", "eval"] + + +def test_predict_passes_explicit_checkpoint(tmp_path): + """An explicit `checkpoint:` in the model entry is forwarded to predict.sbatch.""" + sh = _write_sh(tmp_path) + ckpt = "/hpc/.../jbrwhzr3/checkpoints/epoch=105-step=84800.ckpt" + model = submit_matrix.resolve_model( + {"train_sbatch": str(sh), "checkpoint": ckpt}, + {"ckpt_name": "epoch105-step84800", "collection": "c.yml", "eval_config": "e.yaml", "datasets_root": "/d"}, + ) + predict_cmd = dict(submit_matrix.build_stage_cmds(model, ("predict",)))["predict"] + assert ckpt in predict_cmd + + +def test_predict_empty_checkpoint_when_derived(tmp_path): + """No explicit checkpoint → an empty positional (predict.sbatch then derives).""" + sh = _write_sh(tmp_path) + model = submit_matrix.resolve_model( + {"train_sbatch": str(sh)}, + {"ckpt_name": "last", "collection": "c.yml", "eval_config": "e.yaml", "datasets_root": "/d"}, + ) + predict_cmd = dict(submit_matrix.build_stage_cmds(model, ("predict",)))["predict"] + # positionals end with: … checkpoint(""), markers("") + assert predict_cmd[-2] == "" # empty checkpoint positional + assert predict_cmd[-1] == "" # empty markers positional + + +def test_predict_passes_markers(tmp_path): + """A matrix row's `markers:` list is forwarded as a comma-joined positional.""" + sh = _write_sh(tmp_path) + model = submit_matrix.resolve_model( + {"train_sbatch": str(sh), "markers": ["SEC61B", "Phase3D"]}, + {"ckpt_name": "last", "collection": "c.yml", "eval_config": "e.yaml", "datasets_root": "/d"}, + ) + predict_cmd = dict(submit_matrix.build_stage_cmds(model, ("predict",)))["predict"] + assert predict_cmd[-1] == "SEC61B,Phase3D" + + +# --- matrix-level preflight ------------------------------------------------- +import numpy as np # noqa: E402 +from iohub.ngff import open_ome_zarr # noqa: E402 + + +def _zarr(path, *, normalization, focus_slice): + with open_ome_zarr(path, layout="hcs", mode="w", channel_names=["Phase3D"]) as plate: + pos = plate.create_position("A", "1", "0") + pos.create_zeros("0", shape=(1, 1, 4, 8, 8), dtype=np.float32) + if normalization: + pos.zattrs["normalization"] = {"Phase3D": {"fov_statistics": {"mean": 0.0, "std": 1.0}}} + if focus_slice: + pos.zattrs["focus_slice"] = {"Phase3D": {"fov_statistics": {"z_focus_mean": 2}}} + + +def _collection_for(tmp_path, zarr_path, name="ds"): + coll = tmp_path / f"{name}.yml" + coll.write_text( + yaml.safe_dump( + { + "name": "c", + "experiments": [ + { + "name": name, + "data_path": str(zarr_path), + "tracks_path": str(tmp_path / "t"), + "channels": [{"name": "Phase3D", "marker": "Phase3D"}], + "perturbation_wells": {"uninfected": ["A/1"]}, + } + ], + } + ) + ) + return coll + + +def test_matrix_preflight_passes_when_ready(tmp_path): + z = tmp_path / "ready.zarr" + _zarr(z, normalization=True, focus_slice=True) + models = [{"collection": str(_collection_for(tmp_path, z))}] + submit_matrix.matrix_preflight(models) # should not raise + + +def test_matrix_preflight_raises_on_missing_focus(tmp_path): + z = tmp_path / "nofocus.zarr" + _zarr(z, normalization=True, focus_slice=False) + models = [{"collection": str(_collection_for(tmp_path, z))}] + with pytest.raises(ValueError, match="focus_slice"): + submit_matrix.matrix_preflight(models) + + +def test_resolve_checkpoints_scalar(tmp_path): + sh = _write_sh(tmp_path) + m = submit_matrix.resolve_model({"train_sbatch": str(sh)}, {"ckpt_name": "last"}) + assert submit_matrix.resolve_checkpoints(m) == [("last", "")] + + +def test_resolve_checkpoints_sweep_list(tmp_path): + sh = _write_sh(tmp_path) + m = submit_matrix.resolve_model({"train_sbatch": str(sh), "ckpt_names": ["last", "epoch80-step64000"]}, {}) + got = submit_matrix.resolve_checkpoints(m) + assert [n for n, _ in got] == ["last", "epoch80-step64000"] + + +def test_run_model_sweep_fans_out(tmp_path, capsys): + """ckpt sweep prints one train + a predict/eval pair per checkpoint (dry-run).""" + sh = _write_sh(tmp_path) + m = submit_matrix.resolve_model( + {"train_sbatch": str(sh), "ckpt_names": ["last", "epoch80-step64000"]}, + {"collection": "c.yml", "eval_config": "e.yaml", "datasets_root": "/d"}, + ) + submit_matrix.run_model(m, ("train", "predict", "eval"), print_only=True) + out = capsys.readouterr().out + assert out.count("[train]") == 1 # train once + assert out.count("[predict]") == 2 # one per checkpoint + assert out.count("[eval]") == 2 diff --git a/applications/dynaclr/tests/test_submit_predict.py b/applications/dynaclr/tests/test_submit_predict.py new file mode 100644 index 000000000..6c604ed69 --- /dev/null +++ b/applications/dynaclr/tests/test_submit_predict.py @@ -0,0 +1,59 @@ +"""Unit tests for submit_predict checkpoint resolution + command building.""" + +from pathlib import Path + +from dynaclr.evaluation.orchestration import predict_batch as submit_predict + +MF = "DynaCLR-2D-MIP-BagOfChannels" +RUN = "2d-mip-fix-shuffler" +ROOT = "/models" + + +def test_ckpt_last(): + p = submit_predict.checkpoint_path(MF, RUN, "last", models_root=ROOT) + assert p == Path(f"/models/{MF}/{RUN}/checkpoints/last.ckpt") + + +def test_ckpt_epoch_label(): + p = submit_predict.checkpoint_path(MF, RUN, "epoch105-step84800", models_root=ROOT) + assert p == Path(f"/models/{MF}/{RUN}/checkpoints/epoch=105-step=84800.ckpt") + + +def test_predict_cmd_core_flags(): + cmd = submit_predict.build_predict_cmd( + Path("coll.yml"), + Path("/models/x/last.ckpt"), + MF, + RUN, + "last", + "/data", + predict_flags={"z_range": [15, 45], "z_reduction": "mip", "reference_pixel_size": 0.1494}, + ) + assert cmd[:2] == ["dynaclr", "predict-triplet"] + assert cmd[cmd.index("--model-family") + 1] == MF + assert cmd[cmd.index("--datasets-root") + 1] == "/data" + assert cmd[cmd.index("--z-range") + 1 : cmd.index("--z-range") + 3] == ["15", "45"] + assert cmd[cmd.index("--z-reduction") + 1] == "mip" + assert cmd[cmd.index("--reference-pixel-size") + 1] == "0.1494" + + +def test_predict_cmd_markers_and_labelfree(): + cmd = submit_predict.build_predict_cmd( + Path("coll.yml"), + Path("/c.ckpt"), + MF, + RUN, + "last", + "/data", + markers=["SEC61B", "TOMM20"], + no_labelfree=True, + ) + assert cmd[cmd.index("--markers") + 1] == "SEC61B,TOMM20" + assert "--no-labelfree" in cmd + + +def test_predict_cmd_no_optional_flags(): + cmd = submit_predict.build_predict_cmd(Path("c.yml"), Path("/c.ckpt"), MF, RUN, "last", "/data") + assert "--markers" not in cmd + assert "--no-labelfree" not in cmd + assert "--z-range" not in cmd diff --git a/applications/dynaclr/tests/test_submit_predict_preflight.py b/applications/dynaclr/tests/test_submit_predict_preflight.py new file mode 100644 index 000000000..7ce4fa168 --- /dev/null +++ b/applications/dynaclr/tests/test_submit_predict_preflight.py @@ -0,0 +1,81 @@ +"""Tests for the AI-ready preflight in submit_predict (zattrs detection + flagging).""" + +from pathlib import Path + +import numpy as np +import pytest +import yaml +from iohub.ngff import open_ome_zarr + +from dynaclr.evaluation.orchestration import predict_batch as submit_predict + +CHANNELS = ["Phase3D"] + + +def _make_zarr(path: Path, *, normalization: bool, focus_slice: bool) -> None: + with open_ome_zarr(path, layout="hcs", mode="w", channel_names=CHANNELS) as plate: + pos = plate.create_position("A", "1", "0") + pos.create_zeros("0", shape=(1, 1, 4, 8, 8), dtype=np.float32) + if normalization: + pos.zattrs["normalization"] = {"Phase3D": {"fov_statistics": {"mean": 0.0, "std": 1.0}}} + if focus_slice: + pos.zattrs["focus_slice"] = {"Phase3D": {"fov_statistics": {"z_focus_mean": 2}}} + + +def _collection(tmp_path: Path, data_path: Path, name: str = "ds") -> Path: + coll = tmp_path / "collection.yml" + coll.write_text( + yaml.safe_dump( + { + "name": "c", + "experiments": [ + { + "name": name, + "data_path": str(data_path), + "tracks_path": str(tmp_path / "tracks"), + "channels": [{"name": "Phase3D", "marker": "Phase3D"}], + "perturbation_wells": {"uninfected": ["A/1"]}, + } + ], + } + ) + ) + return coll + + +def test_check_ai_ready_all_present(tmp_path): + z = tmp_path / "ok.zarr" + _make_zarr(z, normalization=True, focus_slice=True) + report = submit_predict.check_ai_ready(_collection(tmp_path, z)) + assert report[0]["missing_normalization"] is False + assert report[0]["missing_focus_slice"] is False + + +def test_check_ai_ready_flags_missing(tmp_path): + z = tmp_path / "raw.zarr" + _make_zarr(z, normalization=False, focus_slice=False) + report = submit_predict.check_ai_ready(_collection(tmp_path, z)) + assert report[0]["missing_normalization"] is True + assert report[0]["missing_focus_slice"] is True + + +def test_preflight_raises_on_missing_focus(tmp_path): + """focus_slice is never auto-run — a missing one must raise with QC guidance.""" + z = tmp_path / "nofocus.zarr" + _make_zarr(z, normalization=True, focus_slice=False) + with pytest.raises(ValueError, match="focus_slice"): + submit_predict.preflight(_collection(tmp_path, z), "/ws", auto_normalize=True) + + +def test_preflight_raises_on_missing_norm_without_auto(tmp_path): + z = tmp_path / "nonorm.zarr" + _make_zarr(z, normalization=False, focus_slice=True) + with pytest.raises(ValueError, match="normalization"): + submit_predict.preflight(_collection(tmp_path, z), "/ws", auto_normalize=False) + + +def test_preflight_passes_when_ai_ready(tmp_path): + z = tmp_path / "ready.zarr" + _make_zarr(z, normalization=True, focus_slice=True) + # Should not raise. + submit_predict.preflight(_collection(tmp_path, z), "/ws", auto_normalize=False) diff --git a/applications/dynaclr/tools/README.md b/applications/dynaclr/tools/README.md new file mode 100644 index 000000000..1b3d8224a --- /dev/null +++ b/applications/dynaclr/tools/README.md @@ -0,0 +1,91 @@ +# DynaCLR model matrix — run many models through train → predict → eval + +Launchers for running one or many DynaCLR models through the full pipeline. Models +run **in parallel**; within a model the three stages chain via SLURM +`--dependency=afterok`. Embeddings are written once into the dataset-centric tree +(`/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr`) +and evaluated from there — no re-prediction to iterate on evals. + +## The matrix YAML + +One file lists the sweep. A `defaults:` block holds shared fields; each model entry +is essentially **one line** — its training `.sh`. `family` / `run` / `train_configs` +are parsed from that script's `export PROJECT= / RUN_NAME= / CONFIGS=` lines (so they +can't drift from what training actually runs). Example: +[`../configs/matrix/example.yml`](../configs/matrix/example.yml). + +```yaml +defaults: + ckpt_name: last # → checkpoints/last.ckpt (or pin epochN-stepM) + collection: applications/dynaclr/configs/collections/<...>.yml + eval_config: applications/dynaclr/configs/evaluation/<...>.yaml + datasets_root: /hpc/projects/intracellular_dashboard/organelle_dynamics + predict_flags: {z_range: [15, 45], z_reduction: mip, reference_pixel_size: 0.1494} +models: + - train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/<...>.sh + - train_sbatch: applications/dynaclr/configs/training/DynaCLR-3D/<...>.sh +``` + +Add a model = add one line. Change something sweep-wide = edit `defaults:`. + +## Running + +```bash +# see the chained sbatch commands without submitting +uv run dynaclr run-matrix -c --dry-run + +# submit the full chain (train → predict → eval) for every model, in parallel +uv run dynaclr run-matrix -c + +# run a subset of stages (e.g. skip training, evaluate existing checkpoints) +uv run dynaclr run-matrix -c --stages predict,eval +``` + +Per model, `dynaclr run-matrix` submits: +`train` → `predict` (`afterok:train`) → `eval` (`afterok:predict`). `train` is the +model's own `.sh`; `predict` and `eval` are the `predict.sbatch` / `eval.sbatch` +wrappers (which call `dynaclr predict-batch` / `dynaclr eval`). + +## Preprocessing precondition (AI-ready datasets) + +Prediction needs each dataset's FOV zattrs to already carry `normalization` + +`focus_slice` (written by the upstream `prepare` pipeline). `dynaclr run-matrix` runs +an **upfront preflight** across every dataset before submitting anything: + +- **normalization** missing → safe to auto-run (`viscy preprocess`, no manual params). + `dynaclr predict-batch --auto-normalize` will do it; the matrix flags it. +- **focus_slice** missing → **only flagged, never auto-run** — z-focus finding needs + per-dataset physics params (NA / wavelength / pixel size in `qc_config.yml`). + Run `qc run -c /qc_config.yml` yourself, then retry. + +`--skip-preflight` bypasses the check. + +## Checkpoint selection + +Manual. By default the checkpoint is derived as +`{models_root}/{family}/{run}/checkpoints/{last.ckpt | epoch=N-step=M.ckpt}` from `ckpt_name` +(`last` or `epochN-stepM`). No automatic best-by-metric selection. + +**Existing models usually need an explicit `checkpoint:`.** Lightning writes checkpoints under a +`{run}/{PROJECT}/{wandb_run_id}/checkpoints/` subdir (the wandb id isn't derivable from the +identity, and a run may have several). Give the full path in the matrix entry to override the +derived default: + +```yaml +models: + - train_sbatch: .../DynaCLR-2D-MIP-BagOfChannels-single-marker.sh + ckpt_name: epoch105-step84800 + checkpoint: /hpc/.../{run}/DynaCLR-2D-MIP-BagOfChannels/jbrwhzr3/checkpoints/epoch=105-step=84800.ckpt +``` + +## The individual launchers + +| Tool | Does | +|---|---| +| `dynaclr run-matrix` | the whole matrix — parse `.sh`, preflight, chain train→predict→eval | +| `dynaclr predict-batch` | predict one model over a collection (wraps `predict-triplet`) + AI-ready preflight | +| `dynaclr eval` | evaluate one model's embeddings (launches Nextflow `eval_from_embeddings`) | +| `predict.sbatch` / `eval.sbatch` | the wrapper jobs the matrix chains | + +See [`../docs/DAGs/end_to_end.md`](../docs/DAGs/end_to_end.md) for the pipeline overview +and [`../nextflow/README.md`](../nextflow/README.md) for the eval Nextflow entries. diff --git a/applications/dynaclr/tools/eval.sbatch b/applications/dynaclr/tools/eval.sbatch new file mode 100644 index 000000000..1dfd1e484 --- /dev/null +++ b/applications/dynaclr/tools/eval.sbatch @@ -0,0 +1,39 @@ +#!/bin/bash +# Eval link of the model matrix (dynaclr run-matrix chains this after predict). +# Runs `dynaclr eval`, which launches the Nextflow eval_from_embeddings entry +# over the frozen embeddings for this (model_family, run, ckpt_name). Nextflow +# submits its own downstream SLURM steps under the executor, so this job is +# light (a launcher) — it stays alive to drive the Nextflow run. +# +# Positional args (passed by `dynaclr run-matrix`): +# $1 eval_config evaluation config YAML (what to compute) +# $2 model_family embedding-tree key +# $3 run embedding-tree key +# $4 ckpt_name embedding-tree key +# +#SBATCH --job-name=dynaclr_eval +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --partition=cpu +#SBATCH --cpus-per-task=4 +#SBATCH --mem=16G +#SBATCH --time=1-00:00:00 + +set -euo pipefail +export PYTHONNOUSERSITE=1 + +WORKSPACE_DIR="${WORKSPACE_DIR:-/hpc/mydata/eduardo.hirata/repos/viscy}" +cd "$WORKSPACE_DIR" + +module load nextflow/24.10.5 + +EVAL_CONFIG="$1" +MODEL_FAMILY="$2" +RUN="$3" +CKPT_NAME="$4" + +uv run --project "$WORKSPACE_DIR" dynaclr eval \ + --eval-config "$EVAL_CONFIG" \ + --model-family "$MODEL_FAMILY" \ + --run "$RUN" \ + --ckpt-name "$CKPT_NAME" diff --git a/applications/dynaclr/tools/predict.sbatch b/applications/dynaclr/tools/predict.sbatch new file mode 100644 index 000000000..fc35aad4a --- /dev/null +++ b/applications/dynaclr/tools/predict.sbatch @@ -0,0 +1,57 @@ +#!/bin/bash +# Predict link of the model matrix (dynaclr run-matrix chains this after train). +# Runs `dynaclr predict-triplet` for one model over its collection via +# `dynaclr predict-batch`. +# +# Positional args (passed by `dynaclr run-matrix`): +# $1 collection collection YAML (predict-triplet input) +# $2 model_family PROJECT / embedding-tree key +# $3 run RUN_NAME / embedding-tree key +# $4 ckpt_name checkpoint label (last | epochN-stepM) +# $5 datasets_root base under which datasets live +# $6 checkpoint (optional) explicit checkpoint path; empty = derive from run dir +# +#SBATCH --job-name=dynaclr_predict +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --partition=gpu +#SBATCH --gpus=1 +#SBATCH --cpus-per-task=8 +#SBATCH --mem=64G +#SBATCH --time=8:00:00 + +set -euo pipefail +export PYTHONNOUSERSITE=1 + +WORKSPACE_DIR="${WORKSPACE_DIR:-/hpc/mydata/eduardo.hirata/repos/viscy}" +cd "$WORKSPACE_DIR" + +COLLECTION="$1" +MODEL_FAMILY="$2" +RUN="$3" +CKPT_NAME="$4" +DATASETS_ROOT="$5" +CHECKPOINT="${6:-}" +MARKERS="${7:-}" + +CKPT_FLAG=() +if [ -n "$CHECKPOINT" ]; then + CKPT_FLAG=(--checkpoint "$CHECKPOINT") +fi + +MARKER_FLAG=() +if [ -n "$MARKERS" ]; then + MARKER_FLAG=(--markers) + IFS=',' read -ra _M <<< "$MARKERS" + MARKER_FLAG+=("${_M[@]}") +fi + +srun uv run --project "$WORKSPACE_DIR" dynaclr predict-batch \ + -c "$COLLECTION" \ + --model-family "$MODEL_FAMILY" \ + --run "$RUN" \ + --ckpt-name "$CKPT_NAME" \ + --datasets-root "$DATASETS_ROOT" \ + "${CKPT_FLAG[@]}" \ + "${MARKER_FLAG[@]}" \ + --num-workers 0 From cd03f60ea3cc0a307f655177039ae00a28274b24 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:09:33 -0700 Subject: [PATCH 45/89] config(dynaclr): add organelle-box remodeling matrix --- .../2024_11_07_A549_SEC61_DENV.yml | 41 +++++++++++++++ .../2026_04_08_A549_G3BP1_ZIKV.yml | 38 ++++++++++++++ .../2026_04_10_A549_TOMM20_ZIKV.yml | 38 ++++++++++++++ .../2026_04_14_A549_SEC61B_DENV.yml | 38 ++++++++++++++ .../2026_04_21_A549_G3BP1_DENV.yml | 41 +++++++++++++++ .../2026_04_28_A549_SEC61B_DENV.yml | 38 ++++++++++++++ .../2026_04_30_A549_TOMM20_DENV.yml | 38 ++++++++++++++ ...26_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV.yml | 51 +++++++++++++++++++ .../configs/matrix/organelle_remodeling.yml | 44 ++++++++++++++++ .../viscy-data/src/viscy_data/collection.py | 41 +++++++++++++++ packages/viscy-data/tests/test_collection.py | 43 ++++++++++++++++ 11 files changed, 451 insertions(+) create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2024_11_07_A549_SEC61_DENV.yml create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_08_A549_G3BP1_ZIKV.yml create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_10_A549_TOMM20_ZIKV.yml create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_21_A549_G3BP1_DENV.yml create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_28_A549_SEC61B_DENV.yml create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_30_A549_TOMM20_DENV.yml create mode 100644 applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV.yml create mode 100644 applications/dynaclr/configs/matrix/organelle_remodeling.yml diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2024_11_07_A549_SEC61_DENV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2024_11_07_A549_SEC61_DENV.yml new file mode 100644 index 000000000..596cd1744 --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2024_11_07_A549_SEC61_DENV.yml @@ -0,0 +1,41 @@ +name: organelle-box-2024_11_07_A549_SEC61_DENV +description: "Profiling/handoff collection: 2024_11_07 A549 SEC61 DENV (single-marker + organelle box). Channels embedded as bag-of-channels samples: SEC61B (GFP), + viral_sensor (mCherry), Phase3D. Prepared zarr on VAST with timepoint_statistics + + focus_slice. Sourced from Airtable base app8vqaoWyOwa0sB5 (dataset + '2024_11_07_A549_SEC61_DENV'). FOV C/1/000000 exists in the zarr but has no tracking + CSV and is excluded at build time." +datasets_root: /hpc/projects/organelle_phenotyping/datasets + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "{dataset}=\"2024_11_07_A549_SEC61_DENV\"" + created_by: "eduardo.hirata" + +experiments: + - name: 2024_11_07_A549_SEC61_DENV + data_path: ${datasets_root}/2024_11_07_A549_SEC61_DENV/2024_11_07_A549_SEC61_DENV.zarr + tracks_path: ${datasets_root}/2024_11_07_A549_SEC61_DENV/tracking.zarr + channels: + - name: raw GFP EX488 EM525-45 + marker: SEC61B + - name: raw mCherry EX561 EM600-37 + marker: viral_sensor + - name: Phase3D + marker: Phase3D + perturbation_wells: + mock: + - B/1 + - B/3 + DENV: + - B/2 + - C/2 + exclude_fovs: + - C/1/000000 + interval_minutes: 10.0 + start_hpi: 4.0 + marker: SEC61B + organelle: endoplasmic_reticulum + moi: 5.0 + pixel_size_xy_um: 0.1494 + pixel_size_z_um: 0.174 diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_08_A549_G3BP1_ZIKV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_08_A549_G3BP1_ZIKV.yml new file mode 100644 index 000000000..ac2b060fd --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_08_A549_G3BP1_ZIKV.yml @@ -0,0 +1,38 @@ +name: organelle-box-2026_04_08_A549_G3BP1_ZIKV +description: "Embed-only collection: 2026_04_08 A549 G3BP1 ZIKV (single-marker organelle box). + Channels embedded as bag-of-channels samples: G3BP1 (GFP), viral_sensor (mCherry), Phase3D. + Prepared zarr on VAST with timepoint_statistics + focus_slice. No annotations." +datasets_root: /hpc/projects/organelle_phenotyping/datasets + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "SEARCH(\"2026_04_08_A549_G3BP1_ZIKV\", {dataset})" + created_by: "eduardo.hirata" + +experiments: + - name: 2026_04_08_A549_G3BP1_ZIKV + data_path: ${datasets_root}/2026_04_08_A549_G3BP1_ZIKV/2026_04_08_A549_G3BP1_ZIKV.zarr + tracks_path: ${datasets_root}/2026_04_08_A549_G3BP1_ZIKV/tracking.zarr + channels: + - name: raw GFP EX488 EM525-45 + marker: G3BP1 + - name: raw mCherry EX561 EM600-37 + marker: viral_sensor + - name: Phase3D + marker: Phase3D + perturbation_wells: + uninfected: + - D/6 + ZIKV: + - B/5 + - B/6 + - C/5 + - C/6 + - D/5 + interval_minutes: 30.0 + start_hpi: 3.0 + marker: G3BP1 + organelle: stress_granules + moi: 5.0 + pixel_size_xy_um: 0.1133 + pixel_size_z_um: 0.16995 diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_10_A549_TOMM20_ZIKV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_10_A549_TOMM20_ZIKV.yml new file mode 100644 index 000000000..db0f5ffe4 --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_10_A549_TOMM20_ZIKV.yml @@ -0,0 +1,38 @@ +name: organelle-box-2026_04_10_A549_TOMM20_ZIKV +description: "Embed-only collection: 2026_04_10 A549 TOMM20 ZIKV (single-marker organelle box). + Channels embedded as bag-of-channels samples: TOMM20 (GFP), viral_sensor (mCherry), Phase3D. + Prepared zarr on VAST with timepoint_statistics + focus_slice. No annotations." +datasets_root: /hpc/projects/organelle_phenotyping/datasets + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "SEARCH(\"2026_04_10_A549_TOMM20_ZIKV\", {dataset})" + created_by: "eduardo.hirata" + +experiments: + - name: 2026_04_10_A549_TOMM20_ZIKV + data_path: ${datasets_root}/2026_04_10_A549_TOMM20_ZIKV/2026_04_10_A549_TOMM20_ZIKV.zarr + tracks_path: ${datasets_root}/2026_04_10_A549_TOMM20_ZIKV/tracking.zarr + channels: + - name: raw GFP EX488 EM525-45 + marker: TOMM20 + - name: raw mCherry EX561 EM600-37 + marker: viral_sensor + - name: Phase3D + marker: Phase3D + perturbation_wells: + uninfected: + - A/2 + ZIKV: + - A/3 + - A/4 + - B/2 + - B/3 + - B/4 + interval_minutes: 30.0 + start_hpi: 5.0 + marker: TOMM20 + organelle: mitochondria + moi: 5.0 + pixel_size_xy_um: 0.1133 + pixel_size_z_um: 0.16995 diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml new file mode 100644 index 000000000..e653fd307 --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml @@ -0,0 +1,38 @@ +name: organelle-box-2026_04_14_A549_SEC61B_DENV +description: "Embed-only collection: 2026_04_14 A549 SEC61B DENV (single-marker organelle box). + Channels embedded as bag-of-channels samples: SEC61B (GFP), viral_sensor (mCherry), Phase3D. + Prepared zarr on VAST with timepoint_statistics + focus_slice. No annotations." +datasets_root: /hpc/projects/organelle_phenotyping/datasets + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "SEARCH(\"2026_04_14_A549_SEC61B_DENV\", {dataset})" + created_by: "eduardo.hirata" + +experiments: + - name: 2026_04_14_A549_SEC61B_DENV + data_path: ${datasets_root}/2026_04_14_A549_SEC61B_DENV/2026_04_14_A549_SEC61B_DENV.zarr + tracks_path: ${datasets_root}/2026_04_14_A549_SEC61B_DENV/tracking.zarr + channels: + - name: raw GFP EX488 EM525-45 + marker: SEC61B + - name: raw mCherry EX561 EM600-37 + marker: viral_sensor + - name: Phase3D + marker: Phase3D + perturbation_wells: + uninfected: + - C/2 + DENV: + - C/3 + - C/4 + - D/2 + - D/3 + - D/4 + interval_minutes: 30.0 + start_hpi: 3.0 + marker: SEC61B + organelle: endoplasmic_reticulum + moi: 5.0 + pixel_size_xy_um: 0.1133 + pixel_size_z_um: 0.16995 diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_21_A549_G3BP1_DENV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_21_A549_G3BP1_DENV.yml new file mode 100644 index 000000000..f5b0329d0 --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_21_A549_G3BP1_DENV.yml @@ -0,0 +1,41 @@ +name: organelle-box-2026_04_21_A549_G3BP1_DENV +description: "Embed-only collection: 2026_04_21 A549 G3BP1 DENV (single-marker organelle box). + Channels embedded as bag-of-channels samples: G3BP1 (GFP), viral_sensor (mCherry), Phase3D. + Source zarrs are the intracellular_dashboard preprocess outputs (5-assemble data + + 4-track ultrack labels); no prepared VAST copy exists. Predict uses fov_statistics + normalization, so timepoint_statistics is not required. Annotations exist (infection, + organelle, cell_division, cell_death)." +datasets_root: /hpc/projects/intracellular_dashboard/organelle_dynamics + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "SEARCH(\"2026_04_21_A549_G3BP1_DENV\", {dataset})" + created_by: "eduardo.hirata" + +experiments: + - name: 2026_04_21_A549_G3BP1_DENV + data_path: ${datasets_root}/2026_04_21_A549_G3BP1_DENV/1-preprocess/5-assemble/2026_04_21_G3BP1_DENV.zarr + tracks_path: ${datasets_root}/2026_04_21_A549_G3BP1_DENV/1-preprocess/4-track/2026_04_21_G3BP1_DENV.zarr + channels: + - name: raw GFP EX488 EM525-45 + marker: G3BP1 + - name: raw mCherry EX561 EM600-37 + marker: viral_sensor + - name: Phase3D + marker: Phase3D + perturbation_wells: + uninfected: + - A/1 + DENV: + - A/2 + - A/3 + - B/1 + - B/2 + - B/3 + interval_minutes: 30.0 + start_hpi: 3.0 + marker: G3BP1 + organelle: stress_granules + moi: 5.0 + pixel_size_xy_um: 0.1133 + pixel_size_z_um: 0.16995 diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_28_A549_SEC61B_DENV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_28_A549_SEC61B_DENV.yml new file mode 100644 index 000000000..b9bbf7e96 --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_28_A549_SEC61B_DENV.yml @@ -0,0 +1,38 @@ +name: organelle-box-2026_04_28_A549_SEC61B_DENV +description: "Embed-only collection: 2026_04_28 A549 SEC61B DENV (single-marker organelle box). + Channels embedded as bag-of-channels samples: SEC61B (GFP), viral_sensor (mCherry), Phase3D. + Prepared zarr on VAST with timepoint_statistics + focus_slice. No annotations." +datasets_root: /hpc/projects/organelle_phenotyping/datasets + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "SEARCH(\"2026_04_28_A549_SEC61B_DENV\", {dataset})" + created_by: "eduardo.hirata" + +experiments: + - name: 2026_04_28_A549_SEC61B_DENV + data_path: ${datasets_root}/2026_04_28_A549_SEC61B_DENV/2026_04_28_A549_SEC61B_DENV.zarr + tracks_path: ${datasets_root}/2026_04_28_A549_SEC61B_DENV/tracking.zarr + channels: + - name: raw GFP EX488 EM525-45 + marker: SEC61B + - name: raw mCherry EX561 EM600-37 + marker: viral_sensor + - name: Phase3D + marker: Phase3D + perturbation_wells: + uninfected: + - A/2 + DENV: + - A/3 + - A/4 + - B/2 + - B/3 + - B/4 + interval_minutes: 30.0 + start_hpi: 3.0 + marker: SEC61B + organelle: endoplasmic_reticulum + moi: 5.0 + pixel_size_xy_um: 0.1133 + pixel_size_z_um: 0.16995 diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_30_A549_TOMM20_DENV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_30_A549_TOMM20_DENV.yml new file mode 100644 index 000000000..e2e8bf159 --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_30_A549_TOMM20_DENV.yml @@ -0,0 +1,38 @@ +name: organelle-box-2026_04_30_A549_TOMM20_DENV +description: "Embed-only collection: 2026_04_30 A549 TOMM20 DENV (single-marker organelle box). + Channels embedded as bag-of-channels samples: TOMM20 (GFP), viral_sensor (mCherry), Phase3D. + Prepared zarr on VAST with timepoint_statistics + focus_slice. No annotations." +datasets_root: /hpc/projects/organelle_phenotyping/datasets + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "SEARCH(\"2026_04_30_A549_TOMM20_DENV\", {dataset})" + created_by: "eduardo.hirata" + +experiments: + - name: 2026_04_30_A549_TOMM20_DENV + data_path: ${datasets_root}/2026_04_30_A549_TOMM20_DENV/2026_04_30_A549_TOMM20_DENV.zarr + tracks_path: ${datasets_root}/2026_04_30_A549_TOMM20_DENV/tracking.zarr + channels: + - name: raw GFP EX488 EM525-45 + marker: TOMM20 + - name: raw mCherry EX561 EM600-37 + marker: viral_sensor + - name: Phase3D + marker: Phase3D + perturbation_wells: + uninfected: + - A/2 + DENV: + - A/3 + - A/4 + - B/2 + - B/3 + - B/4 + interval_minutes: 30.0 + start_hpi: 4.0 + marker: TOMM20 + organelle: mitochondria + moi: 5.0 + pixel_size_xy_um: 0.1133 + pixel_size_z_um: 0.16995 diff --git a/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV.yml b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV.yml new file mode 100644 index 000000000..10bdf24ff --- /dev/null +++ b/applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV.yml @@ -0,0 +1,51 @@ +name: organelle-box-2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV +description: "Embed-only collection: 2026_07_01 A549 SEC61B/TOMM20/G3BP1 ZIKV (multi-organelle box). + Bag-of-channels DynaCLR-2D-MIP embedding. The GFP reporter varies by plate column + (col 2=SEC61B, col 3=TOMM20, col 4=G3BP1); mCherry is the pAL17 viral sensor in every + well. Per-reporter embeddings are produced by subsetting wells (fit_include_wells) on + the GFP channel. Row A=uninfected, row B=ZIKV (MOI 5, 3 hpp). + Prepared zarr on VAST with normalization + focus_slice. No annotations." +datasets_root: /hpc/projects/organelle_phenotyping/datasets + +provenance: + airtable_base_id: app8vqaoWyOwa0sB5 + airtable_query: "SEARCH(\"2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV\", {dataset})" + created_by: "eduardo.hirata" + +experiments: + - name: 2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV + data_path: ${datasets_root}/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV.zarr + tracks_path: ${datasets_root}/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV/tracking.zarr + # Per-reporter embeddings (bag-of-channels, in_channels=1): one channel entry + # per reporter. The GFP reporter varies by plate column, so the same zarr + # channel appears once per organelle with its own `wells` (biologically valid + # wells; empty = all wells). `dynaclr predict-triplet` runs predict once per + # entry, restricting to `wells` via fit_include_wells, writing + # embeddings/{dataset}_{marker}.zarr. Start_hpi 3.0 (3 hpp). + channels: + - name: raw GFP EX488 EM525-45 + marker: SEC61B + wells: [A/2, B/2] + - name: raw GFP EX488 EM525-45 + marker: TOMM20 + wells: [A/3, B/3] + - name: raw GFP EX488 EM525-45 + marker: G3BP1 + wells: [A/4, B/4] + - name: raw mCherry EX561 EM600-37 + marker: pAL17 + wells: [A/2, A/3, A/4, B/2, B/3, B/4] + perturbation_wells: + uninfected: + - A/2 + - A/3 + - A/4 + ZIKV: + - B/2 + - B/3 + - B/4 + interval_minutes: 30.0 + start_hpi: 3.0 + moi: 5.0 + pixel_size_xy_um: 0.1133 + pixel_size_z_um: 0.16995 diff --git a/applications/dynaclr/configs/matrix/organelle_remodeling.yml b/applications/dynaclr/configs/matrix/organelle_remodeling.yml new file mode 100644 index 000000000..6ff17b2f4 --- /dev/null +++ b/applications/dynaclr/configs/matrix/organelle_remodeling.yml @@ -0,0 +1,44 @@ +# Organelle-remodeling model matrix — persistent, iterated on over time. +# +# ONE model (DynaCLR-2D-MIP-BagOfChannels single-marker) applied to MANY datasets. +# One row per dataset-collection; all rows share the same train_sbatch (identity), +# so the model is trained once elsewhere — here you iterate PREDICT + EVAL: +# +# # embeddings for every dataset (skip train — model already trained): +# dynaclr run-matrix -c applications/dynaclr/configs/matrix/organelle_remodeling.yml \ +# --stages predict,eval --dry-run # inspect, then drop --dry-run +# +# # add a dataset later: append a row, re-run (only new ones lack embeddings). +# +# Checkpoint: existing model → Lightning nests under {run}/{PROJECT}/{wandb_id}/checkpoints/, +# so give an explicit `checkpoint:` (the derived {run}/checkpoints/ path is empty). Set it once +# in defaults; all rows share the same trained checkpoint. + +defaults: + # identity: the training .sh's PROJECT/RUN_NAME are the (model_family, run) keys. + train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/DynaCLR-2D-MIP-BagOfChannels-single-marker.sh + ckpt_name: epoch105-step84800 + checkpoint: /hpc/projects/organelle_phenotyping/models/DynaCLR-2D-MIP-BagOfChannels/2d-mip-ntxent-t0p2-lr2e5-bs256-192to160-zext11-single-marker-fix-shuffler/DynaCLR-2D-MIP-BagOfChannels/jbrwhzr3/checkpoints/epoch=105-step=84800.ckpt + eval_config: applications/dynaclr/configs/evaluation/DynaCLR-2D-MIP-BagOfChannels/infectomics-annotated.yaml + datasets_root: /hpc/projects/intracellular_dashboard/organelle_dynamics + predict_flags: {z_range: [15, 45], z_reduction: mip, reference_pixel_size: 0.1494} + +# One row per dataset — the collection is the only thing that differs. +# markers: run exactly the organelle marker(s) present in each dataset (extras ignored). +models: + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2024_11_07_A549_SEC61_DENV.yml + markers: [SEC61B] + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml + markers: [SEC61B] + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_28_A549_SEC61B_DENV.yml + markers: [SEC61B] + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_08_A549_G3BP1_ZIKV.yml + markers: [G3BP1] + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_21_A549_G3BP1_DENV.yml + markers: [G3BP1] + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_10_A549_TOMM20_ZIKV.yml + markers: [TOMM20] + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_30_A549_TOMM20_DENV.yml + markers: [TOMM20] + - collection: applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV.yml + markers: [SEC61B, TOMM20, G3BP1] diff --git a/packages/viscy-data/src/viscy_data/collection.py b/packages/viscy-data/src/viscy_data/collection.py index a28656e7c..253af3f74 100644 --- a/packages/viscy-data/src/viscy_data/collection.py +++ b/packages/viscy-data/src/viscy_data/collection.py @@ -133,8 +133,49 @@ def _normalize(self) -> ExperimentEntry: # Derive channels from channel_names if not set if not self.channels and self.channel_names: self.channels = [ChannelEntry(name=ch, marker=ch) for ch in self.channel_names] + self._validate_shared_channel_wells() return self + def _validate_shared_channel_wells(self) -> None: + """Fail loud on ambiguous box-plate channels. + + When one physical channel ``name`` carries different markers by well + (e.g. a shared GFP → SEC61B in A/2, TOMM20 in A/3), each such entry must + pin non-overlapping ``wells``. If a shared-name channel has empty + ``wells`` (= all wells) or two entries' wells overlap, the well→marker + mapping is ambiguous — the same pixels would be embedded under multiple + markers. Raise now rather than silently produce wrong embeddings. + """ + from collections import defaultdict + + by_name: dict[str, list[ChannelEntry]] = defaultdict(list) + for ch in self.channels: + by_name[ch.name].append(ch) + + for name, entries in by_name.items(): + if len(entries) < 2: + continue # single-use channel: empty wells (= all) is fine + markers = [e.marker for e in entries] + no_wells = [e.marker for e in entries if not e.wells] + if no_wells: + raise ValueError( + f"Experiment '{self.name}': channel '{name}' maps to multiple markers " + f"{markers} but marker(s) {no_wells} have no 'wells' restriction — the " + "well→marker mapping is ambiguous (a shared channel used for several " + "organelles must pin each marker's wells). Add explicit 'wells' per entry." + ) + seen: dict[str, str] = {} + for e in entries: + for w in e.wells: + if w in seen: + raise ValueError( + f"Experiment '{self.name}': channel '{name}' well '{w}' is claimed by " + f"both marker '{seen[w]}' and '{e.marker}' — overlapping wells make the " + "well→marker mapping ambiguous. Wells must be disjoint across markers " + "sharing a channel." + ) + seen[w] = e.marker + class Collection(BaseModel): """Curated collection of experiments for training. diff --git a/packages/viscy-data/tests/test_collection.py b/packages/viscy-data/tests/test_collection.py index 4cd824ef0..31d47429d 100644 --- a/packages/viscy-data/tests/test_collection.py +++ b/packages/viscy-data/tests/test_collection.py @@ -449,3 +449,46 @@ def test_no_datasets_root_passthrough(self, tmp_path): with open(out_path) as f: on_disk = yaml.safe_load(f) assert on_disk["experiments"][0]["data_path"] == "/absolute/data/exp1.zarr" + + +class TestSharedChannelWells: + """Box plates: a shared physical channel used for several markers by well.""" + + _GFP = "raw GFP EX488 EM525-45" + + def test_box_plate_valid_disjoint_wells(self): + # Same GFP channel → 3 markers, each pinned to disjoint wells: OK. + _make_experiment( + channels=[ + ChannelEntry(name=self._GFP, marker="SEC61B", wells=["A/2", "B/2"]), + ChannelEntry(name=self._GFP, marker="TOMM20", wells=["A/3", "B/3"]), + ChannelEntry(name=self._GFP, marker="G3BP1", wells=["A/4", "B/4"]), + ], + ) + + def test_shared_channel_missing_wells_raises(self): + with pytest.raises(ValueError, match="ambiguous"): + _make_experiment( + channels=[ + ChannelEntry(name=self._GFP, marker="SEC61B", wells=["A/2"]), + ChannelEntry(name=self._GFP, marker="TOMM20"), # no wells = all → ambiguous + ], + ) + + def test_shared_channel_overlapping_wells_raises(self): + with pytest.raises(ValueError, match="ambiguous"): + _make_experiment( + channels=[ + ChannelEntry(name=self._GFP, marker="SEC61B", wells=["A/2", "A/3"]), + ChannelEntry(name=self._GFP, marker="TOMM20", wells=["A/3"]), # A/3 overlaps + ], + ) + + def test_single_use_channel_empty_wells_ok(self): + # One marker per channel, empty wells (= all) is fine — the common case. + _make_experiment( + channels=[ + ChannelEntry(name="Phase3D", marker="Phase3D"), + ChannelEntry(name=self._GFP, marker="SEC61B"), + ], + ) From 16502935bf4ef96c4d9dffa0d90cbc8cee73d8d2 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:13:10 -0700 Subject: [PATCH 46/89] feat(data): support parquet focus planes and cell-level splits --- applications/dynaclr/docs/DAGs/training.md | 27 ++++++----- .../dynaclr/src/dynaclr/data/datamodule.py | 46 ++++++++++++------- .../dynaclr/src/dynaclr/data/dataset.py | 45 +++++++++++++----- .../src/dynaclr/data/preprocess_cell_index.py | 10 +++- packages/viscy-data/src/viscy_data/_typing.py | 2 +- .../viscy-data/src/viscy_data/cell_index.py | 36 ++++++++++----- 6 files changed, 113 insertions(+), 53 deletions(-) diff --git a/applications/dynaclr/docs/DAGs/training.md b/applications/dynaclr/docs/DAGs/training.md index ca448dd7c..5a3b7f257 100644 --- a/applications/dynaclr/docs/DAGs/training.md +++ b/applications/dynaclr/docs/DAGs/training.md @@ -30,11 +30,14 @@ dynaclr build-cell-index \ ▼ dynaclr preprocess-cell-index \ /hpc/.../collections/.parquet \ - --focus-channel Phase3D + --focus-channel Phase3D --focus-level fov │ opens each unique FOV once from zarr zattrs: │ norm_mean/std/median/iqr/max/min — per (cell, timepoint, channel) - │ z_focus_mean — per FOV (mean across timepoints) - │ z — per timepoint focus slice index + │ z_focus — focus plane the Z window centers on, + │ from focus_slice at --focus-level + │ (fov = per-FOV mean; per_timepoint = + │ per-timepoint index). `z` (tracked + │ position) is left unchanged. │ drops empty frames (max == 0) ▼ .parquet (ready: self-contained, no zarr reads at training time) @@ -44,8 +47,9 @@ viscy fit --config configs/training/.yml │ OR: sbatch configs/training/.sh (SLURM, recommended) │ MultiExperimentDataModule reads parquet only at init │ tensorstore opens zarr lazily on first batch - │ ExperimentRegistry reads plate.zattrs["focus_slice"] once at startup - │ for z_ranges (z_extraction_window centered on dataset z_focus_mean) + │ Z window centers on the parquet `z_focus` column (per-sample). If z_focus + │ is null, falls back to the per-experiment z_range from zattrs focus_slice, + │ else mid-stack. z_focus_offset sets the below/above split. ▼ checkpoints/ + wandb logs ``` @@ -71,7 +75,7 @@ viscy fit (GPU, hours–days) | Step | Command | Input | Output | | --------------------- | ------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------- | | Build cell index | `dynaclr build-cell-index --num-workers 8` | collection YAML + zarr + tracking CSVs | parquet with TCZYX shape columns | -| Preprocess cell index | `dynaclr preprocess-cell-index --focus-channel Phase3D` | parquet + zarr zattrs | parquet with norm stats, per-timepoint z, empties removed | +| Preprocess cell index | `dynaclr preprocess-cell-index --focus-channel Phase3D --focus-level fov` | parquet + zarr zattrs | parquet with norm stats + z_focus, empties removed | | Train (interactive) | `uv run viscy fit --config configs/training/.yml` | training config + parquet | checkpoints + logs | | Train (SLURM) | `sbatch configs/training/.sh` | training config + parquet | checkpoints + logs | | Resume (SLURM) | `CKPT_PATH=.../last.ckpt sbatch configs/training/.sh` | checkpoint path env var | resumed checkpoints | @@ -85,8 +89,8 @@ viscy fit (GPU, hours–days) | Pixel data (TCZYX arrays) | zarr store on VAST | `prepare run` → concatenate | | Cell tracking (y, x, t, track_id) | tracking.zarr on VAST | `prepare run` → concatenate | | Normalization stats (per FOV/timepoint) | zarr zattrs → parquet `norm_*` columns | `viscy preprocess` → `preprocess-cell-index` | -| Focus slice (per timepoint) | zarr zattrs → parquet `z` column | `viscy preprocess` → `preprocess-cell-index` | -| Focus slice mean (per FOV) | zarr zattrs → parquet `z_focus_mean` | `viscy preprocess` → `preprocess-cell-index` | +| Tracked cell z (per cell) | tracking → parquet `z` column (unchanged by preprocess) | `build-cell-index` | +| Focus plane (Z window center) | zarr zattrs focus_slice → parquet `z_focus` | `viscy preprocess` → `preprocess-cell-index --focus-level` | | TCZYX shape per FOV | parquet columns | `build-cell-index` | | Collection definition | `configs/collections/.yml` in git | manually authored | | Parquet | `/hpc/projects/organelle_phenotyping/models/collections/` | `build-cell-index` | @@ -158,7 +162,8 @@ To reproduce: `build-cell-index` → `preprocess-cell-index` from the same colle ## Notes - `preprocess-cell-index` overwrites the parquet in-place by default. Pass `--output` to write elsewhere. -- `--focus-channel Phase3D` selects which channel's `per_timepoint` focus indices are written to the `z` column. Use the channel that has the sharpest axial contrast (label-free Phase3D for most experiments). -- At training time, `ExperimentRegistry.__post_init__` reads `plate.zattrs["focus_slice"][channel]["dataset_statistics"]["z_focus_mean"]` to compute per-experiment z_ranges for patch extraction. This is the only zarr metadata read at training startup; the parquet is self-contained for all per-cell data. -- The `z` column in the parquet is carried through to embeddings obs during predict — downstream consumers (e.g., visualization) can use it to recover the in-focus plane for each cell at each timepoint. +- `--focus-channel Phase3D` selects which channel's `focus_slice` metadata feeds the `z_focus` column. Use the channel with sharpest axial contrast (label-free Phase3D for most experiments). `--focus-level {fov,per_timepoint}` picks per-FOV mean vs per-timepoint index. +- **Z window centering is parquet-first.** At training time the datamodule centers the Z window on the per-sample `z_focus` column. If `z_focus` is null it falls back to the per-experiment `z_range` (which `ExperimentRegistry` derives from `plate.zattrs["focus_slice"][channel]["dataset_statistics"]["z_focus_mean"]`, else mid-stack). Existing parquets without a `z_focus` column read as all-null → identical to the previous zattrs-only behavior. +- `z_focus` and `z` are distinct: `z_focus` = where the Z window centers; `z` = the tracked cell position (unchanged by preprocess). Datasets without focus_slice (e.g. static bbox-center data) can seed `z_focus = z` in their own prep script. +- The `z` column is carried through to embeddings obs during predict for downstream consumers. - For performance tuning (num_workers, pin_memory, batch_size, augmentation placement), see [profiling.md](profiling.md) — authored after the first validated profiling sweep. diff --git a/applications/dynaclr/src/dynaclr/data/datamodule.py b/applications/dynaclr/src/dynaclr/data/datamodule.py index 1c49aaf82..7462e479b 100644 --- a/applications/dynaclr/src/dynaclr/data/datamodule.py +++ b/applications/dynaclr/src/dynaclr/data/datamodule.py @@ -190,6 +190,7 @@ def __init__( positive_match_columns: list[str] | None = None, positive_channel_source: str = "same", label_columns: dict[str, str] | None = None, + split_mode: str = "fov", max_border_shift: int = -1, shuffle_val: bool = False, pin_memory: bool = True, @@ -203,6 +204,9 @@ def __init__( self.z_window = z_window self.z_extraction_window = z_extraction_window self.z_focus_offset = z_focus_offset + if split_mode not in ("fov", "cell"): + raise ValueError(f"split_mode must be 'fov' or 'cell', got {split_mode!r}") + self.split_mode = split_mode self.yx_patch_size = yx_patch_size self.final_yx_patch_size = final_yx_patch_size self.val_experiments = val_experiments if val_experiments is not None else [] @@ -474,36 +478,44 @@ def _setup_fov_split(self, registry: ExperimentRegistry, cell_index_df: pd.DataF # (81M+ rows for OPS), which hashes a Python tuple per row and # dominates setup-time memory. Per-group isin against a small # Python-set of FOV names is O(group_size) with no object index. - train_fovs_per_exp: dict[str, set[str]] = {} - val_fovs_per_exp: dict[str, set[str]] = {} + # split_key: "fov_name" holds out whole FOVs; "cell_id" holds out cells + # within each experiment (needed when every experiment is a single FOV, + # e.g. one-position-per-store datasets). Splitting on cell_id keeps all + # rows of one cell (its channels) on the same side — no channel leakage. + split_key = "fov_name" if self.split_mode == "fov" else "cell_id" + train_keys_per_exp: dict[str, set[str]] = {} + val_keys_per_exp: dict[str, set[str]] = {} for exp_name, group in full_index.tracks.groupby("experiment"): - fovs = sorted(group["fov_name"].unique()) - n_train = max(1, int(len(fovs) * self.split_ratio)) - rng.shuffle(fovs) - train_fovs_per_exp[exp_name] = set(fovs[:n_train]) - val_fovs_per_exp[exp_name] = set(fovs[n_train:]) - - n_train_fovs = sum(len(s) for s in train_fovs_per_exp.values()) - n_val_fovs = sum(len(s) for s in val_fovs_per_exp.values()) + keys = sorted(group[split_key].unique()) + n_train = max(1, int(len(keys) * self.split_ratio)) + rng.shuffle(keys) + train_keys_per_exp[exp_name] = set(keys[:n_train]) + val_keys_per_exp[exp_name] = set(keys[n_train:]) + + n_train_keys = sum(len(s) for s in train_keys_per_exp.values()) + n_val_keys = sum(len(s) for s in val_keys_per_exp.values()) _logger.info( - "FOV split (ratio=%.2f): %d train FOVs, %d val FOVs", + "%s split (ratio=%.2f): %d train %s, %d val %s", + self.split_mode, self.split_ratio, - n_train_fovs, - n_val_fovs, + n_train_keys, + split_key, + n_val_keys, + split_key, ) def _build_train_mask(df: pd.DataFrame) -> np.ndarray: - """Row-wise boolean mask: True if (experiment, fov_name) is train.""" + """Row-wise boolean mask: True if the split key is in the train set.""" mask = np.zeros(len(df), dtype=bool) # groupby("experiment") returns integer positions in ``df`` via # group.index after reset_index; we rely on the caller passing # reset-indexed frames (which is what MultiExperimentIndex produces). for exp_name, group in df.groupby("experiment", sort=False): - train_fovs = train_fovs_per_exp.get(exp_name, set()) - if not train_fovs: + train_keys = train_keys_per_exp.get(exp_name, set()) + if not train_keys: continue - sub_mask = group["fov_name"].isin(train_fovs).to_numpy() + sub_mask = group[split_key].isin(train_keys).to_numpy() mask[group.index.to_numpy()] = sub_mask return mask diff --git a/applications/dynaclr/src/dynaclr/data/dataset.py b/applications/dynaclr/src/dynaclr/data/dataset.py index a682b744e..57036921f 100644 --- a/applications/dynaclr/src/dynaclr/data/dataset.py +++ b/applications/dynaclr/src/dynaclr/data/dataset.py @@ -360,6 +360,7 @@ def _cache_columns(df: pd.DataFrame, columns: list[str]) -> dict: "norm_std", "norm_median", "norm_iqr", + "z_focus", } if self.positive_match_columns: hot_cols.update(self.positive_match_columns) @@ -717,9 +718,13 @@ def _build_norm_meta( ------- NormMeta or None """ - # Parquet path: norm columns present and value is not NA + # Parquet fast-path: one norm_* row = one channel's stats. Only valid in + # bag-of-channels mode (one channel per sample, keyed "channel_0"). In + # all-channels / fixed mode a sample reads multiple channels but the row + # carries only its own channel's stats, so fall through to the zarr + # zattrs path below, which returns every channel's stats. norm_mean_arr = arrays.get("norm_mean") - if norm_mean_arr is not None: + if self._channel_mode == "from_index" and norm_mean_arr is not None: norm_mean = norm_mean_arr[idx] if norm_mean is not None and not (isinstance(norm_mean, float) and np.isnan(norm_mean)): tp_stats = { @@ -728,12 +733,7 @@ def _build_norm_meta( "median": torch.tensor(arrays["norm_median"][idx], dtype=torch.float32), "iqr": torch.tensor(arrays["norm_iqr"][idx], dtype=torch.float32), } - if self._channel_mode == "from_index": - return {"channel_0": {"timepoint_statistics": tp_stats}} - else: - ch_arr = arrays.get("channel_name") - ch_name = ch_arr[idx] if ch_arr is not None else "channel_0" - return {ch_name: {"timepoint_statistics": tp_stats}} + return {"channel_0": {"timepoint_statistics": tp_stats}} # Fallback: read from zarr zattrs (old parquets without norm columns) store_path = arrays["store_path"][idx] @@ -822,13 +822,36 @@ def _slice_patch( channel_names_to_read = exp.channel_names channel_indices = [exp.channel_names.index(name) for name in channel_names_to_read] - # Per-experiment z_range (scale-adjusted window size centered on z_range center) + # Z window sizing comes from the per-experiment z_range; the center is + # the per-sample focus plane. Single source of truth: the parquet + # ``z_focus`` column when populated (written by preprocess-cell-index), + # otherwise fall back to the z_range center (which the registry derived + # from zattrs focus_slice, else mid-stack). z_focus_offset sets the + # fraction of the window placed below the focus plane (0.5 = symmetric). z_start_base, z_end_base = self.index.registry.z_ranges[exp_name] z_window_size = z_end_base - z_start_base z_count = round(z_window_size * scale_z) - z_focus = (z_start_base + z_end_base) // 2 - z_start = z_focus - z_count // 2 + z_focus_arr = arrays.get("z_focus") + z_focus_val = z_focus_arr[idx] if z_focus_arr is not None else None + # Guard against NaN for both Python float and numpy float. The old + # ``isinstance(x, float)`` check missed numpy floats, crashing int(NaN) + # on parquets with unpopulated z_focus rows. ``np.isnan`` handles both. + z_focus_missing = z_focus_val is None or ( + isinstance(z_focus_val, (float, np.floating)) and np.isnan(z_focus_val) + ) + if not z_focus_missing: + z_center = int(round(float(z_focus_val))) + z_below = round(z_count * self.index.registry.z_focus_offset) + z_start = z_center - z_below + else: + z_start = (z_start_base + z_end_base) // 2 - z_count // 2 z_end = z_start + z_count + # Clamp the window inside the image so edge cells still yield a full patch. + z_total = image.shape[2] + if z_start < 0: + z_start, z_end = 0, z_count + elif z_end > z_total: + z_start, z_end = z_total - z_count, z_total patch = image.oindex[ t, [int(c) for c in channel_indices], diff --git a/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py b/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py index e49bc1c74..64c87e178 100644 --- a/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py +++ b/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py @@ -17,7 +17,14 @@ default=None, help="Channel name for focus_slice lookup (e.g. Phase3D). Default: first channel per FOV.", ) -def main(parquet_path, output, focus_channel): +@click.option( + "--focus-level", + type=click.Choice(["fov", "per_timepoint"]), + default="fov", + show_default=True, + help="focus_slice level written to the z_focus column: per-FOV mean or per-timepoint index.", +) +def main(parquet_path, output, focus_channel, focus_level): """Preprocess a cell index parquet: add normalization stats, focus slice, remove empty frames. Reads precomputed metadata from zarr zattrs and writes them as parquet @@ -27,4 +34,5 @@ def main(parquet_path, output, focus_channel): parquet_path=parquet_path, output_path=output, focus_channel=focus_channel, + focus_level=focus_level, ) diff --git a/packages/viscy-data/src/viscy_data/_typing.py b/packages/viscy-data/src/viscy_data/_typing.py index 0e6baf6cc..0ed9125a0 100644 --- a/packages/viscy-data/src/viscy_data/_typing.py +++ b/packages/viscy-data/src/viscy_data/_typing.py @@ -254,7 +254,7 @@ class TripletSample(TypedDict): "Z_shape", "Y_shape", "X_shape", - "z_focus_mean", + "z_focus", ] CELL_INDEX_NORMALIZATION_COLUMNS = [ diff --git a/packages/viscy-data/src/viscy_data/cell_index.py b/packages/viscy-data/src/viscy_data/cell_index.py index 09a72f64f..ba869067a 100644 --- a/packages/viscy-data/src/viscy_data/cell_index.py +++ b/packages/viscy-data/src/viscy_data/cell_index.py @@ -82,7 +82,7 @@ ("Z_shape", pa.int32()), ("Y_shape", pa.int32()), ("X_shape", pa.int32()), - ("z_focus_mean", pa.float32()), + ("z_focus", pa.float32()), ("norm_mean", pa.float32()), ("norm_std", pa.float32()), ("norm_median", pa.float32()), @@ -238,6 +238,7 @@ def preprocess_cell_index( parquet_path: str | Path, output_path: str | Path | None = None, focus_channel: str | None = None, + focus_level: str = "fov", ) -> None: """Add normalization stats, focus slice, and remove invalid rows. @@ -246,7 +247,8 @@ def preprocess_cell_index( - ``norm_mean``, ``norm_std``, ``norm_median``, ``norm_iqr``, ``norm_max``, ``norm_min`` — per-timepoint, per-channel statistics - - ``z_focus_mean`` — per-FOV focus plane from ``focus_slice`` + - ``z_focus`` — the focus plane the training Z window is centered on, + sourced from ``focus_slice`` at the level chosen by ``focus_level``. Drops rows where timepoint stats are missing or ``norm_max == 0.0`` (empty frames). The processed parquet is written to ``output_path``; @@ -262,12 +264,20 @@ def preprocess_cell_index( focus_channel : str | None Channel name for ``focus_slice`` lookup (e.g. ``"Phase3D"``). When ``None``, uses the first channel_name in each FOV's group. + focus_level : {'fov', 'per_timepoint'} + Which ``focus_slice`` level feeds the ``z_focus`` column: + ``'fov'`` uses the per-FOV ``fov_statistics.z_focus_mean``; + ``'per_timepoint'`` uses the per-timepoint focus index for each + sample's ``t``. The per-cell tracked ``z`` column is left unchanged. Raises ------ ValueError - If a FOV has no normalization metadata (run ``viscy preprocess`` first). + If a FOV has no normalization metadata (run ``viscy preprocess`` first), + or if ``focus_level`` is not one of the accepted values. """ + if focus_level not in ("fov", "per_timepoint"): + raise ValueError(f"focus_level must be 'fov' or 'per_timepoint', got {focus_level!r}") if output_path is None: output_path = parquet_path @@ -315,8 +325,9 @@ def preprocess_cell_index( t_arr = df["t"].astype(int).to_numpy() norm_arrays = {stat: np.full(len(df), float("nan"), dtype=np.float32) for stat in stat_keys} + # z_focus is the plane the training Z window centers on. The per-cell + # tracked ``z`` column is left untouched — it is a distinct quantity. focus_arr = np.full(len(df), float("nan"), dtype=np.float32) - z_arr = df["z"].to_numpy(dtype=np.int16).copy() valid_mask = np.ones(len(df), dtype=bool) for i in range(len(df)): @@ -327,17 +338,18 @@ def preprocess_cell_index( for stat in stat_keys: norm_arrays[stat][i] = float(tp_stats[stat]) fov_key = (store_arr[i], fov_arr[i]) - z_focus = focus_lookup.get(fov_key) - if z_focus is not None: - focus_arr[i] = z_focus - z_t = focus_per_t_lookup.get(fov_key, {}).get(t_arr[i]) - if z_t is not None: - z_arr[i] = z_t + if focus_level == "per_timepoint": + z_t = focus_per_t_lookup.get(fov_key, {}).get(t_arr[i]) + if z_t is not None: + focus_arr[i] = z_t + else: # "fov" + z_focus = focus_lookup.get(fov_key) + if z_focus is not None: + focus_arr[i] = z_focus for stat in stat_keys: df[f"norm_{stat}"] = norm_arrays[stat] - df["z_focus_mean"] = focus_arr - df["z"] = z_arr + df["z_focus"] = focus_arr df = df[valid_mask].reset_index(drop=True) n_dropped = n_before - len(df) From 576c124bd992a5023f46211184e83d2919bf0075 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:13:27 -0700 Subject: [PATCH 47/89] config(dynaclr): add Zuben gut preparation and training recipes --- .../collections/zuben_gut/conform_parquet.py | 64 ++++++++ .../collections/zuben_gut/gen_convert_v3.py | 92 +++++++++++ .../collections/zuben_gut/gen_preprocess.py | 56 +++++++ .../zuben_gut/verify_parquet_zarr.py | 80 ++++++++++ .../DynaCLR-3D-Gut-BagOfChannels.sh | 47 ++++++ .../DynaCLR-3D-Gut-BagOfChannels.yml | 151 ++++++++++++++++++ .../DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh | 43 +++++ .../DynaCLR-3D-Gut-MultiChannel.yml | 147 +++++++++++++++++ 8 files changed, 680 insertions(+) create mode 100644 applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py create mode 100644 applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py create mode 100644 applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py create mode 100644 applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py create mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh create mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml create mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh create mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml diff --git a/applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py b/applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py new file mode 100644 index 000000000..190421970 --- /dev/null +++ b/applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py @@ -0,0 +1,64 @@ +"""Step 2: conform Zuben's cell-index parquet to the canonical schema (end-to-end). + +Single authoritative step, run after the v3 stores are preprocessed (Step 1): + +1. Repoint ``store_path`` from the v2 originals to the v3 copies (same basename). +2. Fill required/derived columns absent from the source + (``tracks_path``, ``microscope``, ``T_shape``, ``C_shape``). +3. ``preprocess_cell_index`` fills the ``norm_*`` columns from the v3 ``.zattrs``. +4. Seed ``z_focus = z`` LAST — gut has no ``focus_slice`` zattrs, so the bbox-center + ``z`` IS the focus. This must come after step 3 because ``preprocess_cell_index`` + also writes ``z_focus`` (as NaN here, since there is no focus_slice) and would + otherwise clobber it. The datamodule centers the Z window on ``z_focus``. + +Run:: + + uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py +""" + +import os + +import pandas as pd + +from viscy_data.cell_index import preprocess_cell_index, read_cell_index, write_cell_index + +SRC = "/hpc/projects/jacobo_group/zuben/proj/gutCellClassifier/data/dynaclr_cell_index_bbox_center.parquet" +V3_ROOT = "/hpc/projects/organelle_phenotyping/datasets/zuben_gut_development" +OUT = "/hpc/projects/jacobo_group/collab/ed/dynaclr/dynaclr_cell_index_gut_v1.parquet" +N_CHANNELS = 4 + + +def main() -> None: + """Conform the parquet, fill norm stats, and seed z_focus from the bbox-center z.""" + df = pd.read_parquet(SRC) + n_rows_in = len(df) + + # Repoint store_path to the v3 copies (same basename). + df["store_path"] = df["store_path"].astype(str).map(lambda p: f"{V3_ROOT}/{os.path.basename(p)}") + + # Required columns absent from the source parquet. + df["tracks_path"] = "" # ignored by ExperimentRegistry.from_cell_index + df["microscope"] = "" + df["T_shape"] = 1 # static: single timepoint + df["C_shape"] = N_CHANNELS + + write_cell_index(df, OUT) + + # Fill norm_* from the v3 .zattrs (writes z_focus as NaN — no focus_slice). + preprocess_cell_index(OUT, focus_channel="nuclear") + + # Seed z_focus = z LAST so it survives preprocess_cell_index. + out_df = read_cell_index(OUT) + out_df["z_focus"] = out_df["z"].astype("float32") + write_cell_index(out_df, OUT) + + check = read_cell_index(OUT) + print("# Step 2 — conform parquet\n") + print(f"- rows in: **{n_rows_in}**, rows out: **{len(check)}**") + print(f"- output: `{OUT}`") + print(f"- norm_mean NaN: **{int(check['norm_mean'].isna().sum())}**") + print(f"- z_focus NaN: **{int(check['z_focus'].isna().sum())}** (should be 0)") + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py b/applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py new file mode 100644 index 000000000..9a3c34c09 --- /dev/null +++ b/applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py @@ -0,0 +1,92 @@ +"""Generate per-store biahub-concatenate configs to copy Zuben's 25 v2 gut stores to v3. + +Each of Zuben's stores is one gut = one experiment = a single position ``A/1/0`` with the +four channels ``[nuclear, septate, brush_border, SuH]``. We convert each store to its own +v3 store under ``OUTPUT_ROOT`` preserving the original basename and the ``A/1/0`` layout, so +the cell-index parquet's ``(store_path, well, fov)`` mapping stays valid — only the +``store_path`` directory changes. + +Emits, per store, ``configs/.yml`` and a single ``submit_all.sh`` that runs +``biahub concatenate`` (conda env ``biautils``) for every store as SLURM jobs. + +Run:: + + uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py + bash /convert_v3/submit_all.sh +""" + +import os +from pathlib import Path + +import pandas as pd + +PARQUET = "/hpc/projects/jacobo_group/zuben/proj/gutCellClassifier/data/dynaclr_cell_index_bbox_center.parquet" +OUTPUT_ROOT = "/hpc/projects/organelle_phenotyping/datasets/zuben_gut_development" +CHANNELS = ["nuclear", "septate", "brush_border", "SuH"] +CHUNKS_CZYX = [1, 16, 256, 256] +SHARDS_RATIO = [1, 1, 4, 8, 8] +OME_VERSION = "0.5" +BIAHUB_ENV = "biautils" + +HERE = Path(__file__).resolve().parent +WORKDIR = HERE / "convert_v3" +CONFIGS_DIR = WORKDIR / "configs" + + +def _yaml_config(store_path: str) -> str: + import yaml + + cfg = { + # One input FOV glob per data path: this store's single position A/1/0. + "concat_data_paths": [f"{store_path}/A/1/*"], + "time_indices": "all", + # List-of-lists: one channel list per data path (here, one path). + "channel_names": [list(CHANNELS)], + "X_slice": "all", + "Y_slice": "all", + "Z_slice": "all", + "chunks_czyx": list(CHUNKS_CZYX), + "shards_ratio": list(SHARDS_RATIO), + "output_ome_zarr_version": OME_VERSION, + } + return yaml.safe_dump(cfg, default_flow_style=False, sort_keys=False) + + +def main() -> None: + """Emit one biahub-concatenate config per store plus a SLURM submit driver.""" + df = pd.read_parquet(PARQUET, columns=["store_path"]) + stores = sorted(df["store_path"].astype(str).unique()) + + CONFIGS_DIR.mkdir(parents=True, exist_ok=True) + submit_lines = [ + "#!/bin/bash", + "set -euo pipefail", + "# Auto-generated by gen_convert_v3.py. Submits one biahub concatenate per store.", + f"mkdir -p {OUTPUT_ROOT}", + "", + ] + + for store in stores: + stem = os.path.basename(store) # e.g. AAY6_sox21a_d0_63x_gut1.zarr + cfg_path = CONFIGS_DIR / f"{stem.replace('.zarr', '')}.yml" + cfg_path.write_text(_yaml_config(store)) + out_path = f"{OUTPUT_ROOT}/{stem}" + submit_lines.append(f'echo "=== {stem} ==="') + submit_lines.append( + f"conda run -n {BIAHUB_ENV} biahub concatenate " + f'-c "{cfg_path}" -o "{out_path}" -m -sb "{WORKDIR / "sbatch_overrides.sh"}"' + ) + submit_lines.append("") + + (WORKDIR / "submit_all.sh").write_text("\n".join(submit_lines) + "\n") + (WORKDIR / "sbatch_overrides.sh").write_text( + "#!/bin/bash\n#SBATCH --partition=cpu\n#SBATCH --cpus-per-task=4\n#SBATCH --mem-per-cpu=32G\n" + ) + + print(f"Generated {len(stores)} configs under {CONFIGS_DIR}") + print(f"Submit driver: {WORKDIR / 'submit_all.sh'}") + print(f"Output root: {OUTPUT_ROOT}") + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py b/applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py new file mode 100644 index 000000000..cb4ee68be --- /dev/null +++ b/applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py @@ -0,0 +1,56 @@ +"""Generate + submit `viscy preprocess` SLURM jobs for the 25 v3 gut stores. + +Writes per-channel normalization stats (fov/dataset/timepoint) into each store's +``.zattrs``. One SLURM job per store (each store is a single large FOV). + +Run:: + + uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py + bash /preprocess/submit_all.sh +""" + +import glob +from pathlib import Path + +V3_ROOT = "/hpc/projects/organelle_phenotyping/datasets/zuben_gut_development" +REPO = "/hpc/mydata/eduardo.hirata/repos/viscy" +VENV = f"{REPO}/.venv-dynaclr" +HERE = Path(__file__).resolve().parent +WORKDIR = HERE / "preprocess" + +JOB_TEMPLATE = """#!/bin/bash +#SBATCH --job-name=pp_{stem} +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --partition=cpu +#SBATCH --cpus-per-task=32 +#SBATCH --mem-per-cpu=4G +#SBATCH --time=02:00:00 +#SBATCH --output={workdir}/slurm_pp_{stem}_%j.out + +export PYTHONNOUSERSITE=1 +export UV_PROJECT_ENVIRONMENT={venv} + +uv run --project "{repo}" --package dynaclr \\ + viscy preprocess --data_path "{store}" \\ + --channel_names=-1 --num_workers 32 --block_size 32 +""" + + +def main() -> None: + """Emit one `viscy preprocess` SLURM job per v3 store plus a submit driver.""" + WORKDIR.mkdir(parents=True, exist_ok=True) + stores = sorted(glob.glob(f"{V3_ROOT}/*.zarr")) + submit = ["#!/bin/bash", "set -euo pipefail", ""] + for store in stores: + stem = Path(store).name.replace(".zarr", "") + job = WORKDIR / f"pp_{stem}.sh" + job.write_text(JOB_TEMPLATE.format(stem=stem, workdir=WORKDIR, venv=VENV, repo=REPO, store=store)) + submit.append(f"sbatch {job}") + (WORKDIR / "submit_all.sh").write_text("\n".join(submit) + "\n") + print(f"Generated {len(stores)} preprocess jobs under {WORKDIR}") + print(f"Submit: bash {WORKDIR / 'submit_all.sh'}") + + +if __name__ == "__main__": + main() diff --git a/applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py b/applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py new file mode 100644 index 000000000..9b16514fe --- /dev/null +++ b/applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py @@ -0,0 +1,80 @@ +"""Step 0: verify Zuben's cell-index parquet is consistent with its zarr stores. + +Checks, for every store referenced in the parquet: +- the store opens and exposes the expected channel names, +- each (well, fov) referenced resolves to a real position, +- cell centroids fit inside the FOV with room for the requested patch half-width. + +Run:: + + uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py +""" + +import sys + +import pandas as pd +from iohub import open_ome_zarr + +PARQUET = "/hpc/projects/jacobo_group/zuben/proj/gutCellClassifier/data/dynaclr_cell_index_bbox_center.parquet" +EXPECTED_CHANNELS = ["nuclear", "septate", "brush_border", "SuH"] +YX_PATCH_SIZE = (256, 256) # extraction patch used by the bag-of-channels config + + +def main() -> int: + """Check every store opens, channels match, positions resolve, and report border-OOB cells.""" + df = pd.read_parquet(PARQUET) + y_half = YX_PATCH_SIZE[0] // 2 + x_half = YX_PATCH_SIZE[1] // 2 + + problems: list[str] = [] + n_cells_out_of_bounds = 0 + + for store_path, store_group in df.groupby("store_path", observed=True): + store_path = str(store_path) + try: + with open_ome_zarr(store_path, mode="r") as plate: + channels = list(plate.channel_names) + if channels != EXPECTED_CHANNELS: + problems.append(f"{store_path}: channels {channels} != {EXPECTED_CHANNELS}") + positions = {name for name, _ in plate.positions()} + except Exception as exc: # noqa: BLE001 - surface any open failure + problems.append(f"{store_path}: failed to open ({exc})") + continue + + for (well, fov), fov_group in store_group.groupby(["well", "fov"], observed=True): + pos_key = f"{well}/{fov}" + if pos_key not in positions: + problems.append(f"{store_path}: position {pos_key} not found") + continue + y_shape = int(fov_group["Y_shape"].iloc[0]) + x_shape = int(fov_group["X_shape"].iloc[0]) + y = fov_group["y"].to_numpy() + x = fov_group["x"].to_numpy() + oob = (y < y_half) | (y > y_shape - y_half) | (x < x_half) | (x > x_shape - x_half) + n_cells_out_of_bounds += int(oob.sum()) + + n_stores = df["store_path"].nunique() + n_cells = df["cell_id"].nunique() + + print("# Step 0 — parquet↔zarr consistency\n") + print(f"- stores checked: **{n_stores}**") + print(f"- unique cells: **{n_cells}**") + print(f"- expected channels: `{EXPECTED_CHANNELS}`") + print( + f"- cells whose {YX_PATCH_SIZE} patch would fall out of bounds: " + f"**{n_cells_out_of_bounds}** " + f"({100 * n_cells_out_of_bounds / len(df):.1f}% of rows)" + ) + + if problems: + print(f"\n**{len(problems)} problem(s):**") + for p in problems[:50]: + print(f" - {p}") + return 1 + + print("\n**OK** — all stores open, channels match, positions resolve.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh new file mode 100644 index 000000000..c3617fb11 --- /dev/null +++ b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# DynaCLR-3D-Gut-BagOfChannels (Phase 1) — Zuben gut cells, bag-of-channels SimCLR. +# +# New run: +# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh +# Resume: +# CKPT_PATH=.../last.ckpt WANDB_RUN_ID= sbatch .../DynaCLR-3D-Gut-BagOfChannels.sh + +#SBATCH --job-name=dynaclr_gut_boc +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --partition=gpu +#SBATCH --cpus-per-task=15 +#SBATCH --mem-per-cpu=8G +#SBATCH --time=2-00:00:00 + +# Set WORKSPACE_DIR to YOUR clone of the repo before submitting, e.g. +# WORKSPACE_DIR=/hpc/mydata//repos/VisCy sbatch .sh +# MODEL_ROOT is where checkpoints/configs are written (defaults to your clone's +# models/ dir; override to a shared project path if desired). +WORKSPACE_DIR="${WORKSPACE_DIR:?Set WORKSPACE_DIR to your repo clone path}" + +export PROJECT="DynaCLR-3D-Gut-BagOfChannels" +export RUN_NAME="gut-3d-z24-cellz-64-ntxent-t0p2-self" +export CONFIGS="applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml" +export MODEL_ROOT="${MODEL_ROOT:-${WORKSPACE_DIR}/models}" +# Point at the dynaclr-pinned venv in your clone (avoids the shared .venv sync race). +export UV_PROJECT_ENVIRONMENT="${UV_PROJECT_ENVIRONMENT:-${WORKSPACE_DIR}/.venv-dynaclr}" + +# W&B writes sample images to a temp dir under $TMPDIR before upload. On some +# SLURM nodes the default /tmp is per-job and gets swept mid-run, causing +# `FileNotFoundError: .../wandb-media/*.png` at validation image logging. Pin +# TMPDIR + WANDB_DIR to persistent paths we create so they can't disappear. +export TMPDIR="${TMPDIR:-${MODEL_ROOT}/tmp}" +export WANDB_DIR="${WANDB_DIR:-${MODEL_ROOT}/wandb}" +mkdir -p "$TMPDIR" "$WANDB_DIR" + +# The shared trainer recipe logs to the `computational_imaging` W&B entity. Set +# WANDB_ENTITY to your own entity to log there instead (EXTRA_ARGS overrides the +# recipe). Leave unset to keep the default. +if [ -n "${WANDB_ENTITY:-}" ]; then + export EXTRA_ARGS="${EXTRA_ARGS:-} --trainer.logger.init_args.entity=${WANDB_ENTITY}" +fi + +# Absolute path (SLURM spools this script, so $(dirname "$0") would break). +source "${WORKSPACE_DIR}/applications/dynaclr/configs/training/slurm/train.sh" diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml new file mode 100644 index 000000000..b019e8cd7 --- /dev/null +++ b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml @@ -0,0 +1,151 @@ +# DynaCLR-3D-Gut-BagOfChannels (Phase 1) +# ======================================= +# 3D bag-of-channels contrastive learning on Zuben's zebrafish gut data. +# One random channel per sample (nuclear | septate | brush_border | SuH), +# 24-slice Z window, 64x64 XY — matches Zuben's 24x64x64 patch convention. +# +# STATIC DATA: single timepoint (t=0), no tracking. SimCLR self-positives +# (same crop → same marker). One marker per batch (batch_group_by=marker) so the +# model can't shortcut on channel identity; batches balanced across the 6 states +# (stratify_by=perturbation) to counter the ~3.5x state imbalance. +# (Phase 3, later: consecutive-state positives to regularize the space temporally.) +# +# Normalization reads per-FOV full-frame stats from the v3 .zattrs written by +# `viscy preprocess`. Single timepoint → timepoint_statistics == fov_statistics. +# +# Z window centered on the parquet `z_focus` column (seeded from bbox-center z +# by conform_parquet.py), 24 slices, no random Z crop. XY extracted at 80 then +# center-cropped to 64. +# Pipeline: extract (24,80,80) → normalize → affine → flip/contrast/noise +# → CenterCrop (24,64,64) [auto-appended]. +# +# Launch: +# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh + +base: + - ../recipes/trainer/fit.yml + - ../recipes/topology/ddp_2gpu.yml + - ../recipes/model/contrastive_encoder_convnext_tiny.yml + +trainer: + precision: bf16-mixed + max_epochs: 150 + logger: + init_args: + project: DynaCLR-3D-Gut-BagOfChannels + name: gut-3d-z24-96to80to64-ntxent-t0p2-self + # Re-list callbacks WITHOUT OnlineEvalCallback (static data → degenerate + # temporal kNN eval). Loss/val + post-hoc PCA/UMAP colored by stage/marker + # is the evaluation signal for Phase 1. + callbacks: + - class_path: lightning.pytorch.callbacks.LearningRateMonitor + init_args: + logging_interval: step + - class_path: lightning.pytorch.callbacks.ModelCheckpoint + init_args: + monitor: loss/val + every_n_epochs: 1 + save_top_k: 5 + save_last: true + +model: + init_args: + encoder: + init_args: + in_stack_depth: 24 + stem_kernel_size: [4, 4, 4] + stem_stride: [4, 4, 4] + projection_dim: 32 + drop_path_rate: 0.1 + loss_function: + init_args: + temperature: 0.2 + lr: 0.00002 + pca_color_keys: "[perturbation,experiment,marker]" + # PCA pairplot cadence (overrides the base recipe's 10). Watch the 6 states + # separate early. + log_embeddings_every_n_epochs: 5 + log_negative_metrics_every_n_epochs: 2 + example_input_array_shape: [1, 1, 24, 64, 64] + +data: + class_path: dynaclr.data.datamodule.MultiExperimentDataModule + init_args: + cell_index_path: /hpc/projects/jacobo_group/collab/ed/dynaclr/dynaclr_cell_index_gut_v1.parquet + focus_channel: null + # The datamodule centers the Z window on the parquet `z_focus` column; + # conform_parquet.py seeds z_focus = bbox-center z (gut cells span the full + # stack within one store, so per-FOV focus won't do). 24-slice window, + # symmetric (offset 0.5), no Z rescale (extraction == window), no random crop. + z_window: 24 + z_extraction_window: 24 + z_focus_offset: 0.5 + # Small XY margin (80 → 64) so the auto-appended CenterCrop trims affine + # rotation zero-fill; no explicit random XY crop. + yx_patch_size: [80, 80] + final_yx_patch_size: [64, 64] + channels_per_sample: 1 + # SimCLR self-positive: anchor == positive (same crop, two augmentations) → + # trivially the SAME marker, and no supervised pull that would fight the + # negatives. State structure emerges unsupervised. + positive_cell_source: self + # One marker per batch: all negatives share the anchor's marker, so the model + # can't take the "tell channels apart" shortcut and must learn within-marker + # (cell-state) structure. + batch_group_by: marker + # Within the single-marker batch, balance the 6 developmental states — the + # dataset is ~3.5x imbalanced (stage_0 1379 vs stage_5 390 per marker). + stratify_by: [perturbation] + # Each gut store is a single FOV → split cells within each gut (not by FOV, + # which would leave 0 val FOVs). All rows of one cell stay on one side. + split_mode: cell + split_ratio: 0.8 + batch_size: 256 + num_workers: 4 + seed: 42 + normalizations: + - class_path: viscy_transforms.NormalizeSampled + init_args: + keys: [channel_0] + level: timepoint_statistics + subtrahend: mean + divisor: std + augmentations: + - class_path: viscy_transforms.BatchedRandAffined + init_args: + keys: [channel_0] + prob: 0.8 + scale_range: [[0.9, 1.1], [0.9, 1.1], [0.9, 1.1]] + rotate_range: [3.14, 0.0, 0.0] + shear_range: [0.05, 0.05, 0.0, 0.05, 0.0, 0.05] + # No random spatial crop — the Z window is already centered on the cell + # plane and XY is tight. The datamodule auto-appends a CenterCrop to + # [24, 64, 64], which also trims affine rotation zero-fill at the edges. + - class_path: viscy_transforms.BatchedRandFlipd + init_args: + keys: [channel_0] + spatial_axes: [1, 2] + prob: 0.5 + - class_path: viscy_transforms.BatchedRandAdjustContrastd + init_args: + keys: [channel_0] + prob: 0.5 + gamma: [0.6, 1.6] + - class_path: viscy_transforms.BatchedRandScaleIntensityd + init_args: + keys: [channel_0] + prob: 0.5 + factors: 0.5 + - class_path: viscy_transforms.BatchedRandGaussianSmoothd + init_args: + keys: [channel_0] + prob: 0.5 + sigma_x: [0.25, 0.50] + sigma_y: [0.25, 0.50] + sigma_z: [0.0, 0.2] + - class_path: viscy_transforms.BatchedRandGaussianNoised + init_args: + keys: [channel_0] + prob: 0.5 + mean: 0.0 + std: 0.1 diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh new file mode 100644 index 000000000..d23dd4b45 --- /dev/null +++ b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# DynaCLR-3D-Gut-MultiChannel (Phase 2) — Zuben gut cells, 4-channel input. +# +# New run: +# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh +# Resume: +# CKPT_PATH=.../last.ckpt WANDB_RUN_ID= sbatch .../DynaCLR-3D-Gut-MultiChannel.sh + +#SBATCH --job-name=dynaclr_gut_4ch +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --partition=gpu +#SBATCH --cpus-per-task=15 +#SBATCH --mem-per-cpu=8G +#SBATCH --time=2-00:00:00 + +# Set WORKSPACE_DIR to YOUR clone of the repo before submitting, e.g. +# WORKSPACE_DIR=/hpc/mydata//repos/VisCy sbatch .sh +WORKSPACE_DIR="${WORKSPACE_DIR:?Set WORKSPACE_DIR to your repo clone path}" + +export PROJECT="DynaCLR-3D-Gut-MultiChannel" +export RUN_NAME="gut-3d-4ch-z24-cellz-64-ntxent-t0p2-self" +export CONFIGS="applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml" +export MODEL_ROOT="${MODEL_ROOT:-${WORKSPACE_DIR}/models}" +export UV_PROJECT_ENVIRONMENT="${UV_PROJECT_ENVIRONMENT:-${WORKSPACE_DIR}/.venv-dynaclr}" + +# W&B writes sample images to a temp dir under $TMPDIR before upload. On some +# SLURM nodes the default /tmp is per-job and gets swept mid-run, causing +# `FileNotFoundError: .../wandb-media/*.png` at validation image logging. Pin +# TMPDIR + WANDB_DIR to persistent paths we create so they can't disappear. +export TMPDIR="${TMPDIR:-${MODEL_ROOT}/tmp}" +export WANDB_DIR="${WANDB_DIR:-${MODEL_ROOT}/wandb}" +mkdir -p "$TMPDIR" "$WANDB_DIR" + +# The shared trainer recipe logs to the `computational_imaging` W&B entity. Set +# WANDB_ENTITY to your own entity to log there instead. Leave unset for default. +if [ -n "${WANDB_ENTITY:-}" ]; then + export EXTRA_ARGS="${EXTRA_ARGS:-} --trainer.logger.init_args.entity=${WANDB_ENTITY}" +fi + +# Absolute path (SLURM spools this script, so $(dirname "$0") would break). +source "${WORKSPACE_DIR}/applications/dynaclr/configs/training/slurm/train.sh" diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml new file mode 100644 index 000000000..269d8628b --- /dev/null +++ b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml @@ -0,0 +1,147 @@ +# DynaCLR-3D-Gut-MultiChannel (Phase 2) +# ====================================== +# 4-channel contrastive learning on Zuben's zebrafish gut data. +# All channels stacked as input (nuclear, septate, brush_border, SuH) → +# (B, 4, 24, 64, 64). Per-channel normalization: each channel normalized with +# its own per-FOV full-frame stats from the v3 .zattrs. +# +# STATIC DATA: SimCLR self-positives; batches balanced across the 6 states +# (stratify_by=perturbation) to counter the ~3.5x state imbalance. +# Switch to consecutive-state positives later (Phase 3) to regularize the space +# temporally — needs a stage-adjacency bucket or code change (see plan). +# +# Z window centered on the parquet `z_focus` column (seeded from bbox-center z +# by conform_parquet.py), 24 slices, no random Z crop. XY 80 → CenterCrop 64. +# +# Launch: +# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh + +base: + - ../recipes/trainer/fit.yml + - ../recipes/topology/ddp_2gpu.yml + - ../recipes/model/contrastive_encoder_convnext_tiny.yml + +trainer: + precision: bf16-mixed + max_epochs: 150 + logger: + init_args: + project: DynaCLR-3D-Gut-MultiChannel + name: gut-3d-4ch-z24-cellz-64-ntxent-t0p2-self + callbacks: + - class_path: lightning.pytorch.callbacks.LearningRateMonitor + init_args: + logging_interval: step + - class_path: lightning.pytorch.callbacks.ModelCheckpoint + init_args: + monitor: loss/val + every_n_epochs: 1 + save_top_k: 5 + save_last: true + +model: + init_args: + encoder: + init_args: + in_channels: 4 + in_stack_depth: 24 + stem_kernel_size: [4, 4, 4] + stem_stride: [4, 4, 4] + projection_dim: 32 + drop_path_rate: 0.1 + loss_function: + init_args: + temperature: 0.2 + lr: 0.00002 + pca_color_keys: "[perturbation,experiment,marker]" + # PCA pairplot cadence (overrides the base recipe's 10). + log_embeddings_every_n_epochs: 5 + log_negative_metrics_every_n_epochs: 2 + example_input_array_shape: [1, 4, 24, 64, 64] + +data: + class_path: dynaclr.data.datamodule.MultiExperimentDataModule + init_args: + cell_index_path: /hpc/projects/jacobo_group/collab/ed/dynaclr/dynaclr_cell_index_gut_v1.parquet + focus_channel: null + # Z window centers on the parquet z_focus column (seeded from bbox-center z). + z_window: 24 + z_extraction_window: 24 + z_focus_offset: 0.5 + yx_patch_size: [80, 80] + final_yx_patch_size: [64, 64] + # All 4 channels stacked per sample → (B, 4, Z, Y, X). No marker shortcut + # here (every sample carries all channels), so no batch_group_by needed. + channels_per_sample: null + # SimCLR self-positive (same crop, two augmentations); state structure emerges + # unsupervised. Batches balanced across the 6 states to counter imbalance. + positive_cell_source: self + stratify_by: [perturbation] + split_mode: cell + split_ratio: 0.8 + batch_size: 64 + num_workers: 4 + seed: 42 + # Per-channel normalization: each channel keyed by its real name, all at + # fov_statistics (== timepoint_statistics for single-timepoint data). + normalizations: + - class_path: viscy_transforms.NormalizeSampled + init_args: + keys: [nuclear] + level: fov_statistics + subtrahend: mean + divisor: std + - class_path: viscy_transforms.NormalizeSampled + init_args: + keys: [septate] + level: fov_statistics + subtrahend: mean + divisor: std + - class_path: viscy_transforms.NormalizeSampled + init_args: + keys: [brush_border] + level: fov_statistics + subtrahend: mean + divisor: std + - class_path: viscy_transforms.NormalizeSampled + init_args: + keys: [SuH] + level: fov_statistics + subtrahend: mean + divisor: std + augmentations: + - class_path: viscy_transforms.BatchedRandAffined + init_args: + keys: [nuclear, septate, brush_border, SuH] + prob: 0.8 + scale_range: [[0.9, 1.1], [0.9, 1.1], [0.9, 1.1]] + rotate_range: [3.14, 0.0, 0.0] + shear_range: [0.05, 0.05, 0.0, 0.05, 0.0, 0.05] + - class_path: viscy_transforms.BatchedRandFlipd + init_args: + keys: [nuclear, septate, brush_border, SuH] + spatial_axes: [1, 2] + prob: 0.5 + - class_path: viscy_transforms.BatchedRandAdjustContrastd + init_args: + keys: [nuclear, septate, brush_border, SuH] + prob: 0.5 + gamma: [0.6, 1.6] + - class_path: viscy_transforms.BatchedRandScaleIntensityd + init_args: + keys: [nuclear, septate, brush_border, SuH] + prob: 0.5 + factors: 0.5 + - class_path: viscy_transforms.BatchedRandGaussianSmoothd + init_args: + keys: [nuclear, septate, brush_border, SuH] + prob: 0.5 + sigma_x: [0.25, 0.50] + sigma_y: [0.25, 0.50] + sigma_z: [0.0, 0.2] + - class_path: viscy_transforms.BatchedRandGaussianNoised + init_args: + keys: [nuclear, septate, brush_border, SuH] + prob: 0.5 + mean: 0.0 + std: 0.1 From 8192d75488d5f53a6fbe4726d9bb754798e50f2b Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:17:10 -0700 Subject: [PATCH 48/89] docs(dynaclr): document run-once eval-many pipeline --- applications/dynaclr/README.md | 21 ++ .../dynaclr/docs/DAGs/ai_ready_datasets.md | 3 + applications/dynaclr/docs/DAGs/end_to_end.md | 184 ++++++++++++++++++ .../dynaclr/docs/DAGs/inference_triplet.md | 145 ++++++++++---- applications/dynaclr/nextflow/README.md | 78 +++++++- .../dynaclr/src/dynaclr/evaluation/README.md | 4 + 6 files changed, 384 insertions(+), 51 deletions(-) create mode 100644 applications/dynaclr/docs/DAGs/end_to_end.md diff --git a/applications/dynaclr/README.md b/applications/dynaclr/README.md index ae362a5bb..d4edf419d 100644 --- a/applications/dynaclr/README.md +++ b/applications/dynaclr/README.md @@ -48,6 +48,18 @@ uv run --package dynaclr viscy predict -c examples/configs/predict.yml sbatch examples/configs/fit_slurm.sh ``` +### End-to-end pipeline (new dataset → embeddings → eval) + +For the full "dataset → per-marker embeddings → downstream evals" workflow — including the +one-call `dynaclr predict-triplet` (writes the dataset-centric tree +`/2-phenotyping/predictions/{model}/{run}/{ckpt}/{marker}.zarr`), the decoupled +Nextflow `eval_from_embeddings` entry, and the **model matrix** for running many models through +train → predict → eval in parallel — see: + +- [`docs/DAGs/end_to_end.md`](docs/DAGs/end_to_end.md) — the pipeline overview + Quickstart runbook +- [`nextflow/README.md`](nextflow/README.md) — the two Nextflow eval entries +- [`tools/README.md`](tools/README.md) — the model matrix (`dynaclr run-matrix`) + launchers + The YAML config determines which model and data module to use via `class_path`: ```yaml @@ -78,12 +90,21 @@ DynaCLR also provides evaluation-specific commands via `dynaclr `: | Command | Description | |---------|-------------| +| `predict-triplet` | One-call per-marker embedding inference from a collection + checkpoint → dataset-centric tree | +| `predict-batch` | Run `predict-triplet` for one model with AI-readiness preflight | +| `eval` | Evaluate frozen embeddings selected by model/run/checkpoint | +| `run-matrix` | Chain train → predict → eval for many models through SLURM dependencies | +| `split-embeddings` | Split a combined embeddings zarr per experiment/marker (`--route-by-dataset` → the dataset tree) | +| `embedding-consistency-qc` | Build per-marker cross-dataset MMD² and correlation matrices | +| `run-linear-classifiers` | Train linear classifiers on embeddings (batch, CSV metrics) | +| `append-annotations` / `append-predictions` | Join annotation / predicted-label columns onto per-experiment zarrs | | `train-linear-classifier` | Train a linear classifier on cell embeddings | | `apply-linear-classifier` | Apply a trained linear classifier to new embeddings | | `append-obs` | Append columns from a CSV to an AnnData zarr obs (with optional prefix, e.g. `annotated_`, `feature_`) | | `reduce-dimensionality` | Compute PCA, UMAP, and/or PHATE on saved embeddings | | `evaluate-smoothness` | Evaluate temporal smoothness of embedding models | | `compare-models` | Compare previously saved smoothness results | +| `compute-mmd` | MMD between perturbation / experiment groups (cross-experiment consistency) | | `info` | Print summary of an AnnData zarr store | ```bash diff --git a/applications/dynaclr/docs/DAGs/ai_ready_datasets.md b/applications/dynaclr/docs/DAGs/ai_ready_datasets.md index 8e000769f..ceab6b769 100644 --- a/applications/dynaclr/docs/DAGs/ai_ready_datasets.md +++ b/applications/dynaclr/docs/DAGs/ai_ready_datasets.md @@ -1,5 +1,8 @@ # Data Preparation DAG +This is stage ① of the full pipeline. For how it connects to parquet build, +predict, and evaluation, see [end_to_end.md](end_to_end.md). + ## Entry point `prepare run -c prepare_config.yaml` (from `airtable_utils`) discovers wells and diff --git a/applications/dynaclr/docs/DAGs/end_to_end.md b/applications/dynaclr/docs/DAGs/end_to_end.md new file mode 100644 index 000000000..64ffa0225 --- /dev/null +++ b/applications/dynaclr/docs/DAGs/end_to_end.md @@ -0,0 +1,184 @@ +# End-to-End DAG (new dataset → prediction per-marker embeddings → downstream tasks) + +## Summary: + +This document details the methods to go from zarr datasets -> predictions ready for downstream tasks. + +These are the two methods: + +- **Primary spine:** the **Airtable →** `dynaclr predict-triplet` per-marker route — a +single command takes a git-tracked collection + a checkpoint and writes one embeddings +zarr per (experiment, marker) into a provenance-scoped tree. Use this to get a new +dataset to embeddings fast. +- **Alternate spine:** the **parquet-first** route (`build-cell-index` → `viscy predict` +→ `split-embeddings`) for large, reproducible multi-experiment runs — see +[evaluation.md](evaluation.md). It produces per-marker embeddings too (as +`{experiment}_{marker}.zarr` via `split-embeddings --prefix-by experiment`); the primary +spine writes them into the dataset-centric tree described below. + +## Quickstart — new dataset → embeddings → eval + +Run inference **once** per dataset (GPU), then evaluate the frozen embeddings **any number +of times** (CPU). Worked example: `2026_04_14_A549_SEC61B_DENV`. + +**1. Build the collection** (Airtable → git-tracked recipe). One-time per dataset. + +```bash +# skill: airtable-build-collection → applications/dynaclr/configs/collections/<...>/.yml +``` + +**2. Predict embeddings — all markers, into the dataset's own tree.** Once per dataset (or a +new checkpoint). `--datasets-root` selects the base; the dataset folder + `{marker}.zarr` +filename are derived automatically, so multiple markers co-locate. + +```bash +dynaclr predict-triplet \ + -c applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml \ + --checkpoint /hpc/projects/organelle_phenotyping/models/.../epoch=105-step=84800.ckpt \ + --model-family DynaCLR-2D-MIP-BagOfChannels \ + --run 2d-mip-fix-shuffler \ + --ckpt-name epoch105-step84800 \ + --datasets-root /hpc/projects/intracellular_dashboard/organelle_dynamics \ + --z-range 15 45 --z-reduction mip --reference-pixel-size 0.1494 \ + --num-workers 0 # [--markers SEC61B] to subset; [--no-labelfree] to skip Phase3D +``` + +Writes (one per marker; dataset is in the path, so the filename is just `{marker}.zarr`): + +``` +//2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/ + SEC61B.zarr viral_sensor.zarr Phase3D.zarr +``` + +**3. Evaluate the embeddings — one command over the cohort.** No GPU, no re-prediction. + +```bash +uv run dynaclr eval \ + --eval-config applications/dynaclr/configs/evaluation/.yaml \ + --model-family DynaCLR-2D-MIP-BagOfChannels \ + --run 2d-mip-fix-shuffler --ckpt-name epoch105-step84800 \ + [--datasets 2026_04_14_A549_SEC61B_DENV ...] # omit = all datasets for this model/run/ckpt +``` + +This builds the `--embeddings_glob` and launches the Nextflow `eval_from_embeddings` entry +(reduce / smoothness / MMD / linear classifiers / append / plots). Equivalent direct call: + +```bash +nextflow run applications/dynaclr/nextflow/main.nf -entry eval_from_embeddings \ + --eval_config .yaml \ + --embeddings_glob '/hpc/projects/intracellular_dashboard/organelle_dynamics/*/2-phenotyping/predictions/DynaCLR-2D-MIP-BagOfChannels/2d-mip-fix-shuffler/epoch105-step84800/*.zarr' \ + -resume +``` + +**Progressive add:** a new dataset → step 2 for it → re-run step 3 with the same glob; the +cohort grows implicitly (the `*` in the dataset slot picks up the new one). Iterating a +classifier or adding a downstream task re-runs step 3 only — the embeddings are frozen. + +See [inference_triplet.md](inference_triplet.md) for `predict-triplet` flag detail and +[../../nextflow/README.md](../../nextflow/README.md) for the two Nextflow entries. + +## Primary spine — Airtable → per-marker inference + +```mermaid +flowchart TD + A["① new dataset
(assembled NFS zarr)"] + A -->|"prepare run → concatenate
→ QC find-Z + preprocess normalize"| B["{dataset}.zarr + tracking.zarr
(AI-ready: focus_slice + normalization in zattrs)"] + B -->|"register / sync — skill: airtable-register"| C["①·5 Airtable records
(well perturbations + zarr metadata:
data_path, tracks_path, channel_names,
focus_slice, norm stats, pixel size)"] + C -->|"skill: airtable-build-collection
(git-commit the YAML)"| D["② collection.yml
(experiments · channels name/marker/wells ·
Provenance stamps base_id + query)"] + D -->|"dynaclr predict-triplet
-c collection.yml --checkpoint … --model-family …
--run … --ckpt-name … --datasets-root …
[--markers …] [--no-labelfree]"| E["③ per-marker embeddings
one zarr per (experiment, marker)"] + E --> F["/2-phenotyping/predictions/
{model_family}/{run}/{ckpt_name}/{marker}.zarr
(AnnData: .X = features, obs = fov_name/track_id/t/…)"] + F --> QC["③·5 embedding-consistency QC
dynaclr embedding-consistency-qc
(per marker · uninfected wells anchor)"] + F --> DOWN["downstream tasks (fan-out below)"] +``` + + + +### Output: + +``` +/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr +``` + +## Downstream fan-out + +Every downstream task consumes the per-marker `{marker}.zarr` embeddings. Each is an +independent, Nextflow-able unit that links to its owning DAG. + +```mermaid +flowchart TD + E["…/{model}/{run}/{ckpt}/{marker}.zarr
(.X = features, obs = cell metadata)"] + E --> LC["linear classifiers
dynaclr run-linear-classifiers
→ append-annotations → append-predictions"] + E --> MMD["cross-experiment MMD / LOT correction
dynaclr compute-mmd · fit/apply-lot-correction"] + E --> PT["pseudotime alignment"] + E --> DR["dimensionality reduction + plots
dynaclr reduce-dimensionality · plot-embeddings"] + LC --> LCd["evaluation.md · witness_gmm_classifiers.md"] + MMD --> MMDd["lot_correction.md"] + PT --> PTd["pseudotime.md"] + DR --> DRd["evaluation.md"] +``` + + + + + +## Alternate spine — parquet-first (large reproducible runs) + +```mermaid +flowchart TD + D["collection.yml"] + D -->|"dynaclr build-cell-index "| P1["cell_index.parquet"] + P1 -->|"dynaclr preprocess-cell-index
--focus-channel Phase3D --focus-level fov"| P2["cell_index.parquet (empty frames dropped)"] + P2 -->|"viscy predict (MultiExperimentDataModule)"| P3["combined embeddings.zarr
(obs tags marker + experiment)"] + P3 -->|"dynaclr split-embeddings --group-by marker --prefix-by experiment"| E["{experiment}_{marker}.zarr"] +``` + + + +The parquet path tags every row with `marker` (bag-of-channels explodes each cell into one +row per channel), so per-marker splitting is a single +`split-embeddings --group-by marker --prefix-by experiment`. See [evaluation.md](evaluation.md). + +## Stage-by-stage + + +| # | Stage | Entry command | Owning DAG | Nextflow-able | +| --- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| ① | **Preprocess** — find focus-Z + normalize | `prepare run -c prepare_config.yaml` → `sbatch 02_qc.sh` + `sbatch 03_preprocess.sh` | [ai_ready_datasets.md](ai_ready_datasets.md) | ❌ (batch tooling, not NF) | +| ①·5 | **Register / sync Airtable** | skill `airtable-register` (well records + zarr `.zattrs` metadata) | [inference_triplet.md](inference_triplet.md) | ❌ human-in-the-loop | +| ② | **Build collection** | skill `airtable-build-collection` → `collection.yml` (git-committed) | [inference_triplet.md](inference_triplet.md) | ❌ human-in-the-loop | +| ③ | **Predict embeddings (per marker)** | `dynaclr predict-triplet -c collection.yml --checkpoint … --model-family … --run … --ckpt-name … [--datasets-root …] [--markers …] [--no-labelfree]` | [inference_triplet.md](inference_triplet.md) | ✅ one process per (exp, marker) | +| ③·5 | **Embedding-consistency QC** | `dynaclr embedding-consistency-qc -c .yml` | `[embedding_consistency_qc.md](../../../../.ed_planning/dynaclr/batch_correction/embedding_consistency_qc.md)` | ✅ | +| ④ | **Run linear classifiers** | `dynaclr run-linear-classifiers -c clf.yml` | [evaluation.md](evaluation.md), [witness_gmm_classifiers.md](witness_gmm_classifiers.md) | ✅ | +| ⑤ | **Append predictions** | `dynaclr append-annotations -c …` → `dynaclr append-predictions -c …` | [evaluation.md](evaluation.md) | ✅ | +| — | **MMD / LOT correction** | `dynaclr compute-mmd` · `dynaclr fit-lot-correction` / `apply-lot-correction` | [lot_correction.md](lot_correction.md) | ✅ | +| — | **Pseudotime** | see owning DAG | [pseudotime.md](pseudotime.md) | ✅ | +| — | **Dim-reduction + plots** | `dynaclr reduce-dimensionality` · `dynaclr plot-embeddings` | [evaluation.md](evaluation.md) | ✅ | + + + + +## Alternate: parquet-first for stage ② + +The alternate spine replaces stages ②–③ with the parquet path — `build-cell-index` (with +`--focus-level fov`) → `preprocess-cell-index` → `viscy predict` → `split-embeddings`. It's +the right choice for large multi-experiment reproducible runs; the collection YAML feeds +both routes. See [training.md](training.md) §build and +[../recipes/build-cell-index.md](../recipes/build-cell-index.md). + +## Nextflow: inference and evaluation are two entries + +Embeddings are written **once** into the dataset-centric tree (stage ③), then evaluation +reads those **frozen** embeddings — no GPU, no re-prediction — so classifiers and downstream +tasks (④/⑤ and the fan-out) can be re-run freely without re-embedding. + +- **`-entry evaluation`** — full spine: predict → split → downstream. Use for the parquet + spine or a one-shot run. +- **`-entry eval_from_embeddings`** — skips predict/split; sources per-experiment zarrs from + `--embeddings_glob` and runs the same shared `DOWNSTREAM` DAG. The glob is the **cohort + selector** (`*` in the dataset slot); the eval config says *what to compute*. Add a dataset → + predict it → re-run the same glob; the cohort grows implicitly. + +One-command launcher: `dynaclr eval` turns a `(model_family, run, ckpt_name)` identity into +the glob and launches the `eval_from_embeddings` entry. See +[../../nextflow/README.md](../../nextflow/README.md) for both entries and the run-once / +eval-many pattern. diff --git a/applications/dynaclr/docs/DAGs/inference_triplet.md b/applications/dynaclr/docs/DAGs/inference_triplet.md index 52aa3aab4..a394ed354 100644 --- a/applications/dynaclr/docs/DAGs/inference_triplet.md +++ b/applications/dynaclr/docs/DAGs/inference_triplet.md @@ -23,49 +23,24 @@ a 3D zarr can feed a 2D model without materializing a separate MIP dataset. ## 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: .X = embedding_key array, mirrored to obsm["X_backbone"] - /["X_projections"]; 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 +```mermaid +flowchart TD + IN["dataset.zarr (normalization in FOV zattrs)
tracking.zarr/CSV (track_id, t, y, x)
checkpoint.ckpt (trained ContrastiveModule)"] + IN -->|"predict config
(TripletDataModule + ContrastiveModule + EmbeddingWriter)"| PRED["viscy predict --config configs/prediction/predict_triplet.yml"] + PRED --> PREDNOTE["TripletDataModule(fit=False): ONE anchor patch per (cell, timepoint)
• extract z_range window, yx at initial_yx_patch_size
• reference_pixel_size → larger patch, BatchedZoomd to final_yx
• z_reduction → BatchedChannelWiseZReductiond collapses Z to 1 (2D)
ContrastiveModule.predict_step → backbone features (+ projections)
EmbeddingWriter accumulates (features, index) → one combined store"] + PREDNOTE --> EMB["embeddings.zarr
(AnnData: .X = embedding_key array, mirrored to
obsm[X_backbone]/[X_projections]; obs = fov_name/track_id/t/…)"] + EMB -->|"dynaclr split-embeddings --input embeddings.zarr --output-dir embeddings/
groups rows by obs[experiment], one zarr per experiment,
removes the combined store afterwards"| SPLIT["embeddings/{experiment}.zarr
(one per experiment, informatively named)"] + SPLIT --> DOWN["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) +```mermaid +flowchart TD + C["predict config + checkpoint + zarr + tracking"] + C --> P["viscy predict
(GPU, minutes–hours by cell count)"] + P --> S["split-embeddings
(CPU, ~1 min, I/O bound)"] + S --> D["downstream eval
(CPU/GPU, per analysis)"] ``` ## Key commands @@ -74,7 +49,7 @@ downstream eval (CPU/GPU, per analysis) | ---------------- | --------------------------------------------------------------------------- | --------------------------------------- | ----------------------------------- | | 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 | +| Split embeddings | `dynaclr split-embeddings --input embeddings.zarr --output-dir embeddings/ [--group-by experiment]` | combined zarr with the `--group-by` column in `obs` | one `{group}.zarr` per group value | ## What lives where @@ -167,13 +142,97 @@ return_predictions: false # writer persists to zarr; don't hold in - **`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. +- **`split-embeddings` groups by any `obs` column via `--group-by`** (default + `experiment`) and can prefix filenames via `--prefix-by` (e.g. + `--group-by marker --prefix-by experiment` → `{experiment}_{marker}.zarr`). The + requested columns must exist on the combined store. For a single-experiment + predict run the default split is optional — the combined `embeddings.zarr` is + already per-experiment. +- **The triplet `EmbeddingWriter` writes only ultrack index columns** to `obs` + (`fov_name, track_id, t, id, parent_track_id, parent_id, z, y, x`). It does + **not** write `experiment` or `marker` — those come from the parquet path + (`MultiExperimentDataModule`), which carries collection metadata. Therefore + `split-embeddings --group-by marker` cannot split triplet output; see below for + the per-marker recipe. - 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). +## Per-marker embeddings (bag-of-channels models) — `dynaclr predict-triplet` + +Bag-of-channels models (e.g. `DynaCLR-2D-MIP-BagOfChannels`) are trained with +`in_channels: 1` — each marker is embedded as its own single-channel sample. On +the triplet path there is no combined store to split by marker (the writer omits +`marker`, see Notes), so the per-marker split is expressed by **running predict +once per marker**, each with a single `source_channel`. + +This is a **single command** — no hand-written per-dataset config generator: + +```sh +dynaclr predict-triplet \ + -c collection.yml \ + --checkpoint /path/to/epoch=105-step=84800.ckpt \ + --model-family DynaCLR-2D-MIP-BagOfChannels \ + --run 2d-mip-...-fix-shuffler \ + --ckpt-name epoch105-step84800 \ + --datasets-root /hpc/projects/intracellular_dashboard/organelle_dynamics \ + --z-range 15 45 --z-reduction mip --reference-pixel-size 0.1494 \ + [--markers SEC61B,TOMM20] [--no-labelfree] +``` + +The **collection YAML is the single source of truth**. Each `ChannelEntry` +carries a zarr `name`, a `marker` label, and optional `wells` (empty = all +wells). The command runs predict once per channel entry, restricting to that +channel's `wells` via `fit_include_wells`, and writes one zarr per marker. When a +reporter varies by plate column (multi-organelle "box" plates), list the same +zarr channel once per organelle with its own `wells`: + +```yaml +experiments: + - name: 2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV + channels: + - {name: raw GFP EX488 EM525-45, marker: SEC61B, wells: [A/2, B/2]} + - {name: raw GFP EX488 EM525-45, marker: TOMM20, wells: [A/3, B/3]} + - {name: raw GFP EX488 EM525-45, marker: G3BP1, wells: [A/4, B/4]} + - {name: raw mCherry EX561 EM600-37, marker: pAL17} # all wells +``` + +**Dataset- and provenance-scoped output tree** — so every zarr traces back to +what produced it and two models/checkpoints can coexist for comparison: + +``` +{datasets_root}/{dataset}/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr +``` + +The dataset is derived from each experiment's `data_path`; the directory tree is +the artifact index, so downstream consumers pool matching stores with a glob and +no registry file is required. + +`--markers` selects a subset; `--no-labelfree` skips phase/brightfield channels +(resolved by name via `parse_channel_name`). + +> **Legacy (retired):** older datasets used a per-dataset +> `generate_predict_configs.py` + `predict_triplet_per_marker.sh` copied into +> `2-phenotyping/predictions/configs/` (e.g. the DENV worked example). That +> pattern fragmented — everyone copied and forked it. Prefer `predict-triplet`; +> the generator is kept only for already-run datasets. + +### Provenance & Nextflow + +`predict-triplet` writes the model/run/checkpoint-scoped tree directly. The +parquet path can route its combined store into the same tree with +`split-embeddings --route-by-dataset --model-family M --run R --ckpt-name C`. +Evaluation is decoupled from prediction: `dynaclr eval` builds the cohort glob +and launches the Nextflow `eval_from_embeddings` entry over the frozen stores. +For batch prediction or a full train→predict→eval sweep, use `dynaclr +predict-batch` or `dynaclr run-matrix`. + +For the **parquet path**, one predict run already tags every row with `marker` +(bag-of-channels explodes each cell into one row per channel), so per-marker +splitting is instead a single +`dynaclr split-embeddings --group-by marker --prefix-by experiment`, which writes +`{experiment}_{marker}.zarr` for the same convention. + ## Triplet vs parquet (MultiExperimentDataModule) | Aspect | Triplet path (this doc) | Parquet path (evaluation.md) | diff --git a/applications/dynaclr/nextflow/README.md b/applications/dynaclr/nextflow/README.md index f2d4f9184..102aa330a 100644 --- a/applications/dynaclr/nextflow/README.md +++ b/applications/dynaclr/nextflow/README.md @@ -11,7 +11,9 @@ applications/dynaclr/nextflow/ ├── main.nf # thin router — -entry ├── nextflow.config # shared params + SLURM resource labels ├── workflows/ -│ ├── evaluation.nf # workflow EVALUATION { take: ... } +│ ├── evaluation.nf # workflow EVALUATION { take: ... } (predict→split→DOWNSTREAM) +│ ├── eval_from_embeddings.nf # workflow EVAL_FROM_EMBEDDINGS { take: ... } (glob→DOWNSTREAM) +│ ├── _downstream.nf # shared DOWNSTREAM sub-workflow (reduce/mmd/lc/append/plot) │ └── training_preprocessing.nf # workflow TRAINING_PREPROCESSING { take: ... } └── modules/ ├── evaluation/ # processes used only by evaluation @@ -36,6 +38,13 @@ nextflow run applications/dynaclr/nextflow/main.nf -entry evaluation \ --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ -resume +# Eval from pre-computed embeddings (decoupled: no predict/split, reads the frozen tree) +nextflow run applications/dynaclr/nextflow/main.nf -entry eval_from_embeddings \ + --eval_config applications/dynaclr/configs/evaluation/.yaml \ + --embeddings_glob '/hpc/projects/intracellular_dashboard/organelle_dynamics/*/2-phenotyping/predictions////*.zarr' \ + --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ + -resume + # Training preprocessing (collection YAML → training-ready parquet) nextflow run applications/dynaclr/nextflow/main.nf -entry training_preprocessing \ --collection_yaml applications/dynaclr/configs/collections/.yml \ @@ -49,14 +58,67 @@ nextflow run applications/dynaclr/nextflow/main.nf -entry training_preprocessing Running `main.nf` without `-entry` fails loudly with the list of valid entries. -## Predict-only runs +## Inference and evaluation are decoupled + +Embeddings are written **once** into the dataset-centric tree +`/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr` +(by `dynaclr predict-triplet`, or by the parquet spine via +`dynaclr split-embeddings --route-by-dataset`). Evaluation reads those **frozen** +embeddings — no GPU, no re-prediction — so you can iterate classifiers and +downstream tasks freely against a fixed set of embeddings. + +- **`-entry evaluation`** — the full spine: predict → split → downstream + (reduce/smoothness/mmd/classifiers/plots). Use for the parquet spine or a + one-shot run. +- **`-entry eval_from_embeddings`** — skips predict/split; sources per-experiment + zarrs from `--embeddings_glob` and runs the same shared `DOWNSTREAM` DAG. + +Both call the same `DOWNSTREAM` sub-workflow (`workflows/_downstream.nf`), so the +per-experiment-zarr write-order barriers live in one place. + +### Selecting which datasets to evaluate + +The **glob is the cohort selector** — the `*` in the dataset slot picks which +datasets; the tail pins model/run/ckpt/marker. No manifest to maintain: + +```bash +# every dataset for this model/run/ckpt (progressive default) +--embeddings_glob '.../organelle_dynamics/*/2-phenotyping/predictions/M/R/C/*.zarr' +# one dataset +--embeddings_glob '.../organelle_dynamics//2-phenotyping/predictions/M/R/C/*.zarr' +# a subset (brace expansion) +--embeddings_glob '.../organelle_dynamics/{ds_a,ds_b}/.../M/R/C/*.zarr' +# one marker across all datasets +--embeddings_glob '.../organelle_dynamics/*/.../M/R/C/SEC61B.zarr' +``` + +Add a dataset → predict it (writes into the tree) → re-run the same glob; the +cohort grows implicitly. The eval config YAML is the orthogonal lever: the glob +says *which datasets*, the config says *what to compute*. + +### One-command launcher + +`dynaclr eval` turns a `(model_family, run, ckpt_name)` identity into the glob +and launches the `eval_from_embeddings` entry: + +```bash +uv run dynaclr eval \ + --eval-config applications/dynaclr/configs/evaluation/.yaml \ + --model-family DynaCLR-2D-MIP-BagOfChannels \ + --run 2d-mip-fix-shuffler --ckpt-name epoch105-step84800 \ + [--marker SEC61B] [--datasets ds_a ds_b] [--print-cmd] +``` + +To run **many models** through train → predict → eval in parallel (chained per model +via SLURM `afterok`), use the model matrix — see +[`../tools/README.md`](../tools/README.md) (`dynaclr run-matrix` + a matrix YAML). + +### Predict-only runs -Use the `evaluation` entry with `steps: [predict, split]` in your eval config -to run inference on a new dataset without any downstream evals. Rerun with -more steps later using `-resume` — predict/split are skipped because -`embeddings.zarr` and `{exp}.zarr` already exist on disk. See -[docs/DAGs/evaluation.md](../docs/DAGs/evaluation.md#predict-only-runs-inference-without-downstream-evals) -for the full pattern. +To run inference without downstream evals, use the `evaluation` entry with +`steps: [predict, split]`; rerun with more steps later via `-resume` (predict/ +split are skipped because the zarrs already exist on disk). See +[docs/DAGs/evaluation.md](../docs/DAGs/evaluation.md#predict-only-runs-inference-without-downstream-evals). ## Adding a new workflow diff --git a/applications/dynaclr/src/dynaclr/evaluation/README.md b/applications/dynaclr/src/dynaclr/evaluation/README.md index 75fcd7ce9..416943fc2 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/README.md +++ b/applications/dynaclr/src/dynaclr/evaluation/README.md @@ -6,6 +6,10 @@ Evaluation tools for DynaCLR cell embedding models. Each evaluation method lives | Method | Directory/Module | Description | |--------|------------------|-------------| +| Embedding paths | `paths.py` | Canonical grammar for the dataset-centric embedding tree (`embedding_store`, `iter_embeddings`) | +| Per-marker inference | `predict_triplet.py` | One-call `predict-triplet`: collection + checkpoint → `{dataset}/2-phenotyping/predictions/{model}/{run}/{ckpt}/{marker}.zarr` | +| Split embeddings | `split_embeddings.py` | Split a combined embeddings zarr per experiment/marker; `--route-by-dataset` writes the dataset tree | +| Cross-experiment MMD | `mmd/` | MMD between perturbation / experiment groups (embedding-consistency QC basis) | | Linear classifiers | `linear_classifiers/` | Logistic regression on embeddings for supervised cell phenotyping | | Temporal smoothness | `benchmarking/smoothness/` | Evaluate how smoothly embeddings change across adjacent time frames | | Dimensionality reduction | `dimensionality_reduction/` | Compute PCA, UMAP, and/or PHATE on saved AnnData zarr embeddings | From 111af4918c5d2746bcd273d2313753bfd54b15c2 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:19:54 -0700 Subject: [PATCH 49/89] feat(dynaclr): Benjamini-Yekutieli FDR control for the MMD significance gate The per-condition MMD gate runs one test per (marker, condition), so raw-p thresholding inflates false positives. Correct the p-values with Benjamini- Yekutieli FDR control (scipy.stats.false_discovery_control, method="by") across the whole Stage-A run's (marker, condition) family, and gate on the ADJUSTED p-value; mmd_pvalue_threshold is now a target FDR level (default 0.05, 1.0 disables). A condition is labeled only if FDR-significant AND GMM-bimodal. Two-pass refactor of Stage A (build_marker_annotation split): - compute_marker_scores (pass 1): references + witness scores + raw per-condition MMD p-values, cached in _MarkerScores. - generate_witness_gmm_annotation: pool all raw p-values, BY-adjust run-wide, decide significant conditions. - label_marker (pass 2): GMM-label the FDR-significant conditions. - config: mmd_pvalue_threshold doc reframed as target FDR level. - test: two-pass helper; 7 green. - recipe + DAG: document BY-FDR + the two-pass flow. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../witness_gmm_labels_infectomics.yml | 8 +- .../docs/DAGs/witness_gmm_classifiers.md | 22 ++- .../src/dynaclr/evaluation/evaluate_config.py | 11 +- .../linear_classifiers/witness_gmm_labels.py | 163 +++++++++++------- .../witness_gmm_labels_test.py | 18 +- 5 files changed, 146 insertions(+), 76 deletions(-) diff --git a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml index c775ed1fd..6fcbd26cc 100644 --- a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml +++ b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml @@ -36,9 +36,11 @@ witness_gmm_labels: label_column: infection_state class_map: {positive: infected, negative: uninfected} gmm_pos_threshold: 0.8 - # Significance gate: skip a condition whose MMD vs the control reference is not - # significant (no detectable perturbation signature). A condition is labeled only - # if BOTH the MMD is significant AND the GMM is bimodal. Set to 1.0 to disable. + # Significance gate (target FDR level): each condition's MMD vs the control + # reference is permutation-tested; raw p-values across the whole run's + # (marker, condition) family get Benjamini-Yekutieli FDR correction, and a + # condition is skipped when its ADJUSTED p exceeds this level. A condition is + # labeled only if BOTH FDR-significant AND GMM-bimodal. Set to 1.0 to disable. mmd_pvalue_threshold: 0.05 mmd_n_permutations: 1000 bandwidth: null # median heuristic on pooled (control, perturbed) diff --git a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md index 915eeeb27..c34713e04 100644 --- a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md +++ b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md @@ -24,7 +24,7 @@ flowchart TD A1["references from wells/filters:
X = control cells, Y = perturbed cells"] A2["bandwidth = median_heuristic(X, Y)
viscy_utils.evaluation.mmd"] A3["score every cell: w(z) = witness_function(z, X, Y, bw)
mmd"] - AG["per perturbed CONDITION: MMD permutation test (X vs cond)
mmd.mmd_permutation_test
p > mmd_pvalue_threshold → not significant → skip condition"] + AG["per perturbed CONDITION: MMD permutation test (X vs cond)
mmd.mmd_permutation_test
run-wide Benjamini-Yekutieli FDR; adjusted p > mmd_pvalue_threshold → skip"] A4["per perturbed CONDITION: 2-component GMM on w[cond]
witness_gmm.fit_gmm_labels
remod = argmin(means); posterior ≥ gmm_pos_threshold → positive
negatives = ALL control-well cells; ambiguous → dropped
unimodal GMM (separated=False) → condition skipped"] A5["map GMM ±1 → class_map vocabulary
(e.g. infected / uninfected)"] A1 --> A2 --> A3 --> AG --> A4 --> A5 @@ -87,13 +87,17 @@ threshold, which is exactly what the GMM removes. The GMM is the principled repl the time gate: it finds the remodeled sub-population within the full mixture. Add a time gate only for a specific reason (e.g. debugging, or a marker with no clean late window). -**Significance gate (before the GMM).** Per condition, an MMD permutation test -(`mmd_permutation_test`, X vs the condition's cells) checks whether the two clouds are -*actually distinct*. If `p > mmd_pvalue_threshold` (default 0.05) the perturbation left no -detectable signature and the condition is skipped — no labels manufactured from noise. A -condition contributes positives only if it is **both** MMD-significant **and** GMM-bimodal -(`separated=True`); the two guard different failure modes (references differ vs. the -perturbed cloud splits cleanly). Set `mmd_pvalue_threshold: 1.0` to disable the gate. +**Significance gate (before the GMM, FDR-controlled).** Per condition, an MMD permutation +test (`mmd_permutation_test`, X vs the condition's cells) checks whether the two clouds are +*actually distinct*. Because one Stage-A run tests many (marker × condition) pairs, the raw +p-values are corrected with **Benjamini-Yekutieli FDR control** +(`scipy.stats.false_discovery_control(method="by")`) across the whole run, and a condition +is skipped when its **adjusted** p-value exceeds `mmd_pvalue_threshold` (the target FDR +level, default 0.05). A condition contributes positives only if it is **both** +FDR-significant **and** GMM-bimodal (`separated=True`); the two guard different failure +modes (references differ vs. the perturbed cloud splits cleanly). This is a **two-pass** +flow: score + p-value every condition (pass 1), BY-adjust run-wide, then GMM-label the +survivors (pass 2). Set `mmd_pvalue_threshold: 1.0` to disable the gate. ```mermaid flowchart LR @@ -128,7 +132,7 @@ witness_gmm_labels: label_column: infection_state class_map: {positive: infected, negative: uninfected} gmm_pos_threshold: 0.8 - mmd_pvalue_threshold: 0.05 # skip a condition if MMD vs control is not significant + mmd_pvalue_threshold: 0.05 # target FDR level (BY-adjusted p) for the MMD gate mmd_n_permutations: 1000 bandwidth: null # median heuristic max_reference_cells: 5000 diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index fa62f883c..30d89193c 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -298,11 +298,12 @@ class WitnessGmmLabelsConfig(BaseModel): GMM remodeled-component posterior at/above which a perturbed cell is a confident positive. Default: 0.8. mmd_pvalue_threshold : float - Significance gate applied per perturbed condition before the GMM: the - condition's cloud is MMD-permutation-tested against the control reference, - and if the p-value exceeds this threshold the separation is not significant - and the condition is skipped (no positives). Set to 1.0 to disable the - gate. Default: 0.05. + Target FDR level for the significance gate. Each perturbed condition is + MMD-permutation-tested against the control reference; the raw p-values + across the whole run's (marker, condition) family are corrected with + Benjamini-Yekutieli FDR control, and a condition is skipped when its + **adjusted** p-value exceeds this level. Set to 1.0 to disable the gate. + Default: 0.05. mmd_n_permutations : int Number of permutations for the MMD significance test. Default: 1000. bandwidth : float or None diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py index 2f521db59..a79442d40 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py @@ -27,6 +27,7 @@ class vocabulary — a file indistinguishable from a hand annotation. from __future__ import annotations import logging +from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING @@ -34,6 +35,7 @@ class vocabulary — a file indistinguishable from a hand annotation. import click import numpy as np import pandas as pd +from scipy.stats import false_discovery_control from viscy_utils.cli_utils import load_config from viscy_utils.evaluation.mmd import median_heuristic, mmd_permutation_test, subsample, witness_function @@ -150,41 +152,33 @@ def _annotation_key_columns(obs: pd.DataFrame) -> list[str]: ) -def build_marker_annotation( - adata: ad.AnnData, - experiments: list[WitnessGmmExperiment], - config: WitnessGmmLabelsConfig, -) -> pd.DataFrame | None: - """Label one marker's cells via witness → per-condition GMM. +@dataclass +class _MarkerScores: + """Cached per-marker witness scores + reference masks for two-pass labeling.""" - Builds control/perturbed references from each experiment's wells/filters, - scores every cell with the MMD witness, fits a 2-component GMM on each - perturbed condition's scores, and assembles a per-cell annotation frame with - the config's ``label_column`` set to the mapped class vocabulary. Negatives - are all control-well cells (well identity); positives are perturbed cells - clearing ``gmm_pos_threshold``; ambiguous perturbed cells are dropped. + obs: pd.DataFrame + control_mask: np.ndarray + perturbed_mask: np.ndarray + scores: np.ndarray # witness score per cell + conditions: np.ndarray # obs[condition_column] as array + cond_pvalues: dict # condition -> MMD permutation p-value (raw) - Parameters - ---------- - adata : ad.AnnData - Embeddings for a single marker, pooled across experiments. ``obs`` must - carry ``experiment``, ``fov_name``, the annotation key, and - ``config.condition_column``. - experiments : list[WitnessGmmExperiment] - Per-experiment reference specs (control/perturbed wells or filters). - config : WitnessGmmLabelsConfig - Labeling settings (threshold, bandwidth, class map, condition column). - Returns - ------- - pd.DataFrame or None - Annotation frame with the join-key columns plus ``t`` and the - ``label_column``. ``None`` when references are missing or the GMM is - unimodal for every condition (near-noise marker → skipped). +def compute_marker_scores( + adata: ad.AnnData, + experiments: list[WitnessGmmExperiment], + config: WitnessGmmLabelsConfig, +) -> _MarkerScores | None: + """Pass 1: build references, witness-score every cell, and MMD-test each condition. + + Returns the cached scores/masks and the raw per-condition MMD permutation + p-values. The caller pools these p-values across all markers and applies + Benjamini-Yekutieli FDR control before deciding which conditions are + significant (see :func:`generate_witness_gmm_annotation`). ``None`` when the + marker has no control or perturbed reference cells. """ obs = adata.obs rng = np.random.default_rng(config.random_seed) - key_cols = _annotation_key_columns(obs) control_mask = np.zeros(len(obs), dtype=bool) perturbed_mask = np.zeros(len(obs), dtype=bool) @@ -202,34 +196,23 @@ def build_marker_annotation( perturbed_mask |= exp_mask & pert X_all = adata.X if isinstance(adata.X, np.ndarray) else adata.X.toarray() - X_ctrl = X_all[control_mask] - Y_pert = X_all[perturbed_mask] - if len(X_ctrl) == 0 or len(Y_pert) == 0: + if control_mask.sum() == 0 or perturbed_mask.sum() == 0: _logger.warning("No control/perturbed reference cells found; skipping marker.") return None - X_ref = subsample(X_ctrl, config.max_reference_cells, rng) - Y_ref = subsample(Y_pert, config.max_reference_cells, rng) + X_ref = subsample(X_all[control_mask], config.max_reference_cells, rng) + Y_ref = subsample(X_all[perturbed_mask], config.max_reference_cells, rng) bandwidth = config.bandwidth if config.bandwidth is not None else median_heuristic(X_ref, Y_ref) scores = witness_function(X_all, X_ref, Y_ref, bandwidth=bandwidth) - pos_label = config.class_map["positive"] - neg_label = config.class_map["negative"] - - # Negatives: all control-well cells (well identity, no gate). - labels = np.full(len(obs), None, dtype=object) - labels[control_mask] = neg_label - - # Positives: per perturbed condition, GMM-gate the scores. + # Per-condition MMD significance: is this condition's cloud distinct from the + # control reference? Raw p-values here; FDR-corrected run-wide by the caller. conditions = obs[config.condition_column].to_numpy() - any_separated = False + cond_pvalues: dict = {} for cond in pd.unique(conditions[perturbed_mask]): cond_mask = perturbed_mask & (conditions == cond) if cond_mask.sum() < 5: continue - # Significance gate: is this condition's cloud actually distinct from the - # control reference? A non-significant MMD means the perturbation left no - # detectable signature — skip rather than manufacture labels from noise. _mmd2, p_value, _null = mmd_permutation_test( X_ref, subsample(X_all[cond_mask], config.max_reference_cells, rng), @@ -237,17 +220,46 @@ def build_marker_annotation( bandwidth=bandwidth, seed=config.random_seed, ) - if p_value > config.mmd_pvalue_threshold: - _logger.warning( - "MMD not significant for condition %r (p=%.3g > %.3g); no positives labeled.", - cond, - p_value, - config.mmd_pvalue_threshold, - ) + cond_pvalues[cond] = float(p_value) + + return _MarkerScores(obs, control_mask, perturbed_mask, scores, conditions, cond_pvalues) + + +def label_marker( + marker_scores: _MarkerScores, + significant_conditions: set, + config: WitnessGmmLabelsConfig, +) -> pd.DataFrame | None: + """Pass 2: GMM-gate each significant condition and assemble the annotation frame. + + A condition is labeled only if it is in ``significant_conditions`` (cleared + the FDR-controlled MMD gate, decided run-wide) AND its GMM is bimodal. + Negatives are all control-well cells (well identity). ``None`` when no + condition survives both gates. + """ + obs = marker_scores.obs + key_cols = _annotation_key_columns(obs) + scores = marker_scores.scores + control_mask = marker_scores.control_mask + perturbed_mask = marker_scores.perturbed_mask + conditions = marker_scores.conditions + pos_label = config.class_map["positive"] + neg_label = config.class_map["negative"] + + labels = np.full(len(obs), None, dtype=object) + labels[control_mask] = neg_label + + any_separated = False + for cond in pd.unique(conditions[perturbed_mask]): + cond_mask = perturbed_mask & (conditions == cond) + if cond_mask.sum() < 5: + continue + if cond not in significant_conditions: + _logger.warning("MMD not significant (FDR) for condition %r; no positives labeled.", cond) continue res = fit_gmm_labels(scores[cond_mask], pos_threshold=config.gmm_pos_threshold, random_state=config.random_seed) if not res.separated: - _logger.warning("GMM unimodal for condition %r (p=%.3g); no positives labeled.", cond, p_value) + _logger.warning("GMM unimodal for condition %r; no positives labeled.", cond) continue any_separated = True idx = np.flatnonzero(cond_mask)[res.hard_label == 1] @@ -295,22 +307,57 @@ def generate_witness_gmm_annotation(config: WitnessGmmLabelsConfig) -> Path: adata.obs_names_make_unique() markers = config.marker_filters or list(pd.unique(adata.obs["marker"])) - frames: list[pd.DataFrame] = [] + + # Pass 1: witness-score every marker and collect raw per-condition MMD + # p-values across the whole run. + marker_scores: dict[str, _MarkerScores] = {} + pval_keys: list[tuple[str, object]] = [] # (marker, condition) + pvals: list[float] = [] for marker in markers: sub = adata[adata.obs["marker"] == marker] if sub.n_obs == 0: _logger.warning("No cells for marker %r; skipping.", marker) continue - frame = build_marker_annotation(sub.copy(), config.experiments, config) + ms = compute_marker_scores(sub.copy(), config.experiments, config) + if ms is None: + continue + marker_scores[marker] = ms + for cond, p in ms.cond_pvalues.items(): + pval_keys.append((marker, cond)) + pvals.append(p) + + if not pvals: + raise RuntimeError("No testable conditions — check references and condition_column.") + + # Benjamini-Yekutieli FDR control across the whole run's (marker, condition) + # family; a condition is significant if its adjusted p ≤ mmd_pvalue_threshold. + adjusted = false_discovery_control(np.asarray(pvals), method="by") + significant: dict[str, set] = {} + for (marker, cond), p_adj in zip(pval_keys, adjusted): + if p_adj <= config.mmd_pvalue_threshold: + significant.setdefault(marker, set()).add(cond) + else: + _logger.info( + "Condition %r (marker %r): BY-adjusted p=%.3g > %.3g — skipped.", + cond, + marker, + p_adj, + config.mmd_pvalue_threshold, + ) + + # Pass 2: GMM-label each marker using the FDR-significant conditions. + frames: list[pd.DataFrame] = [] + for marker, ms in marker_scores.items(): + frame = label_marker(ms, significant.get(marker, set()), config) if frame is None: - _logger.warning("Marker %r produced no labels (missing refs or unimodal GMM); skipping.", marker) + _logger.warning("Marker %r produced no labels (no significant + bimodal condition); skipping.", marker) continue counts = frame[config.label_column].value_counts().to_dict() _logger.info("Marker %r: %d labeled cells %s", marker, len(frame), counts) frames.append(frame) if not frames: - raise RuntimeError("No markers produced labels — check references, threshold, and condition_column.") + raise RuntimeError("No markers produced labels — check references, thresholds, and condition_column.") out = pd.concat(frames, ignore_index=True) output_path = Path(config.output_path) diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py index 85d4c04dd..d8f7504b5 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py @@ -7,13 +7,29 @@ from dynaclr.evaluation.evaluate_config import WitnessGmmExperiment, WitnessGmmLabelsConfig from dynaclr.evaluation.linear_classifiers.witness_gmm_labels import ( _well_prefix_mask, - build_marker_annotation, + compute_marker_scores, generate_witness_gmm_annotation, + label_marker, obs_filter_mask, ) from viscy_utils.evaluation.annotation import load_annotation_anndata +def build_marker_annotation(adata, experiments, config): + """Test helper: compose the two-pass Stage-A labeling (all conditions + treated as significant unless the raw MMD gate would exclude them). + + Mirrors the run-level flow for a single marker: score → per-condition MMD + p-value → treat p ≤ threshold as significant → GMM-label. Returns None when + the marker has no references or no condition survives both gates. + """ + ms = compute_marker_scores(adata, experiments, config) + if ms is None: + return None + significant = {c for c, p in ms.cond_pvalues.items() if p <= config.mmd_pvalue_threshold} + return label_marker(ms, significant, config) + + def _make_separable_embeddings( n_per_well: int = 80, n_features: int = 16, From 87763f9807d3bd3d496756b3016ff6c5e3dc2e9e Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:30:09 -0700 Subject: [PATCH 50/89] feat(viscy-utils): model-agnostic visualization module (occlusion + PCA-RGB) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add packages/viscy-utils/.../visualization/ with pure-tensor, model-agnostic attribution utilities: - occlusion_saliency: sliding-window occlusion over any (B,C,H,W) -> (B,D) embed_fn; distances l2 / cosine / signed_delta; fill zero / mean / blur (blur replaces the patch with a Gaussian-blurred copy of itself, preserving gross shape/density and removing only fine texture — faithful for phase). - saliency_to_rgb: render a saliency map with per-frame or fixed symmetric clipping for cross-frame-comparable signed attribution. - pca_rgb_from_patches: DINO-style PCA-RGB overlay of dense patch features. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/viscy_utils/visualization/__init__.py | 9 + .../viscy_utils/visualization/occlusion.py | 267 ++++++++++++++++++ .../src/viscy_utils/visualization/pca_rgb.py | 224 +++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 packages/viscy-utils/src/viscy_utils/visualization/__init__.py create mode 100644 packages/viscy-utils/src/viscy_utils/visualization/occlusion.py create mode 100644 packages/viscy-utils/src/viscy_utils/visualization/pca_rgb.py diff --git a/packages/viscy-utils/src/viscy_utils/visualization/__init__.py b/packages/viscy-utils/src/viscy_utils/visualization/__init__.py new file mode 100644 index 000000000..ed5e32200 --- /dev/null +++ b/packages/viscy-utils/src/viscy_utils/visualization/__init__.py @@ -0,0 +1,9 @@ +"""Visualization utilities (model-agnostic).""" + +from viscy_utils.visualization.occlusion import ( + occlusion_saliency, + saliency_to_rgb, +) +from viscy_utils.visualization.pca_rgb import pca_rgb_from_patches + +__all__ = ["pca_rgb_from_patches", "occlusion_saliency", "saliency_to_rgb"] diff --git a/packages/viscy-utils/src/viscy_utils/visualization/occlusion.py b/packages/viscy-utils/src/viscy_utils/visualization/occlusion.py new file mode 100644 index 000000000..da0de4e50 --- /dev/null +++ b/packages/viscy-utils/src/viscy_utils/visualization/occlusion.py @@ -0,0 +1,267 @@ +"""Occlusion-based saliency for any frozen embedding model. + +Pure tensor math — no model imports. Given a callable that maps +``(B, C, H, W)`` images to ``(B, D)`` embeddings, slides a fill patch +across each image and measures how the embedding changes. Regions +where occlusion shifts the embedding the most are the regions the +model "relies on" for that cell's representation. + +Unlike PCA-RGB, the spatial resolution of the output is set by the +occlusion *stride* — not the model's patch grid. This makes occlusion +the right tool for backbones like ConvNeXt where the native feature +map is coarse (e.g. 5×5). + +Cost: one forward pass per occlusion position. At ``stride=8`` on a +160×160 image that's 400 forwards per image — batchable, and cheap +relative to a full evaluation run. +""" + +from __future__ import annotations + +from typing import Callable + +import torch +import torch.nn.functional as F +from torch import Tensor + + +def _grid_positions(image_size: int, occ_size: int, stride: int) -> list[int]: + """Return top-left occlusion positions covering ``image_size``.""" + if occ_size <= 0 or stride <= 0: + raise ValueError(f"occ_size and stride must be positive, got {occ_size}, {stride}") + if occ_size > image_size: + raise ValueError(f"occ_size {occ_size} > image_size {image_size}") + last = max(image_size - occ_size, 0) + positions = list(range(0, last + 1, stride)) + if positions[-1] != last: + positions.append(last) + return positions + + +def _gaussian_blur(images: Tensor, ksize: int, sigma: float) -> Tensor: + """Depthwise separable Gaussian blur of ``(B, C, H, W)`` images (reflect pad).""" + device, dtype = images.device, images.dtype + coords = torch.arange(ksize, device=device, dtype=dtype) - (ksize - 1) / 2.0 + k1d = torch.exp(-(coords**2) / (2.0 * sigma**2)) + k1d = k1d / k1d.sum() + c = images.shape[1] + pad = ksize // 2 + kx = k1d.view(1, 1, 1, ksize).expand(c, 1, 1, ksize) + ky = k1d.view(1, 1, ksize, 1).expand(c, 1, ksize, 1) + x = F.pad(images, (pad, pad, 0, 0), mode="reflect") + x = F.conv2d(x, kx, groups=c) + x = F.pad(x, (0, 0, pad, pad), mode="reflect") + x = F.conv2d(x, ky, groups=c) + return x + + +@torch.no_grad() +def occlusion_saliency( + images: Tensor, + embed_fn: Callable[[Tensor], Tensor], + *, + occ_size: int = 16, + stride: int = 8, + fill_value: float | str = 0.0, + batch_size: int = 32, + distance: str = "l2", +) -> Tensor: + """Compute occlusion-based saliency per image. + + Parameters + ---------- + images : Tensor + ``(B, C, H, W)`` input. All occlusions for a single image + share its baseline embedding. + embed_fn : callable + Function that maps ``(N, C, H, W)`` to ``(N, D)`` global + embeddings. Must work for arbitrary batch sizes. + occ_size : int + Side length of the square occlusion window in input pixels. + stride : int + Step between occlusion positions. Smaller stride = denser + saliency map but more forward passes. + fill_value : float or {'mean', 'zero', 'blur'} + How to fill the occluded square. ``'mean'`` uses the per-image + mean (best for z-scored input where ``0`` is already the FOV + mean). ``'zero'`` is a constant 0.0. A float is used verbatim. + ``'blur'`` replaces the square with a Gaussian-blurred copy of + the *same region* (a spatially varying fill), which preserves + gross shape / signed density while destroying fine texture — + the faithful occluder for texture/density models (e.g. phase), + with no hard-edged out-of-distribution flat square. + batch_size : int + How many occluded copies to forward through ``embed_fn`` at + once. Lower if you OOM. + distance : {'l2', 'cosine'} + How to measure the embedding shift. ``'l2'`` (default) returns + Euclidean distance; ``'cosine'`` returns ``1 - cos_sim``. + + Returns + ------- + Tensor + ``(B, H_out, W_out)`` saliency map at the occlusion-stride + resolution. Values are non-negative; higher = larger embedding + shift = more important region. + """ + if images.ndim != 4: + raise ValueError(f"expected (B, C, H, W) images, got shape {tuple(images.shape)}") + + device = images.device + b, c, h, w = images.shape + ys = _grid_positions(h, occ_size, stride) + xs = _grid_positions(w, occ_size, stride) + n_pos = len(ys) * len(xs) + + baseline = embed_fn(images) + if baseline.shape[0] != b: + raise RuntimeError(f"embed_fn returned batch {baseline.shape[0]} for input batch {b}") + + saliency = torch.zeros(b, len(ys), len(xs), device=device, dtype=baseline.dtype) + + # ``blurred`` is a full-image Gaussian-blurred copy used for the spatially + # varying 'blur' fill; ``fill`` is the broadcast scalar used otherwise. + blurred: Tensor | None = None + if isinstance(fill_value, str): + if fill_value == "mean": + fill = images.mean(dim=(2, 3), keepdim=True) # (B, C, 1, 1) + elif fill_value == "zero": + fill = torch.zeros(b, c, 1, 1, device=device, dtype=images.dtype) + elif fill_value == "blur": + # kernel/sigma scale with the occluder so the patch is fully smoothed. + ksize = max(3, (occ_size // 2) * 2 + 1) + sigma = occ_size / 3.0 + blurred = _gaussian_blur(images, ksize, sigma) + fill = torch.zeros(b, c, 1, 1, device=device, dtype=images.dtype) # unused + else: + raise ValueError(f"fill_value string must be 'mean', 'zero', or 'blur', got {fill_value!r}") + else: + fill = torch.full((b, c, 1, 1), float(fill_value), device=device, dtype=images.dtype) + + # Iterate one image at a time so we can batch occlusion positions + # without materializing (B * n_pos, C, H, W). + for i in range(b): + img = images[i : i + 1] + base = baseline[i : i + 1] + fill_i = fill[i : i + 1] + blurred_i = blurred[i : i + 1] if blurred is not None else None + + # Build all occluded copies for this image: (n_pos, C, H, W). + flat_positions = [(yy, xx) for yy in ys for xx in xs] + for start in range(0, n_pos, batch_size): + chunk = flat_positions[start : start + batch_size] + occluded = img.expand(len(chunk), -1, -1, -1).clone() + for k, (yy, xx) in enumerate(chunk): + if blurred_i is not None: + occluded[k, :, yy : yy + occ_size, xx : xx + occ_size] = blurred_i[ + :, :, yy : yy + occ_size, xx : xx + occ_size + ] + else: + occluded[k, :, yy : yy + occ_size, xx : xx + occ_size] = fill_i + occ_emb = embed_fn(occluded) + + if distance == "l2": + d = (occ_emb - base).norm(dim=-1) + elif distance == "cosine": + d = 1.0 - F.cosine_similarity(occ_emb, base.expand_as(occ_emb), dim=-1) + elif distance == "signed_delta": + # Signed difference for scalar-output embed_fns (typically + # a single-class probability). Positive = occlusion *raises* + # the score, negative = occlusion *drops* it. Pair with a + # diverging colormap centred at 0 (e.g. icefire). + if occ_emb.ndim != 2 or occ_emb.shape[-1] != 1: + raise ValueError( + f"distance='signed_delta' requires (B, 1) output from embed_fn, got {tuple(occ_emb.shape)}" + ) + d = (occ_emb - base).squeeze(-1) + else: + raise ValueError(f"unknown distance {distance!r}") + + for k, (yy, xx) in enumerate(chunk): + yi = yy // stride + xi = xx // stride + saliency[i, yi, xi] = d[k] + + return saliency + + +def saliency_to_rgb( + saliency: Tensor, + *, + cmap: str = "magma", + clip_percentile: tuple[float, float] | None = (2.0, 98.0), + clip_value: float | None = None, + upsample_to: tuple[int, int] | None = None, + interp_mode: str = "nearest", +) -> Tensor: + """Render a ``(B, H, W)`` saliency map as ``(B, 3, H_out, W_out)`` RGB. + + Parameters + ---------- + saliency : Tensor + ``(B, H, W)`` saliency. May be signed (e.g. signed_delta + attribution) — pair with a diverging cmap and ``clip_value``. + cmap : str + Any matplotlib or seaborn colormap name. ``'icefire'`` is + recommended for signed attribution; ``'magma'`` for unsigned + magnitude. + clip_percentile : (low, high) or None + Per-image percentile clipping → ``[0, 1]`` rescale. Used when + ``clip_value`` is None. Default ``(2, 98)``. + clip_value : float or None + If given, *fixed* symmetric clipping to ``[-clip_value, + clip_value]`` mapped to ``[0, 1]``. Use this for signed + attribution so zero stays at the colormap centre and the same + scale applies across frames — colors are then comparable + across batch / time. Overrides ``clip_percentile``. + upsample_to : tuple[int, int] or None + If given, resize the colored map to ``(H, W)`` via the chosen + ``interp_mode``. + interp_mode : {'nearest', 'bilinear'} + Upsampling kernel. Default ``'nearest'`` keeps occlusion-grid + cells crisp. + + Returns + ------- + Tensor + ``(B, 3, H_out, W_out)`` float tensor in ``[0, 1]``. + """ + import matplotlib + + if saliency.ndim != 3: + raise ValueError(f"expected (B, H, W), got {tuple(saliency.shape)}") + + sal = saliency.detach().cpu().float() + b = sal.shape[0] + out = [] + try: + cm = matplotlib.colormaps.get_cmap(cmap) + except (ValueError, KeyError): + # Fall back to seaborn for names matplotlib doesn't ship (e.g. icefire). + import seaborn as sns + + cm = sns.color_palette(cmap, as_cmap=True) + for i in range(b): + s = sal[i] + if clip_value is not None: + cv = float(clip_value) + s = ((s.clamp(-cv, cv) + cv) / (2 * cv)).clamp(0.0, 1.0) + elif clip_percentile is not None: + lo = s.quantile(clip_percentile[0] / 100.0) + hi = s.quantile(clip_percentile[1] / 100.0) + s = ((s - lo) / (hi - lo).clamp(min=1e-8)).clamp(0.0, 1.0) + else: + lo, hi = s.min(), s.max() + s = ((s - lo) / (hi - lo).clamp(min=1e-8)).clamp(0.0, 1.0) + rgba = cm(s.numpy()) # (H, W, 4) + out.append(torch.from_numpy(rgba[..., :3]).permute(2, 0, 1)) + rgb = torch.stack(out, dim=0).to(saliency.device) + + if upsample_to is not None: + if interp_mode == "nearest": + rgb = F.interpolate(rgb, size=upsample_to, mode="nearest") + elif interp_mode == "bilinear": + rgb = F.interpolate(rgb, size=upsample_to, mode="bilinear", align_corners=False) + else: + raise ValueError(f"unknown interp_mode {interp_mode!r}") + return rgb.clamp(0.0, 1.0) diff --git a/packages/viscy-utils/src/viscy_utils/visualization/pca_rgb.py b/packages/viscy-utils/src/viscy_utils/visualization/pca_rgb.py new file mode 100644 index 000000000..83a408780 --- /dev/null +++ b/packages/viscy-utils/src/viscy_utils/visualization/pca_rgb.py @@ -0,0 +1,224 @@ +"""DINO-style PCA-RGB visualization of dense patch features. + +Pure tensor math — no model imports. Given dense patch features +``(B, N, D)`` from any vision backbone (ViT patch tokens, CNN feature map +flattened over space), fits PCA, takes the top three components, and +paints each spatial location with the corresponding RGB triple. Optional +foreground masking via the first principal component reproduces the +original DINO paper's "object-centric" overlay. + +The technique works on any backbone that produces dense features — +ViT, ConvNeXt, MAE encoders — and is not tied to attention. +""" + +from __future__ import annotations + +import math +from typing import Literal + +import torch +import torch.nn.functional as F +from torch import Tensor + +PCAMode = Literal["per_image", "batch", "reference"] + + +def fit_pca_components( + patches: Tensor, + n_components: int = 3, +) -> tuple[Tensor, Tensor]: + """Fit a PCA basis on a flat ``(M, D)`` patch matrix. + + Parameters + ---------- + patches : Tensor + ``(M, D)`` patch features. Caller is responsible for pooling + across whichever images / batches should share the basis. + n_components : int + Number of principal components to retain, by default ``3``. + + Returns + ------- + mean : Tensor + ``(D,)`` per-feature mean used to center new data. + components : Tensor + ``(D, n_components)`` orthonormal basis ordered by descending + explained variance. + """ + if patches.ndim != 2: + raise ValueError(f"expected (M, D) patches, got shape {tuple(patches.shape)}") + mean = patches.mean(dim=0) + centered = patches - mean + q = max(n_components + 4, n_components * 2) + q = min(q, min(centered.shape)) + _, _, v = torch.pca_lowrank(centered, q=q, center=False) + return mean, v[:, :n_components].contiguous() + + +def _project(patches: Tensor, mean: Tensor, components: Tensor) -> Tensor: + """Project ``(B, N, D)`` patches to ``(B, N, K)`` PC scores.""" + return (patches - mean) @ components + + +def _minmax_normalize( + scores: Tensor, + dim: tuple[int, ...], + clip_percentile: tuple[float, float] | None = None, +) -> Tensor: + """Min-max scale ``scores`` to ``[0, 1]`` over the given dims. + + Parameters + ---------- + scores : Tensor + dim : tuple of int + Dims to reduce when computing the per-channel min/max. + clip_percentile : (low, high) in [0, 100] or None + If given, use the ``low``/``high`` percentile across ``dim`` + instead of strict min/max, then clip the output to ``[0, 1]``. + Robust to outlier patches. ``None`` (default) uses strict + ``amin``/``amax``. + """ + if clip_percentile is None: + lo = scores.amin(dim=dim, keepdim=True) + hi = scores.amax(dim=dim, keepdim=True) + return (scores - lo) / (hi - lo).clamp(min=1e-8) + + low_q, high_q = clip_percentile + if not (0.0 <= low_q < high_q <= 100.0): + raise ValueError(f"clip_percentile must be (low, high) with 0 <= low < high <= 100, got {clip_percentile}") + # torch.quantile only collapses one dim at a time; iterate. + lo = scores + hi = scores + for d in sorted(dim, reverse=True): + lo = lo.quantile(low_q / 100.0, dim=d, keepdim=True) + hi = hi.quantile(high_q / 100.0, dim=d, keepdim=True) + return ((scores - lo) / (hi - lo).clamp(min=1e-8)).clamp(0.0, 1.0) + + +def _infer_grid_hw(n_patches: int) -> tuple[int, int]: + """Infer ``(H, W)`` from a square patch count.""" + side = int(round(math.sqrt(n_patches))) + if side * side != n_patches: + raise ValueError(f"cannot infer square grid from {n_patches} patches; pass grid_hw explicitly") + return side, side + + +def pca_rgb_from_patches( + patches: Tensor, + *, + mode: PCAMode = "batch", + mask_fg: bool = True, + fg_threshold: float = 0.5, + components: Tensor | None = None, + mean: Tensor | None = None, + grid_hw: tuple[int, int] | None = None, + upsample_to: tuple[int, int] | None = None, + bg_color: tuple[float, float, float] = (1.0, 1.0, 1.0), + clip_percentile: tuple[float, float] | None = None, + interp_mode: Literal["nearest", "bilinear"] = "nearest", +) -> Tensor: + """Render DINO-style PCA-RGB overlays from dense patch features. + + Parameters + ---------- + patches : Tensor + ``(B, N, D)`` patch features. ``N`` is the number of spatial + locations (e.g. 196 for a ViT-L/16 at 224 px), ``D`` is the + feature dimension. + mode : {'per_image', 'batch', 'reference'} + How the PCA basis is fit. + + - ``'per_image'`` — one PCA per image. Colors are not comparable + across images. + - ``'batch'`` (default) — fit one PCA on patches pooled across + the batch. Colors are consistent within the batch. + - ``'reference'`` — use the basis passed via ``components`` / + ``mean``. Colors are consistent across any data projected + with the same basis. + + mask_fg : bool + If ``True`` (default), patches whose first principal component + is below ``fg_threshold`` (after min-max normalization to + ``[0,1]``) are painted with ``bg_color`` instead of their RGB + triple. This reproduces the original DINO paper's foreground + masking. + fg_threshold : float + Threshold on the normalized PC1 score, by default ``0.5`` + (the median). + components, mean : Tensor or None + ``(D, 3)`` basis and ``(D,)`` mean for ``mode='reference'``. + Ignored otherwise. Use :func:`fit_pca_components` to compute + them on a held-out set. + grid_hw : tuple[int, int] or None + Spatial layout of the patches. Inferred as ``(sqrt(N), sqrt(N))`` + when ``None``. + upsample_to : tuple[int, int] or None + If given, bilinearly upsample the RGB grid to ``(H, W)`` before + returning. + bg_color : tuple[float, float, float] + RGB triple in ``[0, 1]`` used for masked-out background patches, + by default white. + clip_percentile : (low, high) in [0, 100] or None + If given, clip PC scores to the ``low``/``high`` percentile per + channel before scaling to ``[0, 1]``. Robust to outlier patches + (debris, FOV-edge artifacts) that would otherwise peg the RGB + range. ``None`` (default) uses strict min/max — fine for clean + data, but a few outliers will wash everything else out. + interp_mode : {'nearest', 'bilinear'} + Upsampling kernel used when ``upsample_to`` is set. Defaults to + ``'nearest'`` so the output honestly reflects the model's patch + grid (e.g. a 14×14 ViT-L/16 grid renders as crisp 16×16-pixel + squares at 224 px output). ``'bilinear'`` smooths the grid into + a continuous heatmap, which is prettier but lies about spatial + resolution. + + Returns + ------- + Tensor + ``(B, 3, H, W)`` RGB tensor in ``[0, 1]``. ``H, W`` is + ``upsample_to`` if given, otherwise ``grid_hw``. + """ + if patches.ndim != 3: + raise ValueError(f"expected (B, N, D) patches, got shape {tuple(patches.shape)}") + b, n, d = patches.shape + grid_h, grid_w = grid_hw if grid_hw is not None else _infer_grid_hw(n) + if grid_h * grid_w != n: + raise ValueError(f"grid_hw {grid_h}x{grid_w} = {grid_h * grid_w} != N={n}") + + if mode == "reference": + if components is None or mean is None: + raise ValueError("mode='reference' requires components and mean") + if components.shape != (d, 3): + raise ValueError(f"components must be (D=3, 3), got {tuple(components.shape)}") + scores = _project(patches, mean.to(patches), components.to(patches)) + elif mode == "batch": + flat = patches.reshape(b * n, d) + m, comps = fit_pca_components(flat, n_components=3) + scores = _project(patches, m, comps) + elif mode == "per_image": + scores_list = [] + for i in range(b): + m, comps = fit_pca_components(patches[i], n_components=3) + scores_list.append(_project(patches[i : i + 1], m, comps)) + scores = torch.cat(scores_list, dim=0) + else: + raise ValueError(f"unknown mode: {mode!r}") + + pc1 = _minmax_normalize(scores[..., 0:1], dim=(1,), clip_percentile=clip_percentile) + rgb_norm_dim = (1,) if mode == "per_image" else (0, 1) + rgb = _minmax_normalize(scores, dim=rgb_norm_dim, clip_percentile=clip_percentile) + + if mask_fg: + keep = (pc1 >= fg_threshold).to(rgb) + bg = torch.tensor(bg_color, device=rgb.device, dtype=rgb.dtype).view(1, 1, 3) + rgb = keep * rgb + (1 - keep) * bg + + rgb_grid = rgb.transpose(1, 2).reshape(b, 3, grid_h, grid_w) + if upsample_to is not None: + if interp_mode == "nearest": + rgb_grid = F.interpolate(rgb_grid, size=upsample_to, mode="nearest") + elif interp_mode == "bilinear": + rgb_grid = F.interpolate(rgb_grid, size=upsample_to, mode="bilinear", align_corners=False) + else: + raise ValueError(f"unknown interp_mode {interp_mode!r}; expected 'nearest' or 'bilinear'") + return rgb_grid.clamp(0.0, 1.0) From 1ac6b49939eddf80fd6760c033edfc8892bbd45a Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Wed, 22 Jul 2026 14:30:49 -0700 Subject: [PATCH 51/89] feat(dynaclr): expose occluder fill in occlusion report CONFIG Add a "fill" key to the report CONFIG (default "zero") passed through to occlusion_saliency, so runs can select zero / mean / blur / float without editing code. Under z-score normalization, "zero" fills the occluded patch with the background mean; "blur" preserves gross shape/density and removes only fine texture (faithful alternative for phase). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../explainability/occlusion/occlusion_infection_report.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py b/applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py index bcf535720..d3a296358 100644 --- a/applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py +++ b/applications/dynaclr/scripts/explainability/occlusion/occlusion_infection_report.py @@ -55,6 +55,9 @@ # occlusion "occ_size": 8, "stride": 4, + "fill": "zero", # occluder fill: "zero" | "mean" | "blur" | float. "blur" preserves + # gross shape/density and removes only fine texture (faithful for phase); "zero"/"mean" + # erase to background (≈0 under z-score). See occlusion_saliency docstring. "distance": "signed_delta", # signed_delta (classifier) | l2 | cosine (embedding) "cmap": "icefire", "clip_value": 0.2, @@ -273,7 +276,7 @@ def main(cfg: dict[str, Any]) -> None: clf_fn, occ_size=int(cfg["occ_size"]), stride=int(cfg["stride"]), - fill_value=0.0, + fill_value=cfg.get("fill", "zero"), batch_size=64, distance=cfg["distance"], ) From 32bf4f0f14d7c10d5465bc7a9cf774428500afe8 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 23 Jul 2026 09:22:48 -0700 Subject: [PATCH 52/89] Revert "config(dynaclr): add Zuben gut preparation and training recipes" This reverts commit 576c124bd992a5023f46211184e83d2919bf0075. --- .../collections/zuben_gut/conform_parquet.py | 64 -------- .../collections/zuben_gut/gen_convert_v3.py | 92 ----------- .../collections/zuben_gut/gen_preprocess.py | 56 ------- .../zuben_gut/verify_parquet_zarr.py | 80 ---------- .../DynaCLR-3D-Gut-BagOfChannels.sh | 47 ------ .../DynaCLR-3D-Gut-BagOfChannels.yml | 151 ------------------ .../DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh | 43 ----- .../DynaCLR-3D-Gut-MultiChannel.yml | 147 ----------------- 8 files changed, 680 deletions(-) delete mode 100644 applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py delete mode 100644 applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py delete mode 100644 applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py delete mode 100644 applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py delete mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh delete mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml delete mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh delete mode 100644 applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml diff --git a/applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py b/applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py deleted file mode 100644 index 190421970..000000000 --- a/applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Step 2: conform Zuben's cell-index parquet to the canonical schema (end-to-end). - -Single authoritative step, run after the v3 stores are preprocessed (Step 1): - -1. Repoint ``store_path`` from the v2 originals to the v3 copies (same basename). -2. Fill required/derived columns absent from the source - (``tracks_path``, ``microscope``, ``T_shape``, ``C_shape``). -3. ``preprocess_cell_index`` fills the ``norm_*`` columns from the v3 ``.zattrs``. -4. Seed ``z_focus = z`` LAST — gut has no ``focus_slice`` zattrs, so the bbox-center - ``z`` IS the focus. This must come after step 3 because ``preprocess_cell_index`` - also writes ``z_focus`` (as NaN here, since there is no focus_slice) and would - otherwise clobber it. The datamodule centers the Z window on ``z_focus``. - -Run:: - - uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/conform_parquet.py -""" - -import os - -import pandas as pd - -from viscy_data.cell_index import preprocess_cell_index, read_cell_index, write_cell_index - -SRC = "/hpc/projects/jacobo_group/zuben/proj/gutCellClassifier/data/dynaclr_cell_index_bbox_center.parquet" -V3_ROOT = "/hpc/projects/organelle_phenotyping/datasets/zuben_gut_development" -OUT = "/hpc/projects/jacobo_group/collab/ed/dynaclr/dynaclr_cell_index_gut_v1.parquet" -N_CHANNELS = 4 - - -def main() -> None: - """Conform the parquet, fill norm stats, and seed z_focus from the bbox-center z.""" - df = pd.read_parquet(SRC) - n_rows_in = len(df) - - # Repoint store_path to the v3 copies (same basename). - df["store_path"] = df["store_path"].astype(str).map(lambda p: f"{V3_ROOT}/{os.path.basename(p)}") - - # Required columns absent from the source parquet. - df["tracks_path"] = "" # ignored by ExperimentRegistry.from_cell_index - df["microscope"] = "" - df["T_shape"] = 1 # static: single timepoint - df["C_shape"] = N_CHANNELS - - write_cell_index(df, OUT) - - # Fill norm_* from the v3 .zattrs (writes z_focus as NaN — no focus_slice). - preprocess_cell_index(OUT, focus_channel="nuclear") - - # Seed z_focus = z LAST so it survives preprocess_cell_index. - out_df = read_cell_index(OUT) - out_df["z_focus"] = out_df["z"].astype("float32") - write_cell_index(out_df, OUT) - - check = read_cell_index(OUT) - print("# Step 2 — conform parquet\n") - print(f"- rows in: **{n_rows_in}**, rows out: **{len(check)}**") - print(f"- output: `{OUT}`") - print(f"- norm_mean NaN: **{int(check['norm_mean'].isna().sum())}**") - print(f"- z_focus NaN: **{int(check['z_focus'].isna().sum())}** (should be 0)") - - -if __name__ == "__main__": - main() diff --git a/applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py b/applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py deleted file mode 100644 index 9a3c34c09..000000000 --- a/applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Generate per-store biahub-concatenate configs to copy Zuben's 25 v2 gut stores to v3. - -Each of Zuben's stores is one gut = one experiment = a single position ``A/1/0`` with the -four channels ``[nuclear, septate, brush_border, SuH]``. We convert each store to its own -v3 store under ``OUTPUT_ROOT`` preserving the original basename and the ``A/1/0`` layout, so -the cell-index parquet's ``(store_path, well, fov)`` mapping stays valid — only the -``store_path`` directory changes. - -Emits, per store, ``configs/.yml`` and a single ``submit_all.sh`` that runs -``biahub concatenate`` (conda env ``biautils``) for every store as SLURM jobs. - -Run:: - - uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/gen_convert_v3.py - bash /convert_v3/submit_all.sh -""" - -import os -from pathlib import Path - -import pandas as pd - -PARQUET = "/hpc/projects/jacobo_group/zuben/proj/gutCellClassifier/data/dynaclr_cell_index_bbox_center.parquet" -OUTPUT_ROOT = "/hpc/projects/organelle_phenotyping/datasets/zuben_gut_development" -CHANNELS = ["nuclear", "septate", "brush_border", "SuH"] -CHUNKS_CZYX = [1, 16, 256, 256] -SHARDS_RATIO = [1, 1, 4, 8, 8] -OME_VERSION = "0.5" -BIAHUB_ENV = "biautils" - -HERE = Path(__file__).resolve().parent -WORKDIR = HERE / "convert_v3" -CONFIGS_DIR = WORKDIR / "configs" - - -def _yaml_config(store_path: str) -> str: - import yaml - - cfg = { - # One input FOV glob per data path: this store's single position A/1/0. - "concat_data_paths": [f"{store_path}/A/1/*"], - "time_indices": "all", - # List-of-lists: one channel list per data path (here, one path). - "channel_names": [list(CHANNELS)], - "X_slice": "all", - "Y_slice": "all", - "Z_slice": "all", - "chunks_czyx": list(CHUNKS_CZYX), - "shards_ratio": list(SHARDS_RATIO), - "output_ome_zarr_version": OME_VERSION, - } - return yaml.safe_dump(cfg, default_flow_style=False, sort_keys=False) - - -def main() -> None: - """Emit one biahub-concatenate config per store plus a SLURM submit driver.""" - df = pd.read_parquet(PARQUET, columns=["store_path"]) - stores = sorted(df["store_path"].astype(str).unique()) - - CONFIGS_DIR.mkdir(parents=True, exist_ok=True) - submit_lines = [ - "#!/bin/bash", - "set -euo pipefail", - "# Auto-generated by gen_convert_v3.py. Submits one biahub concatenate per store.", - f"mkdir -p {OUTPUT_ROOT}", - "", - ] - - for store in stores: - stem = os.path.basename(store) # e.g. AAY6_sox21a_d0_63x_gut1.zarr - cfg_path = CONFIGS_DIR / f"{stem.replace('.zarr', '')}.yml" - cfg_path.write_text(_yaml_config(store)) - out_path = f"{OUTPUT_ROOT}/{stem}" - submit_lines.append(f'echo "=== {stem} ==="') - submit_lines.append( - f"conda run -n {BIAHUB_ENV} biahub concatenate " - f'-c "{cfg_path}" -o "{out_path}" -m -sb "{WORKDIR / "sbatch_overrides.sh"}"' - ) - submit_lines.append("") - - (WORKDIR / "submit_all.sh").write_text("\n".join(submit_lines) + "\n") - (WORKDIR / "sbatch_overrides.sh").write_text( - "#!/bin/bash\n#SBATCH --partition=cpu\n#SBATCH --cpus-per-task=4\n#SBATCH --mem-per-cpu=32G\n" - ) - - print(f"Generated {len(stores)} configs under {CONFIGS_DIR}") - print(f"Submit driver: {WORKDIR / 'submit_all.sh'}") - print(f"Output root: {OUTPUT_ROOT}") - - -if __name__ == "__main__": - main() diff --git a/applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py b/applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py deleted file mode 100644 index cb4ee68be..000000000 --- a/applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Generate + submit `viscy preprocess` SLURM jobs for the 25 v3 gut stores. - -Writes per-channel normalization stats (fov/dataset/timepoint) into each store's -``.zattrs``. One SLURM job per store (each store is a single large FOV). - -Run:: - - uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/gen_preprocess.py - bash /preprocess/submit_all.sh -""" - -import glob -from pathlib import Path - -V3_ROOT = "/hpc/projects/organelle_phenotyping/datasets/zuben_gut_development" -REPO = "/hpc/mydata/eduardo.hirata/repos/viscy" -VENV = f"{REPO}/.venv-dynaclr" -HERE = Path(__file__).resolve().parent -WORKDIR = HERE / "preprocess" - -JOB_TEMPLATE = """#!/bin/bash -#SBATCH --job-name=pp_{stem} -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=1 -#SBATCH --partition=cpu -#SBATCH --cpus-per-task=32 -#SBATCH --mem-per-cpu=4G -#SBATCH --time=02:00:00 -#SBATCH --output={workdir}/slurm_pp_{stem}_%j.out - -export PYTHONNOUSERSITE=1 -export UV_PROJECT_ENVIRONMENT={venv} - -uv run --project "{repo}" --package dynaclr \\ - viscy preprocess --data_path "{store}" \\ - --channel_names=-1 --num_workers 32 --block_size 32 -""" - - -def main() -> None: - """Emit one `viscy preprocess` SLURM job per v3 store plus a submit driver.""" - WORKDIR.mkdir(parents=True, exist_ok=True) - stores = sorted(glob.glob(f"{V3_ROOT}/*.zarr")) - submit = ["#!/bin/bash", "set -euo pipefail", ""] - for store in stores: - stem = Path(store).name.replace(".zarr", "") - job = WORKDIR / f"pp_{stem}.sh" - job.write_text(JOB_TEMPLATE.format(stem=stem, workdir=WORKDIR, venv=VENV, repo=REPO, store=store)) - submit.append(f"sbatch {job}") - (WORKDIR / "submit_all.sh").write_text("\n".join(submit) + "\n") - print(f"Generated {len(stores)} preprocess jobs under {WORKDIR}") - print(f"Submit: bash {WORKDIR / 'submit_all.sh'}") - - -if __name__ == "__main__": - main() diff --git a/applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py b/applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py deleted file mode 100644 index 9b16514fe..000000000 --- a/applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Step 0: verify Zuben's cell-index parquet is consistent with its zarr stores. - -Checks, for every store referenced in the parquet: -- the store opens and exposes the expected channel names, -- each (well, fov) referenced resolves to a real position, -- cell centroids fit inside the FOV with room for the requested patch half-width. - -Run:: - - uv run --no-sync python applications/dynaclr/configs/collections/zuben_gut/verify_parquet_zarr.py -""" - -import sys - -import pandas as pd -from iohub import open_ome_zarr - -PARQUET = "/hpc/projects/jacobo_group/zuben/proj/gutCellClassifier/data/dynaclr_cell_index_bbox_center.parquet" -EXPECTED_CHANNELS = ["nuclear", "septate", "brush_border", "SuH"] -YX_PATCH_SIZE = (256, 256) # extraction patch used by the bag-of-channels config - - -def main() -> int: - """Check every store opens, channels match, positions resolve, and report border-OOB cells.""" - df = pd.read_parquet(PARQUET) - y_half = YX_PATCH_SIZE[0] // 2 - x_half = YX_PATCH_SIZE[1] // 2 - - problems: list[str] = [] - n_cells_out_of_bounds = 0 - - for store_path, store_group in df.groupby("store_path", observed=True): - store_path = str(store_path) - try: - with open_ome_zarr(store_path, mode="r") as plate: - channels = list(plate.channel_names) - if channels != EXPECTED_CHANNELS: - problems.append(f"{store_path}: channels {channels} != {EXPECTED_CHANNELS}") - positions = {name for name, _ in plate.positions()} - except Exception as exc: # noqa: BLE001 - surface any open failure - problems.append(f"{store_path}: failed to open ({exc})") - continue - - for (well, fov), fov_group in store_group.groupby(["well", "fov"], observed=True): - pos_key = f"{well}/{fov}" - if pos_key not in positions: - problems.append(f"{store_path}: position {pos_key} not found") - continue - y_shape = int(fov_group["Y_shape"].iloc[0]) - x_shape = int(fov_group["X_shape"].iloc[0]) - y = fov_group["y"].to_numpy() - x = fov_group["x"].to_numpy() - oob = (y < y_half) | (y > y_shape - y_half) | (x < x_half) | (x > x_shape - x_half) - n_cells_out_of_bounds += int(oob.sum()) - - n_stores = df["store_path"].nunique() - n_cells = df["cell_id"].nunique() - - print("# Step 0 — parquet↔zarr consistency\n") - print(f"- stores checked: **{n_stores}**") - print(f"- unique cells: **{n_cells}**") - print(f"- expected channels: `{EXPECTED_CHANNELS}`") - print( - f"- cells whose {YX_PATCH_SIZE} patch would fall out of bounds: " - f"**{n_cells_out_of_bounds}** " - f"({100 * n_cells_out_of_bounds / len(df):.1f}% of rows)" - ) - - if problems: - print(f"\n**{len(problems)} problem(s):**") - for p in problems[:50]: - print(f" - {p}") - return 1 - - print("\n**OK** — all stores open, channels match, positions resolve.") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh deleted file mode 100644 index c3617fb11..000000000 --- a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -# DynaCLR-3D-Gut-BagOfChannels (Phase 1) — Zuben gut cells, bag-of-channels SimCLR. -# -# New run: -# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh -# Resume: -# CKPT_PATH=.../last.ckpt WANDB_RUN_ID= sbatch .../DynaCLR-3D-Gut-BagOfChannels.sh - -#SBATCH --job-name=dynaclr_gut_boc -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=2 -#SBATCH --gpus-per-node=2 -#SBATCH --partition=gpu -#SBATCH --cpus-per-task=15 -#SBATCH --mem-per-cpu=8G -#SBATCH --time=2-00:00:00 - -# Set WORKSPACE_DIR to YOUR clone of the repo before submitting, e.g. -# WORKSPACE_DIR=/hpc/mydata//repos/VisCy sbatch .sh -# MODEL_ROOT is where checkpoints/configs are written (defaults to your clone's -# models/ dir; override to a shared project path if desired). -WORKSPACE_DIR="${WORKSPACE_DIR:?Set WORKSPACE_DIR to your repo clone path}" - -export PROJECT="DynaCLR-3D-Gut-BagOfChannels" -export RUN_NAME="gut-3d-z24-cellz-64-ntxent-t0p2-self" -export CONFIGS="applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml" -export MODEL_ROOT="${MODEL_ROOT:-${WORKSPACE_DIR}/models}" -# Point at the dynaclr-pinned venv in your clone (avoids the shared .venv sync race). -export UV_PROJECT_ENVIRONMENT="${UV_PROJECT_ENVIRONMENT:-${WORKSPACE_DIR}/.venv-dynaclr}" - -# W&B writes sample images to a temp dir under $TMPDIR before upload. On some -# SLURM nodes the default /tmp is per-job and gets swept mid-run, causing -# `FileNotFoundError: .../wandb-media/*.png` at validation image logging. Pin -# TMPDIR + WANDB_DIR to persistent paths we create so they can't disappear. -export TMPDIR="${TMPDIR:-${MODEL_ROOT}/tmp}" -export WANDB_DIR="${WANDB_DIR:-${MODEL_ROOT}/wandb}" -mkdir -p "$TMPDIR" "$WANDB_DIR" - -# The shared trainer recipe logs to the `computational_imaging` W&B entity. Set -# WANDB_ENTITY to your own entity to log there instead (EXTRA_ARGS overrides the -# recipe). Leave unset to keep the default. -if [ -n "${WANDB_ENTITY:-}" ]; then - export EXTRA_ARGS="${EXTRA_ARGS:-} --trainer.logger.init_args.entity=${WANDB_ENTITY}" -fi - -# Absolute path (SLURM spools this script, so $(dirname "$0") would break). -source "${WORKSPACE_DIR}/applications/dynaclr/configs/training/slurm/train.sh" diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml deleted file mode 100644 index b019e8cd7..000000000 --- a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.yml +++ /dev/null @@ -1,151 +0,0 @@ -# DynaCLR-3D-Gut-BagOfChannels (Phase 1) -# ======================================= -# 3D bag-of-channels contrastive learning on Zuben's zebrafish gut data. -# One random channel per sample (nuclear | septate | brush_border | SuH), -# 24-slice Z window, 64x64 XY — matches Zuben's 24x64x64 patch convention. -# -# STATIC DATA: single timepoint (t=0), no tracking. SimCLR self-positives -# (same crop → same marker). One marker per batch (batch_group_by=marker) so the -# model can't shortcut on channel identity; batches balanced across the 6 states -# (stratify_by=perturbation) to counter the ~3.5x state imbalance. -# (Phase 3, later: consecutive-state positives to regularize the space temporally.) -# -# Normalization reads per-FOV full-frame stats from the v3 .zattrs written by -# `viscy preprocess`. Single timepoint → timepoint_statistics == fov_statistics. -# -# Z window centered on the parquet `z_focus` column (seeded from bbox-center z -# by conform_parquet.py), 24 slices, no random Z crop. XY extracted at 80 then -# center-cropped to 64. -# Pipeline: extract (24,80,80) → normalize → affine → flip/contrast/noise -# → CenterCrop (24,64,64) [auto-appended]. -# -# Launch: -# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-BagOfChannels.sh - -base: - - ../recipes/trainer/fit.yml - - ../recipes/topology/ddp_2gpu.yml - - ../recipes/model/contrastive_encoder_convnext_tiny.yml - -trainer: - precision: bf16-mixed - max_epochs: 150 - logger: - init_args: - project: DynaCLR-3D-Gut-BagOfChannels - name: gut-3d-z24-96to80to64-ntxent-t0p2-self - # Re-list callbacks WITHOUT OnlineEvalCallback (static data → degenerate - # temporal kNN eval). Loss/val + post-hoc PCA/UMAP colored by stage/marker - # is the evaluation signal for Phase 1. - callbacks: - - class_path: lightning.pytorch.callbacks.LearningRateMonitor - init_args: - logging_interval: step - - class_path: lightning.pytorch.callbacks.ModelCheckpoint - init_args: - monitor: loss/val - every_n_epochs: 1 - save_top_k: 5 - save_last: true - -model: - init_args: - encoder: - init_args: - in_stack_depth: 24 - stem_kernel_size: [4, 4, 4] - stem_stride: [4, 4, 4] - projection_dim: 32 - drop_path_rate: 0.1 - loss_function: - init_args: - temperature: 0.2 - lr: 0.00002 - pca_color_keys: "[perturbation,experiment,marker]" - # PCA pairplot cadence (overrides the base recipe's 10). Watch the 6 states - # separate early. - log_embeddings_every_n_epochs: 5 - log_negative_metrics_every_n_epochs: 2 - example_input_array_shape: [1, 1, 24, 64, 64] - -data: - class_path: dynaclr.data.datamodule.MultiExperimentDataModule - init_args: - cell_index_path: /hpc/projects/jacobo_group/collab/ed/dynaclr/dynaclr_cell_index_gut_v1.parquet - focus_channel: null - # The datamodule centers the Z window on the parquet `z_focus` column; - # conform_parquet.py seeds z_focus = bbox-center z (gut cells span the full - # stack within one store, so per-FOV focus won't do). 24-slice window, - # symmetric (offset 0.5), no Z rescale (extraction == window), no random crop. - z_window: 24 - z_extraction_window: 24 - z_focus_offset: 0.5 - # Small XY margin (80 → 64) so the auto-appended CenterCrop trims affine - # rotation zero-fill; no explicit random XY crop. - yx_patch_size: [80, 80] - final_yx_patch_size: [64, 64] - channels_per_sample: 1 - # SimCLR self-positive: anchor == positive (same crop, two augmentations) → - # trivially the SAME marker, and no supervised pull that would fight the - # negatives. State structure emerges unsupervised. - positive_cell_source: self - # One marker per batch: all negatives share the anchor's marker, so the model - # can't take the "tell channels apart" shortcut and must learn within-marker - # (cell-state) structure. - batch_group_by: marker - # Within the single-marker batch, balance the 6 developmental states — the - # dataset is ~3.5x imbalanced (stage_0 1379 vs stage_5 390 per marker). - stratify_by: [perturbation] - # Each gut store is a single FOV → split cells within each gut (not by FOV, - # which would leave 0 val FOVs). All rows of one cell stay on one side. - split_mode: cell - split_ratio: 0.8 - batch_size: 256 - num_workers: 4 - seed: 42 - normalizations: - - class_path: viscy_transforms.NormalizeSampled - init_args: - keys: [channel_0] - level: timepoint_statistics - subtrahend: mean - divisor: std - augmentations: - - class_path: viscy_transforms.BatchedRandAffined - init_args: - keys: [channel_0] - prob: 0.8 - scale_range: [[0.9, 1.1], [0.9, 1.1], [0.9, 1.1]] - rotate_range: [3.14, 0.0, 0.0] - shear_range: [0.05, 0.05, 0.0, 0.05, 0.0, 0.05] - # No random spatial crop — the Z window is already centered on the cell - # plane and XY is tight. The datamodule auto-appends a CenterCrop to - # [24, 64, 64], which also trims affine rotation zero-fill at the edges. - - class_path: viscy_transforms.BatchedRandFlipd - init_args: - keys: [channel_0] - spatial_axes: [1, 2] - prob: 0.5 - - class_path: viscy_transforms.BatchedRandAdjustContrastd - init_args: - keys: [channel_0] - prob: 0.5 - gamma: [0.6, 1.6] - - class_path: viscy_transforms.BatchedRandScaleIntensityd - init_args: - keys: [channel_0] - prob: 0.5 - factors: 0.5 - - class_path: viscy_transforms.BatchedRandGaussianSmoothd - init_args: - keys: [channel_0] - prob: 0.5 - sigma_x: [0.25, 0.50] - sigma_y: [0.25, 0.50] - sigma_z: [0.0, 0.2] - - class_path: viscy_transforms.BatchedRandGaussianNoised - init_args: - keys: [channel_0] - prob: 0.5 - mean: 0.0 - std: 0.1 diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh deleted file mode 100644 index d23dd4b45..000000000 --- a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# DynaCLR-3D-Gut-MultiChannel (Phase 2) — Zuben gut cells, 4-channel input. -# -# New run: -# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh -# Resume: -# CKPT_PATH=.../last.ckpt WANDB_RUN_ID= sbatch .../DynaCLR-3D-Gut-MultiChannel.sh - -#SBATCH --job-name=dynaclr_gut_4ch -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=2 -#SBATCH --gpus-per-node=2 -#SBATCH --partition=gpu -#SBATCH --cpus-per-task=15 -#SBATCH --mem-per-cpu=8G -#SBATCH --time=2-00:00:00 - -# Set WORKSPACE_DIR to YOUR clone of the repo before submitting, e.g. -# WORKSPACE_DIR=/hpc/mydata//repos/VisCy sbatch .sh -WORKSPACE_DIR="${WORKSPACE_DIR:?Set WORKSPACE_DIR to your repo clone path}" - -export PROJECT="DynaCLR-3D-Gut-MultiChannel" -export RUN_NAME="gut-3d-4ch-z24-cellz-64-ntxent-t0p2-self" -export CONFIGS="applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml" -export MODEL_ROOT="${MODEL_ROOT:-${WORKSPACE_DIR}/models}" -export UV_PROJECT_ENVIRONMENT="${UV_PROJECT_ENVIRONMENT:-${WORKSPACE_DIR}/.venv-dynaclr}" - -# W&B writes sample images to a temp dir under $TMPDIR before upload. On some -# SLURM nodes the default /tmp is per-job and gets swept mid-run, causing -# `FileNotFoundError: .../wandb-media/*.png` at validation image logging. Pin -# TMPDIR + WANDB_DIR to persistent paths we create so they can't disappear. -export TMPDIR="${TMPDIR:-${MODEL_ROOT}/tmp}" -export WANDB_DIR="${WANDB_DIR:-${MODEL_ROOT}/wandb}" -mkdir -p "$TMPDIR" "$WANDB_DIR" - -# The shared trainer recipe logs to the `computational_imaging` W&B entity. Set -# WANDB_ENTITY to your own entity to log there instead. Leave unset for default. -if [ -n "${WANDB_ENTITY:-}" ]; then - export EXTRA_ARGS="${EXTRA_ARGS:-} --trainer.logger.init_args.entity=${WANDB_ENTITY}" -fi - -# Absolute path (SLURM spools this script, so $(dirname "$0") would break). -source "${WORKSPACE_DIR}/applications/dynaclr/configs/training/slurm/train.sh" diff --git a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml b/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml deleted file mode 100644 index 269d8628b..000000000 --- a/applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.yml +++ /dev/null @@ -1,147 +0,0 @@ -# DynaCLR-3D-Gut-MultiChannel (Phase 2) -# ====================================== -# 4-channel contrastive learning on Zuben's zebrafish gut data. -# All channels stacked as input (nuclear, septate, brush_border, SuH) → -# (B, 4, 24, 64, 64). Per-channel normalization: each channel normalized with -# its own per-FOV full-frame stats from the v3 .zattrs. -# -# STATIC DATA: SimCLR self-positives; batches balanced across the 6 states -# (stratify_by=perturbation) to counter the ~3.5x state imbalance. -# Switch to consecutive-state positives later (Phase 3) to regularize the space -# temporally — needs a stage-adjacency bucket or code change (see plan). -# -# Z window centered on the parquet `z_focus` column (seeded from bbox-center z -# by conform_parquet.py), 24 slices, no random Z crop. XY 80 → CenterCrop 64. -# -# Launch: -# sbatch applications/dynaclr/configs/training/DynaCLR-3D/DynaCLR-3D-Gut-MultiChannel.sh - -base: - - ../recipes/trainer/fit.yml - - ../recipes/topology/ddp_2gpu.yml - - ../recipes/model/contrastive_encoder_convnext_tiny.yml - -trainer: - precision: bf16-mixed - max_epochs: 150 - logger: - init_args: - project: DynaCLR-3D-Gut-MultiChannel - name: gut-3d-4ch-z24-cellz-64-ntxent-t0p2-self - callbacks: - - class_path: lightning.pytorch.callbacks.LearningRateMonitor - init_args: - logging_interval: step - - class_path: lightning.pytorch.callbacks.ModelCheckpoint - init_args: - monitor: loss/val - every_n_epochs: 1 - save_top_k: 5 - save_last: true - -model: - init_args: - encoder: - init_args: - in_channels: 4 - in_stack_depth: 24 - stem_kernel_size: [4, 4, 4] - stem_stride: [4, 4, 4] - projection_dim: 32 - drop_path_rate: 0.1 - loss_function: - init_args: - temperature: 0.2 - lr: 0.00002 - pca_color_keys: "[perturbation,experiment,marker]" - # PCA pairplot cadence (overrides the base recipe's 10). - log_embeddings_every_n_epochs: 5 - log_negative_metrics_every_n_epochs: 2 - example_input_array_shape: [1, 4, 24, 64, 64] - -data: - class_path: dynaclr.data.datamodule.MultiExperimentDataModule - init_args: - cell_index_path: /hpc/projects/jacobo_group/collab/ed/dynaclr/dynaclr_cell_index_gut_v1.parquet - focus_channel: null - # Z window centers on the parquet z_focus column (seeded from bbox-center z). - z_window: 24 - z_extraction_window: 24 - z_focus_offset: 0.5 - yx_patch_size: [80, 80] - final_yx_patch_size: [64, 64] - # All 4 channels stacked per sample → (B, 4, Z, Y, X). No marker shortcut - # here (every sample carries all channels), so no batch_group_by needed. - channels_per_sample: null - # SimCLR self-positive (same crop, two augmentations); state structure emerges - # unsupervised. Batches balanced across the 6 states to counter imbalance. - positive_cell_source: self - stratify_by: [perturbation] - split_mode: cell - split_ratio: 0.8 - batch_size: 64 - num_workers: 4 - seed: 42 - # Per-channel normalization: each channel keyed by its real name, all at - # fov_statistics (== timepoint_statistics for single-timepoint data). - normalizations: - - class_path: viscy_transforms.NormalizeSampled - init_args: - keys: [nuclear] - level: fov_statistics - subtrahend: mean - divisor: std - - class_path: viscy_transforms.NormalizeSampled - init_args: - keys: [septate] - level: fov_statistics - subtrahend: mean - divisor: std - - class_path: viscy_transforms.NormalizeSampled - init_args: - keys: [brush_border] - level: fov_statistics - subtrahend: mean - divisor: std - - class_path: viscy_transforms.NormalizeSampled - init_args: - keys: [SuH] - level: fov_statistics - subtrahend: mean - divisor: std - augmentations: - - class_path: viscy_transforms.BatchedRandAffined - init_args: - keys: [nuclear, septate, brush_border, SuH] - prob: 0.8 - scale_range: [[0.9, 1.1], [0.9, 1.1], [0.9, 1.1]] - rotate_range: [3.14, 0.0, 0.0] - shear_range: [0.05, 0.05, 0.0, 0.05, 0.0, 0.05] - - class_path: viscy_transforms.BatchedRandFlipd - init_args: - keys: [nuclear, septate, brush_border, SuH] - spatial_axes: [1, 2] - prob: 0.5 - - class_path: viscy_transforms.BatchedRandAdjustContrastd - init_args: - keys: [nuclear, septate, brush_border, SuH] - prob: 0.5 - gamma: [0.6, 1.6] - - class_path: viscy_transforms.BatchedRandScaleIntensityd - init_args: - keys: [nuclear, septate, brush_border, SuH] - prob: 0.5 - factors: 0.5 - - class_path: viscy_transforms.BatchedRandGaussianSmoothd - init_args: - keys: [nuclear, septate, brush_border, SuH] - prob: 0.5 - sigma_x: [0.25, 0.50] - sigma_y: [0.25, 0.50] - sigma_z: [0.0, 0.2] - - class_path: viscy_transforms.BatchedRandGaussianNoised - init_args: - keys: [nuclear, septate, brush_border, SuH] - prob: 0.5 - mean: 0.0 - std: 0.1 From a405e30a320990e4b00c47764da29b0711ba1631 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 23 Jul 2026 09:22:48 -0700 Subject: [PATCH 53/89] Revert "feat(data): support parquet focus planes and cell-level splits" This reverts commit 16502935bf4ef96c4d9dffa0d90cbc8cee73d8d2. --- applications/dynaclr/docs/DAGs/training.md | 27 +++++------ .../dynaclr/src/dynaclr/data/datamodule.py | 46 +++++++------------ .../dynaclr/src/dynaclr/data/dataset.py | 45 +++++------------- .../src/dynaclr/data/preprocess_cell_index.py | 10 +--- packages/viscy-data/src/viscy_data/_typing.py | 2 +- .../viscy-data/src/viscy_data/cell_index.py | 36 +++++---------- 6 files changed, 53 insertions(+), 113 deletions(-) diff --git a/applications/dynaclr/docs/DAGs/training.md b/applications/dynaclr/docs/DAGs/training.md index 5a3b7f257..ca448dd7c 100644 --- a/applications/dynaclr/docs/DAGs/training.md +++ b/applications/dynaclr/docs/DAGs/training.md @@ -30,14 +30,11 @@ dynaclr build-cell-index \ ▼ dynaclr preprocess-cell-index \ /hpc/.../collections/.parquet \ - --focus-channel Phase3D --focus-level fov + --focus-channel Phase3D │ opens each unique FOV once from zarr zattrs: │ norm_mean/std/median/iqr/max/min — per (cell, timepoint, channel) - │ z_focus — focus plane the Z window centers on, - │ from focus_slice at --focus-level - │ (fov = per-FOV mean; per_timepoint = - │ per-timepoint index). `z` (tracked - │ position) is left unchanged. + │ z_focus_mean — per FOV (mean across timepoints) + │ z — per timepoint focus slice index │ drops empty frames (max == 0) ▼ .parquet (ready: self-contained, no zarr reads at training time) @@ -47,9 +44,8 @@ viscy fit --config configs/training/.yml │ OR: sbatch configs/training/.sh (SLURM, recommended) │ MultiExperimentDataModule reads parquet only at init │ tensorstore opens zarr lazily on first batch - │ Z window centers on the parquet `z_focus` column (per-sample). If z_focus - │ is null, falls back to the per-experiment z_range from zattrs focus_slice, - │ else mid-stack. z_focus_offset sets the below/above split. + │ ExperimentRegistry reads plate.zattrs["focus_slice"] once at startup + │ for z_ranges (z_extraction_window centered on dataset z_focus_mean) ▼ checkpoints/ + wandb logs ``` @@ -75,7 +71,7 @@ viscy fit (GPU, hours–days) | Step | Command | Input | Output | | --------------------- | ------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------- | | Build cell index | `dynaclr build-cell-index --num-workers 8` | collection YAML + zarr + tracking CSVs | parquet with TCZYX shape columns | -| Preprocess cell index | `dynaclr preprocess-cell-index --focus-channel Phase3D --focus-level fov` | parquet + zarr zattrs | parquet with norm stats + z_focus, empties removed | +| Preprocess cell index | `dynaclr preprocess-cell-index --focus-channel Phase3D` | parquet + zarr zattrs | parquet with norm stats, per-timepoint z, empties removed | | Train (interactive) | `uv run viscy fit --config configs/training/.yml` | training config + parquet | checkpoints + logs | | Train (SLURM) | `sbatch configs/training/.sh` | training config + parquet | checkpoints + logs | | Resume (SLURM) | `CKPT_PATH=.../last.ckpt sbatch configs/training/.sh` | checkpoint path env var | resumed checkpoints | @@ -89,8 +85,8 @@ viscy fit (GPU, hours–days) | Pixel data (TCZYX arrays) | zarr store on VAST | `prepare run` → concatenate | | Cell tracking (y, x, t, track_id) | tracking.zarr on VAST | `prepare run` → concatenate | | Normalization stats (per FOV/timepoint) | zarr zattrs → parquet `norm_*` columns | `viscy preprocess` → `preprocess-cell-index` | -| Tracked cell z (per cell) | tracking → parquet `z` column (unchanged by preprocess) | `build-cell-index` | -| Focus plane (Z window center) | zarr zattrs focus_slice → parquet `z_focus` | `viscy preprocess` → `preprocess-cell-index --focus-level` | +| Focus slice (per timepoint) | zarr zattrs → parquet `z` column | `viscy preprocess` → `preprocess-cell-index` | +| Focus slice mean (per FOV) | zarr zattrs → parquet `z_focus_mean` | `viscy preprocess` → `preprocess-cell-index` | | TCZYX shape per FOV | parquet columns | `build-cell-index` | | Collection definition | `configs/collections/.yml` in git | manually authored | | Parquet | `/hpc/projects/organelle_phenotyping/models/collections/` | `build-cell-index` | @@ -162,8 +158,7 @@ To reproduce: `build-cell-index` → `preprocess-cell-index` from the same colle ## Notes - `preprocess-cell-index` overwrites the parquet in-place by default. Pass `--output` to write elsewhere. -- `--focus-channel Phase3D` selects which channel's `focus_slice` metadata feeds the `z_focus` column. Use the channel with sharpest axial contrast (label-free Phase3D for most experiments). `--focus-level {fov,per_timepoint}` picks per-FOV mean vs per-timepoint index. -- **Z window centering is parquet-first.** At training time the datamodule centers the Z window on the per-sample `z_focus` column. If `z_focus` is null it falls back to the per-experiment `z_range` (which `ExperimentRegistry` derives from `plate.zattrs["focus_slice"][channel]["dataset_statistics"]["z_focus_mean"]`, else mid-stack). Existing parquets without a `z_focus` column read as all-null → identical to the previous zattrs-only behavior. -- `z_focus` and `z` are distinct: `z_focus` = where the Z window centers; `z` = the tracked cell position (unchanged by preprocess). Datasets without focus_slice (e.g. static bbox-center data) can seed `z_focus = z` in their own prep script. -- The `z` column is carried through to embeddings obs during predict for downstream consumers. +- `--focus-channel Phase3D` selects which channel's `per_timepoint` focus indices are written to the `z` column. Use the channel that has the sharpest axial contrast (label-free Phase3D for most experiments). +- At training time, `ExperimentRegistry.__post_init__` reads `plate.zattrs["focus_slice"][channel]["dataset_statistics"]["z_focus_mean"]` to compute per-experiment z_ranges for patch extraction. This is the only zarr metadata read at training startup; the parquet is self-contained for all per-cell data. +- The `z` column in the parquet is carried through to embeddings obs during predict — downstream consumers (e.g., visualization) can use it to recover the in-focus plane for each cell at each timepoint. - For performance tuning (num_workers, pin_memory, batch_size, augmentation placement), see [profiling.md](profiling.md) — authored after the first validated profiling sweep. diff --git a/applications/dynaclr/src/dynaclr/data/datamodule.py b/applications/dynaclr/src/dynaclr/data/datamodule.py index 7462e479b..1c49aaf82 100644 --- a/applications/dynaclr/src/dynaclr/data/datamodule.py +++ b/applications/dynaclr/src/dynaclr/data/datamodule.py @@ -190,7 +190,6 @@ def __init__( positive_match_columns: list[str] | None = None, positive_channel_source: str = "same", label_columns: dict[str, str] | None = None, - split_mode: str = "fov", max_border_shift: int = -1, shuffle_val: bool = False, pin_memory: bool = True, @@ -204,9 +203,6 @@ def __init__( self.z_window = z_window self.z_extraction_window = z_extraction_window self.z_focus_offset = z_focus_offset - if split_mode not in ("fov", "cell"): - raise ValueError(f"split_mode must be 'fov' or 'cell', got {split_mode!r}") - self.split_mode = split_mode self.yx_patch_size = yx_patch_size self.final_yx_patch_size = final_yx_patch_size self.val_experiments = val_experiments if val_experiments is not None else [] @@ -478,44 +474,36 @@ def _setup_fov_split(self, registry: ExperimentRegistry, cell_index_df: pd.DataF # (81M+ rows for OPS), which hashes a Python tuple per row and # dominates setup-time memory. Per-group isin against a small # Python-set of FOV names is O(group_size) with no object index. - # split_key: "fov_name" holds out whole FOVs; "cell_id" holds out cells - # within each experiment (needed when every experiment is a single FOV, - # e.g. one-position-per-store datasets). Splitting on cell_id keeps all - # rows of one cell (its channels) on the same side — no channel leakage. - split_key = "fov_name" if self.split_mode == "fov" else "cell_id" - train_keys_per_exp: dict[str, set[str]] = {} - val_keys_per_exp: dict[str, set[str]] = {} + train_fovs_per_exp: dict[str, set[str]] = {} + val_fovs_per_exp: dict[str, set[str]] = {} for exp_name, group in full_index.tracks.groupby("experiment"): - keys = sorted(group[split_key].unique()) - n_train = max(1, int(len(keys) * self.split_ratio)) - rng.shuffle(keys) - train_keys_per_exp[exp_name] = set(keys[:n_train]) - val_keys_per_exp[exp_name] = set(keys[n_train:]) - - n_train_keys = sum(len(s) for s in train_keys_per_exp.values()) - n_val_keys = sum(len(s) for s in val_keys_per_exp.values()) + fovs = sorted(group["fov_name"].unique()) + n_train = max(1, int(len(fovs) * self.split_ratio)) + rng.shuffle(fovs) + train_fovs_per_exp[exp_name] = set(fovs[:n_train]) + val_fovs_per_exp[exp_name] = set(fovs[n_train:]) + + n_train_fovs = sum(len(s) for s in train_fovs_per_exp.values()) + n_val_fovs = sum(len(s) for s in val_fovs_per_exp.values()) _logger.info( - "%s split (ratio=%.2f): %d train %s, %d val %s", - self.split_mode, + "FOV split (ratio=%.2f): %d train FOVs, %d val FOVs", self.split_ratio, - n_train_keys, - split_key, - n_val_keys, - split_key, + n_train_fovs, + n_val_fovs, ) def _build_train_mask(df: pd.DataFrame) -> np.ndarray: - """Row-wise boolean mask: True if the split key is in the train set.""" + """Row-wise boolean mask: True if (experiment, fov_name) is train.""" mask = np.zeros(len(df), dtype=bool) # groupby("experiment") returns integer positions in ``df`` via # group.index after reset_index; we rely on the caller passing # reset-indexed frames (which is what MultiExperimentIndex produces). for exp_name, group in df.groupby("experiment", sort=False): - train_keys = train_keys_per_exp.get(exp_name, set()) - if not train_keys: + train_fovs = train_fovs_per_exp.get(exp_name, set()) + if not train_fovs: continue - sub_mask = group[split_key].isin(train_keys).to_numpy() + sub_mask = group["fov_name"].isin(train_fovs).to_numpy() mask[group.index.to_numpy()] = sub_mask return mask diff --git a/applications/dynaclr/src/dynaclr/data/dataset.py b/applications/dynaclr/src/dynaclr/data/dataset.py index 57036921f..a682b744e 100644 --- a/applications/dynaclr/src/dynaclr/data/dataset.py +++ b/applications/dynaclr/src/dynaclr/data/dataset.py @@ -360,7 +360,6 @@ def _cache_columns(df: pd.DataFrame, columns: list[str]) -> dict: "norm_std", "norm_median", "norm_iqr", - "z_focus", } if self.positive_match_columns: hot_cols.update(self.positive_match_columns) @@ -718,13 +717,9 @@ def _build_norm_meta( ------- NormMeta or None """ - # Parquet fast-path: one norm_* row = one channel's stats. Only valid in - # bag-of-channels mode (one channel per sample, keyed "channel_0"). In - # all-channels / fixed mode a sample reads multiple channels but the row - # carries only its own channel's stats, so fall through to the zarr - # zattrs path below, which returns every channel's stats. + # Parquet path: norm columns present and value is not NA norm_mean_arr = arrays.get("norm_mean") - if self._channel_mode == "from_index" and norm_mean_arr is not None: + if norm_mean_arr is not None: norm_mean = norm_mean_arr[idx] if norm_mean is not None and not (isinstance(norm_mean, float) and np.isnan(norm_mean)): tp_stats = { @@ -733,7 +728,12 @@ def _build_norm_meta( "median": torch.tensor(arrays["norm_median"][idx], dtype=torch.float32), "iqr": torch.tensor(arrays["norm_iqr"][idx], dtype=torch.float32), } - return {"channel_0": {"timepoint_statistics": tp_stats}} + if self._channel_mode == "from_index": + return {"channel_0": {"timepoint_statistics": tp_stats}} + else: + ch_arr = arrays.get("channel_name") + ch_name = ch_arr[idx] if ch_arr is not None else "channel_0" + return {ch_name: {"timepoint_statistics": tp_stats}} # Fallback: read from zarr zattrs (old parquets without norm columns) store_path = arrays["store_path"][idx] @@ -822,36 +822,13 @@ def _slice_patch( channel_names_to_read = exp.channel_names channel_indices = [exp.channel_names.index(name) for name in channel_names_to_read] - # Z window sizing comes from the per-experiment z_range; the center is - # the per-sample focus plane. Single source of truth: the parquet - # ``z_focus`` column when populated (written by preprocess-cell-index), - # otherwise fall back to the z_range center (which the registry derived - # from zattrs focus_slice, else mid-stack). z_focus_offset sets the - # fraction of the window placed below the focus plane (0.5 = symmetric). + # Per-experiment z_range (scale-adjusted window size centered on z_range center) z_start_base, z_end_base = self.index.registry.z_ranges[exp_name] z_window_size = z_end_base - z_start_base z_count = round(z_window_size * scale_z) - z_focus_arr = arrays.get("z_focus") - z_focus_val = z_focus_arr[idx] if z_focus_arr is not None else None - # Guard against NaN for both Python float and numpy float. The old - # ``isinstance(x, float)`` check missed numpy floats, crashing int(NaN) - # on parquets with unpopulated z_focus rows. ``np.isnan`` handles both. - z_focus_missing = z_focus_val is None or ( - isinstance(z_focus_val, (float, np.floating)) and np.isnan(z_focus_val) - ) - if not z_focus_missing: - z_center = int(round(float(z_focus_val))) - z_below = round(z_count * self.index.registry.z_focus_offset) - z_start = z_center - z_below - else: - z_start = (z_start_base + z_end_base) // 2 - z_count // 2 + z_focus = (z_start_base + z_end_base) // 2 + z_start = z_focus - z_count // 2 z_end = z_start + z_count - # Clamp the window inside the image so edge cells still yield a full patch. - z_total = image.shape[2] - if z_start < 0: - z_start, z_end = 0, z_count - elif z_end > z_total: - z_start, z_end = z_total - z_count, z_total patch = image.oindex[ t, [int(c) for c in channel_indices], diff --git a/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py b/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py index 64c87e178..e49bc1c74 100644 --- a/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py +++ b/applications/dynaclr/src/dynaclr/data/preprocess_cell_index.py @@ -17,14 +17,7 @@ default=None, help="Channel name for focus_slice lookup (e.g. Phase3D). Default: first channel per FOV.", ) -@click.option( - "--focus-level", - type=click.Choice(["fov", "per_timepoint"]), - default="fov", - show_default=True, - help="focus_slice level written to the z_focus column: per-FOV mean or per-timepoint index.", -) -def main(parquet_path, output, focus_channel, focus_level): +def main(parquet_path, output, focus_channel): """Preprocess a cell index parquet: add normalization stats, focus slice, remove empty frames. Reads precomputed metadata from zarr zattrs and writes them as parquet @@ -34,5 +27,4 @@ def main(parquet_path, output, focus_channel, focus_level): parquet_path=parquet_path, output_path=output, focus_channel=focus_channel, - focus_level=focus_level, ) diff --git a/packages/viscy-data/src/viscy_data/_typing.py b/packages/viscy-data/src/viscy_data/_typing.py index 0ed9125a0..0e6baf6cc 100644 --- a/packages/viscy-data/src/viscy_data/_typing.py +++ b/packages/viscy-data/src/viscy_data/_typing.py @@ -254,7 +254,7 @@ class TripletSample(TypedDict): "Z_shape", "Y_shape", "X_shape", - "z_focus", + "z_focus_mean", ] CELL_INDEX_NORMALIZATION_COLUMNS = [ diff --git a/packages/viscy-data/src/viscy_data/cell_index.py b/packages/viscy-data/src/viscy_data/cell_index.py index ba869067a..09a72f64f 100644 --- a/packages/viscy-data/src/viscy_data/cell_index.py +++ b/packages/viscy-data/src/viscy_data/cell_index.py @@ -82,7 +82,7 @@ ("Z_shape", pa.int32()), ("Y_shape", pa.int32()), ("X_shape", pa.int32()), - ("z_focus", pa.float32()), + ("z_focus_mean", pa.float32()), ("norm_mean", pa.float32()), ("norm_std", pa.float32()), ("norm_median", pa.float32()), @@ -238,7 +238,6 @@ def preprocess_cell_index( parquet_path: str | Path, output_path: str | Path | None = None, focus_channel: str | None = None, - focus_level: str = "fov", ) -> None: """Add normalization stats, focus slice, and remove invalid rows. @@ -247,8 +246,7 @@ def preprocess_cell_index( - ``norm_mean``, ``norm_std``, ``norm_median``, ``norm_iqr``, ``norm_max``, ``norm_min`` — per-timepoint, per-channel statistics - - ``z_focus`` — the focus plane the training Z window is centered on, - sourced from ``focus_slice`` at the level chosen by ``focus_level``. + - ``z_focus_mean`` — per-FOV focus plane from ``focus_slice`` Drops rows where timepoint stats are missing or ``norm_max == 0.0`` (empty frames). The processed parquet is written to ``output_path``; @@ -264,20 +262,12 @@ def preprocess_cell_index( focus_channel : str | None Channel name for ``focus_slice`` lookup (e.g. ``"Phase3D"``). When ``None``, uses the first channel_name in each FOV's group. - focus_level : {'fov', 'per_timepoint'} - Which ``focus_slice`` level feeds the ``z_focus`` column: - ``'fov'`` uses the per-FOV ``fov_statistics.z_focus_mean``; - ``'per_timepoint'`` uses the per-timepoint focus index for each - sample's ``t``. The per-cell tracked ``z`` column is left unchanged. Raises ------ ValueError - If a FOV has no normalization metadata (run ``viscy preprocess`` first), - or if ``focus_level`` is not one of the accepted values. + If a FOV has no normalization metadata (run ``viscy preprocess`` first). """ - if focus_level not in ("fov", "per_timepoint"): - raise ValueError(f"focus_level must be 'fov' or 'per_timepoint', got {focus_level!r}") if output_path is None: output_path = parquet_path @@ -325,9 +315,8 @@ def preprocess_cell_index( t_arr = df["t"].astype(int).to_numpy() norm_arrays = {stat: np.full(len(df), float("nan"), dtype=np.float32) for stat in stat_keys} - # z_focus is the plane the training Z window centers on. The per-cell - # tracked ``z`` column is left untouched — it is a distinct quantity. focus_arr = np.full(len(df), float("nan"), dtype=np.float32) + z_arr = df["z"].to_numpy(dtype=np.int16).copy() valid_mask = np.ones(len(df), dtype=bool) for i in range(len(df)): @@ -338,18 +327,17 @@ def preprocess_cell_index( for stat in stat_keys: norm_arrays[stat][i] = float(tp_stats[stat]) fov_key = (store_arr[i], fov_arr[i]) - if focus_level == "per_timepoint": - z_t = focus_per_t_lookup.get(fov_key, {}).get(t_arr[i]) - if z_t is not None: - focus_arr[i] = z_t - else: # "fov" - z_focus = focus_lookup.get(fov_key) - if z_focus is not None: - focus_arr[i] = z_focus + z_focus = focus_lookup.get(fov_key) + if z_focus is not None: + focus_arr[i] = z_focus + z_t = focus_per_t_lookup.get(fov_key, {}).get(t_arr[i]) + if z_t is not None: + z_arr[i] = z_t for stat in stat_keys: df[f"norm_{stat}"] = norm_arrays[stat] - df["z_focus"] = focus_arr + df["z_focus_mean"] = focus_arr + df["z"] = z_arr df = df[valid_mask].reset_index(drop=True) n_dropped = n_before - len(df) From 6ac217e9eaa0611f220e0a224e374dc0be362869 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 23 Jul 2026 10:40:55 -0700 Subject: [PATCH 54/89] qc for the embeddings via pearson and mmd --- .../recipes/embedding_consistency_qc.yml | 10 ++++ .../src/dynaclr/evaluation/mmd/consistency.py | 49 +++++++++++++------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml b/applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml index 324c21d30..d62295d62 100644 --- a/applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml +++ b/applications/dynaclr/configs/evaluation/recipes/embedding_consistency_qc.yml @@ -31,3 +31,13 @@ obs_filter: # Measure residual batch effects independent of a global mean offset. center_per_experiment: true + +# Temporal binning (optional; unset here = all timepoints pooled). +# WARNING: setting temporal_bin_size or temporal_bins changes ONLY the MMD +# matrix — it computes one MMD² per (condition, bin) and the matrix cell becomes +# the MEAN over bins (per-bin values kept in consistency_mmd_results.csv). The +# Pearson matrix always pools all timepoints into one mean per dataset, so once +# bins are on the two matrices describe different populations. Requires the obs +# column 'hours_post_perturbation'. +# temporal_bin_size: 6.0 +# temporal_bins: [0, 6, 12, 24] diff --git a/applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py b/applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py index 0d06384a3..244d12a9a 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py +++ b/applications/dynaclr/src/dynaclr/evaluation/mmd/consistency.py @@ -80,23 +80,20 @@ def plot_consistency_matrix(matrix: pd.DataFrame, marker: str, output_path: Path Output file path. """ n = len(matrix) - fig, ax = plt.subplots(figsize=(max(4, n * 0.9), max(3.5, n * 0.8))) + fig, ax = plt.subplots(figsize=(max(6, n * 1.3), max(5, n * 1.1))) sns.heatmap( matrix, ax=ax, cmap="viridis", square=True, linewidths=0.5, - annot=True, - fmt=".3f", - cbar_kws={"label": "MMD²"}, + cbar_kws={"label": "MMD²", "shrink": 0.7}, ) - ax.set_title(f"Embedding consistency — {marker}\n(control cells, dataset × dataset MMD²)") + ax.set_title(f"Embedding consistency — {marker}\ncontrol cells, dataset × dataset MMD²", pad=12) ax.set_xlabel("Dataset") ax.set_ylabel("Dataset") - ax.tick_params(axis="x", labelsize=8, rotation=45) - ax.tick_params(axis="y", labelsize=8, rotation=0) - fig.tight_layout() + ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right", fontsize=8) + ax.set_yticklabels(ax.get_yticklabels(), rotation=0, fontsize=8) fig.savefig(output_path, dpi=150, bbox_inches="tight") plt.close(fig) @@ -172,7 +169,7 @@ def plot_corr_matrix(matrix: pd.DataFrame, marker: str, output_path: Path) -> No Output file path. """ n = len(matrix) - fig, ax = plt.subplots(figsize=(max(4, n * 0.9), max(3.5, n * 0.8))) + fig, ax = plt.subplots(figsize=(max(6, n * 1.3), max(5, n * 1.1))) sns.heatmap( matrix, ax=ax, @@ -181,16 +178,13 @@ def plot_corr_matrix(matrix: pd.DataFrame, marker: str, output_path: Path) -> No vmax=1.0, square=True, linewidths=0.5, - annot=True, - fmt=".3f", - cbar_kws={"label": "Pearson r"}, + cbar_kws={"label": "Pearson r", "shrink": 0.7}, ) - ax.set_title(f"Embedding consistency — {marker}\n(control cells, mean-embedding correlation)") + ax.set_title(f"Embedding consistency — {marker}\ncontrol cells, mean-embedding correlation", pad=12) ax.set_xlabel("Dataset") ax.set_ylabel("Dataset") - ax.tick_params(axis="x", labelsize=8, rotation=45) - ax.tick_params(axis="y", labelsize=8, rotation=0) - fig.tight_layout() + ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right", fontsize=8) + ax.set_yticklabels(ax.get_yticklabels(), rotation=0, fontsize=8) fig.savefig(output_path, dpi=150, bbox_inches="tight") plt.close(fig) @@ -212,6 +206,29 @@ def run_consistency_qc(config: EmbeddingConsistencyConfig) -> pd.DataFrame: ------- pd.DataFrame The long-form pairwise MMD results (same schema as ``run_mmd_combined``). + + Notes + ----- + The two matrices do **not** always summarize the same population: + + - **MMD²** partitions each dataset pair by ``group_by`` condition and, when + ``temporal_bin_size``/``temporal_bins`` is set, by temporal bin. It runs + one MMD² per (condition, bin) and the matrix cell is the **mean** of those + values (see :func:`mmd_matrix_per_marker`). With the default (no temporal + bins, ``obs_filter`` collapsing ``group_by`` to a single value) this is a + single MMD² over all control cells at all timepoints pooled. The per-bin + values survive in ``consistency_mmd_results.csv`` even though the matrix + shows only their average. + - **Pearson correlation** ignores ``group_by`` and time entirely: it pools + every cell passing ``obs_filter`` into one mean embedding per dataset + (see :func:`corr_matrix_per_marker`). + + Consequence: with no temporal bins the two matrices describe the same + population, but **turning on temporal bins makes the MMD matrix a + mean-over-bins statistic while the Pearson matrix stays all-time pooled** — + they then measure different things. Per-bin-then-average (MMD) controls for + differences in *time sampling* between acquisitions, which is usually the + right batch-effect choice, but the matrix itself is not time-resolved. """ datasets_root = config.datasets_root if config.datasets_root is not None else DATASETS_ROOT input_paths = [ From 1897b8fa7f1d6bce0c82579cd5ab012c228e4934 Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 23 Jul 2026 10:51:07 -0700 Subject: [PATCH 55/89] plotting for mmd --- .../witness_gmm_labels_infectomics.yml | 11 +- .../docs/DAGs/witness_gmm_classifiers.md | 263 ++++++++---------- .../src/dynaclr/evaluation/evaluate_config.py | 17 +- .../linear_classifiers/witness_gmm_labels.py | 152 ++++++++-- .../witness_gmm_labels_test.py | 24 +- .../linear_classifiers/witness_gmm_plots.py | 253 +++++++++++++++++ 6 files changed, 544 insertions(+), 176 deletions(-) create mode 100644 applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_plots.py diff --git a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml index 6fcbd26cc..c5b3d1361 100644 --- a/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml +++ b/applications/dynaclr/configs/evaluation/recipes/witness_gmm_labels_infectomics.yml @@ -46,7 +46,13 @@ witness_gmm_labels: bandwidth: null # median heuristic on pooled (control, perturbed) max_reference_cells: 5000 condition_column: perturbation - output_path: "/path/to/output/infection_state_witness.csv" + # Stage-A outputs land under /labels/: the annotation file + # (.) plus a plots/ subdir with the + # label-decision evidence (witness-GMM fit, MMD permutation null, and + # %-remodeling-vs-time). Point output_dir at the checkpoint root so Stage B + # can write its trained classifiers to a sibling /classifiers/. + output_dir: "/path/to/model/ckpt" + annotation_format: csv # For an organelle marker, run a SECOND Stage-A config with: # marker_filters: [SEC61B] @@ -59,7 +65,8 @@ linear_classifiers: label_source: annotations annotations: - experiment: "2025_07_24_A549_viral_sensor_ZIKV" - path: "/path/to/output/infection_state_witness.csv" + # Stage-A output: /labels/_. + path: "/path/to/model/ckpt/labels/viral_sensor_infection_state.csv" tasks: - task: infection_state marker_filters: [viral_sensor] diff --git a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md index c34713e04..6bd29f35e 100644 --- a/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md +++ b/applications/dynaclr/docs/DAGs/witness_gmm_classifiers.md @@ -1,190 +1,159 @@ -# Witness-GMM annotations → linear classifiers DAG +# Witness-GMM classifier training -**Written:** 2026-07-16 -**Status:** Active. Replaces the earlier witness-score classifier path -(the sign+dead-zone gate + circular annotation grading, now removed). -**Rolls up to:** `.ed_planning/dynaclr/batch_correction/per_microscope_classifier/PLAN.md` - -A witness→GMM label **is an annotation**: its meaning is named by the modality it -is computed from — a witness over `viral_sensor` produces `infection_state` -(infected/uninfected); over an organelle marker, `organelle_remodeling_state` -(remodel/noremodel). So Stage A writes a **named biological-state column** with the -real class vocabulary, a file indistinguishable from a hand annotation, and the -**existing annotation training path** (`run-linear-classifiers`, -`label_source: annotations`) consumes it unchanged. - -## End-to-end DAG (two stages, one existing training path) +Use the MMD witness and a Gaussian mixture model (GMM) to create confident +pseudo-labels, then train a linear classifier through the standard annotation +path. ```mermaid -flowchart TD - Z["embeddings zarr
obs: experiment, marker, fov_name, id, track_id, t,
perturbation, hours_post_perturbation
one microscope / marker per config — no LOT, no pooling"] - - subgraph A["STAGE A · dynaclr witness-gmm-labels (NEW)"] - direction TB - A1["references from wells/filters:
X = control cells, Y = perturbed cells"] - A2["bandwidth = median_heuristic(X, Y)
viscy_utils.evaluation.mmd"] - A3["score every cell: w(z) = witness_function(z, X, Y, bw)
mmd"] - AG["per perturbed CONDITION: MMD permutation test (X vs cond)
mmd.mmd_permutation_test
run-wide Benjamini-Yekutieli FDR; adjusted p > mmd_pvalue_threshold → skip"] - A4["per perturbed CONDITION: 2-component GMM on w[cond]
witness_gmm.fit_gmm_labels
remod = argmin(means); posterior ≥ gmm_pos_threshold → positive
negatives = ALL control-well cells; ambiguous → dropped
unimodal GMM (separated=False) → condition skipped"] - A5["map GMM ±1 → class_map vocabulary
(e.g. infected / uninfected)"] - A1 --> A2 --> A3 --> AG --> A4 --> A5 - end - - LBL["ANNOTATION FILE .csv|parquet
key: fov_name + id (or fov_name + t + track_id) + experiment
named state column, e.g. infection_state ∈ {infected, uninfected}
hand-annotation format — producer-agnostic"] - - subgraph B["STAGE B · dynaclr run-linear-classifiers (EXISTING, label_source: annotations)"] - direction TB - B1["_annotation_run_specs → load_annotation_anndata (join by key)"] - B2["train_linear_classifier → save joblib →
metrics_summary.csv → publish → PDF"] - B1 --> B2 - end - - OUT["output_dir/
metrics_summary.csv · {task}_summary.pdf
pipelines/{task}_{marker}.joblib
[publish_dir/vN + latest] → append-predictions"] - - Z --> A --> LBL --> B --> OUT - LBL -. "teacher/student: SEC61 labels,
train on a different modality's zarr" .-> B +flowchart LR + A["Teacher embeddings"] --> B["MMD witness scores"] + B --> C["MMD significance gate"] + C --> D["Per-condition 2-component GMM"] + D --> E["Annotation file"] + E --> F["Target embeddings"] + F --> G["Scaled logistic-regression pipeline"] ``` +The teacher and target embeddings may be the same modality. To transfer labels +between modalities, generate labels from the teacher modality and train on the +target modality using the shared cell identifiers. +## Label generation +For each witness marker: +1. Build the reference sets from all timepoints: + - \(X\): cells from clean control wells. + - \(Y\): cells from perturbed wells. +2. Select the RBF bandwidth with the median heuristic and score every cell: -## What's parallel vs sequential + \[ + w(z) = \operatorname{mean}_{x \in X} k(z, x) + - \operatorname{mean}_{y \in Y} k(z, y) + \] -```mermaid -flowchart TD - E["experiments
(Stage A pools them per marker)"] - W["witness → per-condition GMM
(one pass per marker)"] - L["labels.parquet
(single annotation file)"] - R["run-linear-classifiers
(one LC per (task, marker), existing loop)"] - O["metrics_summary.csv + pipelines/"] - E --> W --> L --> R --> O -``` +3. For each perturbed condition, test \(X\) against that condition with an MMD + permutation test. Apply Benjamini-Yekutieli correction across all + marker-condition tests in the run. Skip conditions whose adjusted + \(p\)-value is greater than `mmd_pvalue_threshold`. +4. Fit a two-component GMM to the surviving condition's witness scores. + - The component with the lower mean is the positive, perturbed state. + - Label a cell positive when its posterior for that component is at least + `gmm_pos_threshold`. + - Skip the condition if the components are not separated. +5. Label all control-reference cells negative. Drop perturbed cells that do not + pass the positive posterior threshold and cells outside both reference sets. +Do not add a time gate by default: the GMM is intended to separate affected and +unaffected cells within the perturbed population. +The output is an annotation file with: -## Reference construction & the GMM gate (why no time gate) +- `experiment`, `fov_name`, and `id`; or `experiment`, `fov_name`, `t`, and + `track_id`; +- the biological label column and class names, such as + `infection_state: infected | uninfected`; +- available tracking and biological metadata. -The two witness references are built **asymmetrically**, and this is the crux of the -method: +## Stage A configuration -- **Control cloud (X) = all cells in the control/uninfected wells, all timepoints.** - An uninfected cell looks uninfected at any hpi, so pooling every timepoint gives a - large, *clean* reference. No gating. -- **Perturbed cloud (Y) = all cells in the perturbed wells, all timepoints.** A perturbed - well is a **mixture**: early cells have not remodeled yet, late cells have. Y is - therefore *dirty* by construction. +Use one configuration per witness marker and embedding domain. -The witness `w(z) = mean k(z, X) − mean k(z, Y)` is scored against both clouds. The -**per-condition GMM is then fit on the perturbed cells' scores only** — it separates the -remodeled mode from the not-yet-remodeled mode *inside* the dirty Y. Control cells are -taken as negatives wholesale (well identity). This asymmetry is why the GMM is fit on one -side but not the other. +```yaml +witness_gmm_labels: + experiments: + - experiment: "2026_04_28_A549_SEC61B_DENV" + embeddings_zarr: "/path/to/teacher/embeddings.zarr" + control_filter: {perturbation: uninfected} + perturbed_filter: {perturbation: [DENV]} -**No time gate by default.** A `hours_post_perturbation` window on the perturbed filter -would pre-clean Y by hand — but that discards data and re-introduces a hand-tuned -threshold, which is exactly what the GMM removes. The GMM is the principled replacement for -the time gate: it finds the remodeled sub-population within the full mixture. Add a time -gate only for a specific reason (e.g. debugging, or a marker with no clean late window). + marker_filters: [viral_sensor] + condition_column: perturbation + label_column: infection_state + class_map: {positive: infected, negative: uninfected} -**Significance gate (before the GMM, FDR-controlled).** Per condition, an MMD permutation -test (`mmd_permutation_test`, X vs the condition's cells) checks whether the two clouds are -*actually distinct*. Because one Stage-A run tests many (marker × condition) pairs, the raw -p-values are corrected with **Benjamini-Yekutieli FDR control** -(`scipy.stats.false_discovery_control(method="by")`) across the whole run, and a condition -is skipped when its **adjusted** p-value exceeds `mmd_pvalue_threshold` (the target FDR -level, default 0.05). A condition contributes positives only if it is **both** -FDR-significant **and** GMM-bimodal (`separated=True`); the two guard different failure -modes (references differ vs. the perturbed cloud splits cleanly). This is a **two-pass** -flow: score + p-value every condition (pass 1), BY-adjust run-wide, then GMM-label the -survivors (pass 2). Set `mmd_pvalue_threshold: 1.0` to disable the gate. + bandwidth: null + max_reference_cells: 5000 + mmd_n_permutations: 1000 + mmd_pvalue_threshold: 0.05 + gmm_pos_threshold: 0.8 + random_seed: 42 -```mermaid -flowchart LR - subgraph refs["reference clouds (per marker, all timepoints)"] - X["X = control wells
clean (uninfected at any hpi)"] - Y["Y = perturbed wells
dirty mixture
early: not remodeled · late: remodeled"] - end - W["witness score per cell
w(z) = mean k(z,X) − mean k(z,Y)"] - G["2-component GMM on w over Y
(separates the mixture)"] - POS["remodeled mode → positive
(posterior ≥ threshold)"] - NEG["all X cells → negative
(well identity, no gate)"] - X --> W - Y --> W - W --> G --> POS - X --> NEG - POS --> LAB["annotation file
positive / negative"] - NEG --> LAB + output_dir: "/path/to/run" + annotation_format: csv ``` -## Recipe / config +This writes: + +```text +/path/to/run/labels/viral_sensor_infection_state.csv +/path/to/run/labels/plots/ +``` -**Stage A** — `labels_config.yml`: +For an organelle witness, change the marker and label vocabulary, for example: ```yaml -witness_gmm_labels: - experiments: - - experiment: "2026_04_28_A549_SEC61B_DENV" - embeddings_zarr: ".../2-phenotyping/predictions/embeddings" - control_filter: {perturbation: uninfected} # all uninfected cells, all timepoints - perturbed_filter: {perturbation: [DENV]} # all DENV cells, all timepoints (no time gate) - marker_filters: [viral_sensor] # from viral_sensor → infection_state - label_column: infection_state - class_map: {positive: infected, negative: uninfected} - gmm_pos_threshold: 0.8 - mmd_pvalue_threshold: 0.05 # target FDR level (BY-adjusted p) for the MMD gate - mmd_n_permutations: 1000 - bandwidth: null # median heuristic - max_reference_cells: 5000 - condition_column: perturbation - output_path: ".../infection_state_witness.csv" +marker_filters: [SEC61B] +label_column: organelle_remodeling_state +class_map: {positive: remodel, negative: noremodel} +``` + +Generate the labels: + +```sh +dynaclr witness-gmm-labels -c labels_config.yml ``` -For an organelle marker: `marker_filters: [SEC61B]`, -`label_column: organelle_remodeling_state`, -`class_map: {positive: remodel, negative: noremodel}`. +## Classifier training -**Stage B** — `train_config.yml` (the existing annotation path): +Train from the generated file with `label_source: annotations`. The +`embeddings_path` is the target feature space: use the teacher embeddings for a +same-modality classifier or another modality's embeddings for label transfer. +`tasks[].marker_filters` must name the target marker. ```yaml +embeddings_path: "/path/to/target/embeddings.zarr" +output_dir: "/path/to/run/classifiers" + linear_classifiers: label_source: annotations - embeddings_path: ".../embeddings" # same modality, or a phase zarr for SEC61→phase annotations: - experiment: "2026_04_28_A549_SEC61B_DENV" - path: ".../infection_state_witness.csv" - tasks: [{task: infection_state}] + path: "/path/to/run/labels/viral_sensor_infection_state.csv" + tasks: + - task: infection_state + marker_filters: [viral_sensor] + use_scaling: true + use_pca: false + class_weight: balanced split_train_data: 0.8 split_groups_by: [experiment, fov_name, track_id] + random_seed: 42 ``` -Invoke: +Use a group-aware split so that a track cannot appear in both training and +validation. Train the classifier: ```sh -dynaclr witness-gmm-labels -c labels_config.yml dynaclr run-linear-classifiers -c train_config.yml ``` -## The deployable artifact (do NOT recompute the scaler / PCA) - -> **Note:** each `pipelines/{task}_{marker}.joblib` is a `LinearClassifierPipeline` -> holding the **fitted** `StandardScaler`, the **fitted** `PCA` (when `use_pca: true`), -> and the logistic-regression weights — all frozen from training. Applying to a new -> dataset (`apply-linear-classifier` / `append-predictions`) calls -> `scaler.transform → pca.transform → classifier.predict_proba` — **`.transform`, never -> `.fit`**. The scaler's mean/std and PCA's rotation are **not** recomputed on new data, -> and must not be: re-fitting would re-center/re-rotate the new embeddings into a -> different space than the classifier's `w·x+b` boundary was learned in, silently -> corrupting predictions. The pipeline is embedding-only and self-contained — no witness -> references or GMM are carried into it (those live only in Stage A). -> -> **Validity condition:** reusing the frozen scaler/PCA is correct only when the new -> embeddings share the training distribution — i.e. the **same microscope / domain**. -> Under a batch shift (e.g. mantis v1 → v2) applying the frozen pipeline is mechanically -> valid but biologically off; that is why the design is **one LC per microscope** rather -> than cross-domain transfer. - -## Related - -- Annotation training path: [evaluation.md](evaluation.md) +The command writes validation metrics, plots, and the fitted pipeline: + +```text +/path/to/run/classifiers/metrics_summary.csv +/path/to/run/classifiers/infection_state_summary.pdf +/path/to/run/classifiers/pipelines/infection_state_.joblib +``` + +## Acceptance checks + +- Review the MMD-null and witness-GMM plots for every accepted condition. +- Confirm that both classes have enough cells and are distributed across + independent tracks and fields of view. +- Use validation metrics from the group-aware split, not a cell-level split. +- Apply the saved pipeline only to the same embedding feature space and domain. + Reuse its fitted scaler and PCA; do not refit preprocessing at inference. + +See [evaluation.md](evaluation.md) for the annotation training pipeline. diff --git a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py index 30d89193c..619ad0d99 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py +++ b/applications/dynaclr/src/dynaclr/evaluation/evaluate_config.py @@ -286,8 +286,16 @@ class WitnessGmmLabelsConfig(BaseModel): Maps the GMM gate outcome to the class vocabulary: ``{"positive": , "negative": }`` — e.g. ``{"positive": "infected", "negative": "uninfected"}``. - output_path : str - Path to write the annotation file (``.csv`` or ``.parquet`` by extension). + output_dir : str + Directory for Stage-A outputs. The annotation file is written to + ``/labels/_.`` + (the marker prefix disambiguates sibling single-marker configs that share + a ``label_column``; dropped when ``marker_filters`` is None) and diagnostic + plots to ``/labels/plots/``. Keeping labels under a + ``labels/`` subtree lets Stage B write its trained classifiers to a + sibling ``classifiers/`` subtree under the same checkpoint root. + annotation_format : str + Annotation-file format, ``"csv"`` or ``"parquet"``. Default: ``"csv"``. marker_filters : list[str] or None Markers to label (one annotation column per config; usually one). None = all unique ``obs["marker"]``. Default: None. @@ -319,7 +327,8 @@ class WitnessGmmLabelsConfig(BaseModel): experiments: list[WitnessGmmExperiment] label_column: str class_map: dict[str, str] - output_path: str + output_dir: str + annotation_format: str = "csv" marker_filters: list[str] | None = None condition_column: str = "perturbation" gmm_pos_threshold: float = 0.8 @@ -340,6 +349,8 @@ def _validate(self) -> "WitnessGmmLabelsConfig": raise ValueError(f"gmm_pos_threshold must be in (0, 1], got {self.gmm_pos_threshold}") if not 0.0 < self.mmd_pvalue_threshold <= 1.0: raise ValueError(f"mmd_pvalue_threshold must be in (0, 1], got {self.mmd_pvalue_threshold}") + if self.annotation_format not in ("csv", "parquet"): + raise ValueError(f"annotation_format must be 'csv' or 'parquet', got {self.annotation_format!r}") return self diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py index a79442d40..bb5f17be5 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels.py @@ -37,6 +37,11 @@ class vocabulary — a file indistinguishable from a hand annotation. import pandas as pd from scipy.stats import false_discovery_control +from dynaclr.evaluation.linear_classifiers.witness_gmm_plots import ( + plot_mmd_null, + plot_remodeling_vs_time, + plot_witness_gmm, +) from viscy_utils.cli_utils import load_config from viscy_utils.evaluation.mmd import median_heuristic, mmd_permutation_test, subsample, witness_function from viscy_utils.evaluation.witness_gmm import fit_gmm_labels @@ -139,6 +144,22 @@ def obs_filter_mask(obs: pd.DataFrame, filter_dict: dict) -> np.ndarray: _KEY_PRIMARY = ["experiment", "fov_name", "id"] _KEY_FALLBACK = ["experiment", "fov_name", "t", "track_id"] +# Tracking / spatial / biological metadata carried through to the annotation file +# in addition to the join key, so the label CSV is self-describing (an +# annotation-format file that keeps track lineage, position, marker, and +# perturbation rather than only exp/fov/id/t/state). +_METADATA_COLUMNS = [ + "t", + "track_id", + "parent_track_id", + "parent_id", + "y", + "x", + "marker", + "perturbation", + "hours_post_perturbation", +] + def _annotation_key_columns(obs: pd.DataFrame) -> list[str]: """Pick the annotation join key present in ``obs`` (``id`` primary, else t+track_id).""" @@ -152,6 +173,15 @@ def _annotation_key_columns(obs: pd.DataFrame) -> list[str]: ) +@dataclass +class _MmdResult: + """MMD permutation-test outcome for one condition (cached for diagnostics).""" + + mmd2: float + p_value: float + null: np.ndarray + + @dataclass class _MarkerScores: """Cached per-marker witness scores + reference masks for two-pass labeling.""" @@ -161,7 +191,27 @@ class _MarkerScores: perturbed_mask: np.ndarray scores: np.ndarray # witness score per cell conditions: np.ndarray # obs[condition_column] as array - cond_pvalues: dict # condition -> MMD permutation p-value (raw) + cond_mmd: dict # condition -> _MmdResult (raw MMD² + p-value + null) + + @property + def cond_pvalues(self) -> dict: + """condition -> raw MMD permutation p-value.""" + return {cond: res.p_value for cond, res in self.cond_mmd.items()} + + +@dataclass +class _MarkerLabels: + """Result of GMM-labeling one marker: the annotation frame plus, for + diagnostics, the per-condition GMM fits, the scores they were fit on, and the + time vector over the *full* perturbed population (labeled + GMM-dropped) so + the remodeling-vs-time plot has the whole-well denominator, not just the + confident positives.""" + + frame: pd.DataFrame + cond_gmm: dict # condition -> GmmLabelResult + cond_scores: dict # condition -> witness scores (all perturbed cells for the condition) + cond_time: dict # condition -> time value per perturbed cell (hpp if present, else t) + control_time: np.ndarray | None # time value per control-reference cell (the 0% baseline) def compute_marker_scores( @@ -208,34 +258,36 @@ def compute_marker_scores( # Per-condition MMD significance: is this condition's cloud distinct from the # control reference? Raw p-values here; FDR-corrected run-wide by the caller. conditions = obs[config.condition_column].to_numpy() - cond_pvalues: dict = {} + cond_mmd: dict = {} for cond in pd.unique(conditions[perturbed_mask]): cond_mask = perturbed_mask & (conditions == cond) if cond_mask.sum() < 5: continue - _mmd2, p_value, _null = mmd_permutation_test( + mmd2, p_value, null = mmd_permutation_test( X_ref, subsample(X_all[cond_mask], config.max_reference_cells, rng), n_permutations=config.mmd_n_permutations, bandwidth=bandwidth, seed=config.random_seed, ) - cond_pvalues[cond] = float(p_value) + cond_mmd[cond] = _MmdResult(mmd2=float(mmd2), p_value=float(p_value), null=np.asarray(null)) - return _MarkerScores(obs, control_mask, perturbed_mask, scores, conditions, cond_pvalues) + return _MarkerScores(obs, control_mask, perturbed_mask, scores, conditions, cond_mmd) def label_marker( marker_scores: _MarkerScores, significant_conditions: set, config: WitnessGmmLabelsConfig, -) -> pd.DataFrame | None: +) -> _MarkerLabels | None: """Pass 2: GMM-gate each significant condition and assemble the annotation frame. A condition is labeled only if it is in ``significant_conditions`` (cleared the FDR-controlled MMD gate, decided run-wide) AND its GMM is bimodal. Negatives are all control-well cells (well identity). ``None`` when no - condition survives both gates. + condition survives both gates. The returned :class:`_MarkerLabels` also + carries the per-condition GMM fits and the scores they were fit on, for the + Stage-A diagnostic plots. """ obs = marker_scores.obs key_cols = _annotation_key_columns(obs) @@ -249,6 +301,12 @@ def label_marker( labels = np.full(len(obs), None, dtype=object) labels[control_mask] = neg_label + time_col = "hours_post_perturbation" if "hours_post_perturbation" in obs.columns else "t" + time_values = obs[time_col].to_numpy() if time_col in obs.columns else None + + cond_gmm: dict = {} + cond_scores: dict = {} + cond_time: dict = {} any_separated = False for cond in pd.unique(conditions[perturbed_mask]): cond_mask = perturbed_mask & (conditions == cond) @@ -258,6 +316,10 @@ def label_marker( _logger.warning("MMD not significant (FDR) for condition %r; no positives labeled.", cond) continue res = fit_gmm_labels(scores[cond_mask], pos_threshold=config.gmm_pos_threshold, random_state=config.random_seed) + cond_gmm[cond] = res + cond_scores[cond] = scores[cond_mask] + if time_values is not None: + cond_time[cond] = time_values[cond_mask] if not res.separated: _logger.warning("GMM unimodal for condition %r; no positives labeled.", cond) continue @@ -269,21 +331,29 @@ def label_marker( return None keep = labels != None # noqa: E711 — object-array null test - frame = obs.loc[keep, key_cols].copy() - if "t" in obs.columns and "t" not in frame.columns: - frame["t"] = obs.loc[keep, "t"].to_numpy() + carry = key_cols + [c for c in _METADATA_COLUMNS if c in obs.columns and c not in key_cols] + frame = obs.loc[keep, carry].copy() frame[config.label_column] = labels[keep] # Normalize fov_name to match the annotation-loader convention. frame["fov_name"] = frame["fov_name"].astype(object).str.strip("/") - return frame.reset_index(drop=True) + control_time = time_values[control_mask] if time_values is not None else None + return _MarkerLabels(frame.reset_index(drop=True), cond_gmm, cond_scores, cond_time, control_time) def generate_witness_gmm_annotation(config: WitnessGmmLabelsConfig) -> Path: - """Run Stage A end to end and write the annotation file. + """Run Stage A end to end and write the annotation file plus diagnostics. Loads each experiment's embeddings zarr, pools per marker, labels via - :func:`build_marker_annotation`, concatenates, and writes to - ``config.output_path`` (CSV or parquet by extension). + :func:`label_marker`, concatenates, and writes the annotation to + ``/labels/.``. Diagnostic plots + (the label-decision evidence) go to ``/labels/plots/``: + + - ``mmd_null__.png`` — MMD permutation-null + observed + + p-values (the significance gate), for every tested condition. + - ``witness_gmm__.png`` — witness-score histogram + fitted + GMM (the bimodality gate), for every FDR-significant condition. + - ``remodeling_vs_time_.png`` — fraction of cells in the positive + class vs time per condition, for every labeled marker. Parameters ---------- @@ -332,8 +402,10 @@ def generate_witness_gmm_annotation(config: WitnessGmmLabelsConfig) -> Path: # Benjamini-Yekutieli FDR control across the whole run's (marker, condition) # family; a condition is significant if its adjusted p ≤ mmd_pvalue_threshold. adjusted = false_discovery_control(np.asarray(pvals), method="by") + p_adj_by_key: dict[tuple[str, object], float] = {} significant: dict[str, set] = {} for (marker, cond), p_adj in zip(pval_keys, adjusted): + p_adj_by_key[(marker, cond)] = float(p_adj) if p_adj <= config.mmd_pvalue_threshold: significant.setdefault(marker, set()).add(cond) else: @@ -345,23 +417,67 @@ def generate_witness_gmm_annotation(config: WitnessGmmLabelsConfig) -> Path: config.mmd_pvalue_threshold, ) + labels_dir = Path(config.output_dir) / "labels" + plots_dir = labels_dir / "plots" + plots_dir.mkdir(parents=True, exist_ok=True) + + # MMD-null diagnostic for every tested (marker, condition) — the significance gate. + for marker, ms in marker_scores.items(): + for cond, mmd in ms.cond_mmd.items(): + p_adj = p_adj_by_key[(marker, cond)] + plot_mmd_null( + mmd.mmd2, + mmd.null, + mmd.p_value, + p_adj, + significant=cond in significant.get(marker, set()), + marker=str(marker), + condition=str(cond), + output_path=plots_dir / f"mmd_null_{marker}_{cond}.png", + ) + # Pass 2: GMM-label each marker using the FDR-significant conditions. frames: list[pd.DataFrame] = [] for marker, ms in marker_scores.items(): - frame = label_marker(ms, significant.get(marker, set()), config) - if frame is None: + result = label_marker(ms, significant.get(marker, set()), config) + # Witness-GMM diagnostic for every fitted condition (labeled or unimodal-skipped). + for cond, res in (result.cond_gmm if result else {}).items(): + plot_witness_gmm( + result.cond_scores[cond], + res, + config.gmm_pos_threshold, + marker=str(marker), + condition=str(cond), + output_path=plots_dir / f"witness_gmm_{marker}_{cond}.png", + ) + if result is None: _logger.warning("Marker %r produced no labels (no significant + bimodal condition); skipping.", marker) continue + frame = result.frame counts = frame[config.label_column].value_counts().to_dict() _logger.info("Marker %r: %d labeled cells %s", marker, len(frame), counts) + plot_remodeling_vs_time( + result.cond_time, + result.cond_gmm, + result.control_time, + config.class_map["positive"], + marker=str(marker), + output_path=plots_dir / f"remodeling_vs_time_{marker}.png", + time_is_hpp="hours_post_perturbation" in ms.obs.columns, + ) frames.append(frame) if not frames: raise RuntimeError("No markers produced labels — check references, thresholds, and condition_column.") out = pd.concat(frames, ignore_index=True) - output_path = Path(config.output_path) - output_path.parent.mkdir(parents=True, exist_ok=True) + # Disambiguate by marker: sibling single-marker configs often share a + # label_column (e.g. three organelle markers → organelle_remodeling_state), + # which would clobber a bare .. Prefix with the filtered + # marker(s) so each config writes its own file. + stem = f"{'_'.join(config.marker_filters)}_{config.label_column}" if config.marker_filters else config.label_column + output_path = labels_dir / f"{stem}.{config.annotation_format}" + labels_dir.mkdir(parents=True, exist_ok=True) if output_path.suffix == ".parquet": out.to_parquet(output_path, index=False) else: diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py index d8f7504b5..aa7b41b5d 100644 --- a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_labels_test.py @@ -27,7 +27,8 @@ def build_marker_annotation(adata, experiments, config): if ms is None: return None significant = {c for c, p in ms.cond_pvalues.items() if p <= config.mmd_pvalue_threshold} - return label_marker(ms, significant, config) + result = label_marker(ms, significant, config) + return result.frame if result is not None else None def _make_separable_embeddings( @@ -74,7 +75,7 @@ def _make_separable_embeddings( return ad.AnnData(X=X, obs=obs, var=var) -def _config(output_path, experiment="exp_A", embeddings_zarr="unused.zarr"): +def _config(output_dir, experiment="exp_A", embeddings_zarr="unused.zarr", annotation_format="csv"): return WitnessGmmLabelsConfig( experiments=[ WitnessGmmExperiment( @@ -88,7 +89,8 @@ def _config(output_path, experiment="exp_A", embeddings_zarr="unused.zarr"): label_column="infection_state", class_map={"positive": "infected", "negative": "uninfected"}, condition_column="perturbation", - output_path=str(output_path), + output_dir=str(output_dir), + annotation_format=annotation_format, ) @@ -148,11 +150,21 @@ def test_generate_writes_annotation_file(tmp_path): """generate_witness_gmm_annotation writes a parquet/csv annotation file.""" zarr_path = tmp_path / "embeddings.zarr" _make_separable_embeddings().write_zarr(zarr_path) - out = generate_witness_gmm_annotation(_config(tmp_path / "labels.parquet", embeddings_zarr=str(zarr_path))) + out = generate_witness_gmm_annotation( + _config(tmp_path / "ckpt", embeddings_zarr=str(zarr_path), annotation_format="parquet") + ) assert out.exists() + assert out == tmp_path / "ckpt" / "labels" / "viral_sensor_infection_state.parquet" df = pd.read_parquet(out) assert "infection_state" in df.columns assert set(df["infection_state"].unique()) == {"infected", "uninfected"} + # Full tracking metadata carried through (not just exp/fov/id/t/state). + assert {"track_id", "marker", "perturbation"}.issubset(df.columns) + # Diagnostic plots written alongside the labels. + plots = tmp_path / "ckpt" / "labels" / "plots" + assert (plots / "witness_gmm_viral_sensor_DENV.png").exists() + assert (plots / "mmd_null_viral_sensor_DENV.png").exists() + assert (plots / "remodeling_vs_time_viral_sensor.png").exists() def test_annotation_joins_by_key_under_shuffle(tmp_path): @@ -161,7 +173,7 @@ def test_annotation_joins_by_key_under_shuffle(tmp_path): adata = _make_separable_embeddings() zarr_path = tmp_path / "embeddings.zarr" adata.write_zarr(zarr_path) - out = generate_witness_gmm_annotation(_config(tmp_path / "labels.csv", embeddings_zarr=str(zarr_path))) + out = generate_witness_gmm_annotation(_config(tmp_path / "ckpt", embeddings_zarr=str(zarr_path))) # Shuffle the embedding rows, then join the annotation back by key. rng = np.random.default_rng(3) @@ -192,7 +204,7 @@ def test_build_marker_annotation_none_when_reference_missing(tmp_path): marker_filters=["viral_sensor"], label_column="infection_state", class_map={"positive": "infected", "negative": "uninfected"}, - output_path=str(tmp_path / "labels.csv"), + output_dir=str(tmp_path / "ckpt"), ) assert build_marker_annotation(adata, cfg.experiments, cfg) is None diff --git a/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_plots.py b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_plots.py new file mode 100644 index 000000000..a8fa784f2 --- /dev/null +++ b/applications/dynaclr/src/dynaclr/evaluation/linear_classifiers/witness_gmm_plots.py @@ -0,0 +1,253 @@ +"""Stage-A witness-GMM diagnostic plots — the label-decision evidence. + +Stage A (:mod:`dynaclr.evaluation.linear_classifiers.witness_gmm_labels`) turns +the MMD witness + per-condition GMM into an annotation file. These plots are the +*evidence* behind each labeling decision, written alongside the CSV so the call +(label / skip) is auditable: + +- :func:`plot_witness_gmm` — per (marker, condition): the perturbed cells' + witness-score histogram with the fitted two-component GMM overlaid (the + bimodality the gate keys on) and the positive-posterior threshold marked. +- :func:`plot_mmd_null` — per (marker, condition): the MMD² permutation-null + histogram with the observed MMD² and p-value (the significance gate). +- :func:`plot_remodeling_vs_time` — per marker: the fraction of cells in the + perturbed (remodeled/infected) class vs time, per condition — the biological + kinetics the labels imply. +""" + +from __future__ import annotations + +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from numpy.typing import NDArray + +from viscy_utils.evaluation.witness_gmm import GmmLabelResult + + +def plot_witness_gmm( + scores: NDArray, + result: GmmLabelResult, + pos_threshold: float, + marker: str, + condition: str, + output_path: Path, +) -> None: + """Plot perturbed-cell witness scores with the fitted GMM overlaid. + + Shows the histogram of the condition's witness scores, the two Gaussian + component densities, and the posterior-threshold decision boundary (cells + whose remodeled-component posterior clears ``pos_threshold`` are the + confident positives). This is the bimodality the GMM gate reads. + + Parameters + ---------- + scores : NDArray + Witness scores for the condition's perturbed cells, shape ``(n,)``. + result : GmmLabelResult + The fitted GMM result for this condition. + pos_threshold : float + Posterior threshold used to call confident positives. + marker : str + Marker name (title). + condition : str + Condition name (title). + output_path : Path + Output file path. + """ + scores = np.asarray(scores).ravel() + fig, ax = plt.subplots(figsize=(7, 4.5)) + ax.hist(scores, bins=60, density=True, color="0.7", edgecolor="white", label="witness scores") + + grid = np.linspace(scores.min(), scores.max(), 400) + means = result.gmm.means_.ravel() + stds = np.sqrt(result.gmm.covariances_.ravel()) + weights = result.gmm.weights_.ravel() + for k in range(len(means)): + density = weights[k] / (stds[k] * np.sqrt(2 * np.pi)) * np.exp(-0.5 * ((grid - means[k]) / stds[k]) ** 2) + is_remod = k == result.remod_component + ax.plot( + grid, + density, + lw=2, + color="tab:red" if is_remod else "tab:blue", + label=f"{'remodeled' if is_remod else 'unaffected'} (w={weights[k]:.2f})", + ) + + # Score at which the remodeled-component posterior equals pos_threshold — the + # decision boundary, found on the score grid (posterior is monotone in score + # for a two-component 1-D GMM). + grid_post = result.gmm.predict_proba(grid.reshape(-1, 1))[:, result.remod_component] + crossing = grid[grid_post >= pos_threshold] + if crossing.size: + boundary = crossing.max() if means[result.remod_component] < means.mean() else crossing.min() + ax.axvline(boundary, color="k", ls="--", lw=1, label=f"posterior ≥ {pos_threshold:g}") + + n_pos = int((result.hard_label == 1).sum()) + ax.set_title( + f"Witness-GMM gate — {marker} / {condition}\n" + f"{'bimodal' if result.separated else 'UNIMODAL (skipped)'} · " + f"{n_pos}/{len(scores)} confident positives" + ) + ax.set_xlabel("witness score (negative = perturbed-leaning)") + ax.set_ylabel("density") + ax.legend(fontsize=8) + fig.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close(fig) + + +def plot_mmd_null( + observed_mmd2: float, + null: NDArray, + p_value: float, + p_adjusted: float, + significant: bool, + marker: str, + condition: str, + output_path: Path, +) -> None: + """Plot the MMD² permutation-null with the observed statistic and p-values. + + The significance gate: is this condition's cloud distinct from the control + reference? The observed MMD² is marked against its permutation null; the raw + and Benjamini-Yekutieli-adjusted p-values and the gate verdict are annotated. + + Parameters + ---------- + observed_mmd2 : float + Observed unbiased MMD² (control vs condition). + null : NDArray + Permutation-null MMD² values, shape ``(n_permutations,)``. + p_value : float + Raw permutation p-value. + p_adjusted : float + Benjamini-Yekutieli-adjusted p-value (run-wide FDR). + significant : bool + Whether the condition cleared the FDR gate. + marker : str + Marker name (title). + condition : str + Condition name (title). + output_path : Path + Output file path. + """ + null = np.asarray(null).ravel() + fig, ax = plt.subplots(figsize=(7, 4.5)) + ax.hist(null, bins=50, color="0.7", edgecolor="white", label="permutation null") + ax.axvline(observed_mmd2, color="tab:red", lw=2, label=f"observed MMD² = {observed_mmd2:.3g}") + ax.set_title( + f"MMD significance — {marker} / {condition}\n" + f"raw p={p_value:.3g} · BY-adj p={p_adjusted:.3g} · " + f"{'SIGNIFICANT' if significant else 'not significant (skipped)'}" + ) + ax.set_xlabel("MMD²") + ax.set_ylabel("count") + ax.legend(fontsize=8) + fig.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close(fig) + + +def _pct_positive_by_time(times: NDArray, is_pos: NDArray) -> pd.DataFrame: + """Per-timepoint % positive ± Bernoulli SEM over the given cell population. + + Groups by timepoint and returns, per timepoint, ``100·k/n`` and its Bernoulli + standard error ``100·sqrt(p(1-p)/n)`` — the standard binomial error bar used + for a per-timepoint positive rate (matches the paper figure convention). + """ + df = pd.DataFrame({"t": np.asarray(times), "is_pos": np.asarray(is_pos, dtype=float)}) + rows = [] + for t, grp in df.groupby("t"): + n = len(grp) + p = float(grp["is_pos"].mean()) + rows.append({"t": t, "pct": 100.0 * p, "sem": 100.0 * np.sqrt(p * (1 - p) / n), "n": n}) + return pd.DataFrame(rows).sort_values("t") + + +def plot_remodeling_vs_time( + cond_time: dict, + cond_gmm: dict, + control_time: NDArray | None, + positive_class: str, + marker: str, + output_path: Path, + time_is_hpp: bool = True, +) -> None: + """Plot % of the *whole well population* in the positive class vs time ± SEM. + + For each perturbed condition, the denominator at each timepoint is **every** + perturbed-well cell (labeled positive plus the GMM-dropped ambiguous ones), + not only the confident positives — plotting positives / all-perturbed-cells is + what reveals the remodeling/infection *rise* over time (the fraction over + labeled cells alone is flat ~100% by construction, since the gate keeps only + positives). The numerator is the GMM confident-positive count + (``hard_label == 1``). The control reference is drawn dashed as the ~0% + baseline. Error bars are Bernoulli SEM per timepoint. Style follows the + paper's ``plot_infection_state_vs_time`` figure. + + Parameters + ---------- + cond_time : dict + ``condition -> np.ndarray`` of the time value per perturbed cell (same + order as the condition's GMM ``hard_label``). Empty → nothing plotted. + cond_gmm : dict + ``condition -> GmmLabelResult``; ``hard_label == 1`` marks confident + positives among that condition's perturbed cells. + control_time : NDArray or None + Time value per control-reference cell (the 0% baseline, drawn dashed). + None → no baseline line. + positive_class : str + The positive class value (e.g. ``"infected"``), for labeling. + marker : str + Marker name (title). + output_path : Path + Output file path. + time_is_hpp : bool + Whether the time axis is ``hours_post_perturbation`` (else raw timepoint + ``t``), for the axis label. By default True. + """ + if not cond_time: + return + + fig, ax = plt.subplots(figsize=(10, 5)) + colors = plt.rcParams["axes.prop_cycle"].by_key()["color"] + + for i, (condition, times) in enumerate(cond_time.items()): + res = cond_gmm.get(condition) + if res is None: + continue + stats = _pct_positive_by_time(times, res.hard_label == 1) + ax.errorbar( + stats["t"], + stats["pct"], + yerr=stats["sem"], + marker="o", + markersize=4, + capsize=2, + linewidth=1.8, + color=colors[i % len(colors)], + label=f"{condition} (n={int(stats['n'].sum())})", + ) + + if control_time is not None and len(control_time): + stats = _pct_positive_by_time(control_time, np.zeros(len(control_time))) + ax.errorbar( + stats["t"], + stats["pct"], + yerr=stats["sem"], + linestyle="--", + linewidth=1.0, + alpha=0.6, + color="0.4", + label=f"control (n={int(stats['n'].sum())})", + ) + + ax.set_ylim(-5, 105) + ax.set_title(f"% cells in '{positive_class}' vs time — {marker}", fontsize=11, fontweight="bold") + ax.set_xlabel("hours post perturbation" if time_is_hpp else "timepoint", fontsize=11) + ax.set_ylabel(f"% cells '{positive_class}'", fontsize=11) + ax.grid(True, alpha=0.3) + ax.legend(frameon=True, fontsize=9) + fig.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close(fig) From 214b3989c836137bd7a46c13f9dcbbbf483fd2fa Mon Sep 17 00:00:00 2001 From: Eduardo Hirata-Miyasaki Date: Thu, 23 Jul 2026 11:07:19 -0700 Subject: [PATCH 56/89] markdown cleanup --- .../dynaclr/docs/DAGs/ai_ready_datasets.md | 184 ++-- applications/dynaclr/docs/DAGs/end_to_end.md | 231 ++--- applications/dynaclr/docs/DAGs/evaluation.md | 850 +++++------------- .../dynaclr/docs/DAGs/evaluation_matrix.md | 144 +++ .../dynaclr/docs/DAGs/inference_triplet.md | 325 +++---- .../dynaclr/docs/DAGs/lot_correction.md | 133 +++ applications/dynaclr/docs/DAGs/pseudotime.md | 841 ++++------------- applications/dynaclr/docs/DAGs/training.md | 225 ++--- .../docs/DAGs/witness_gmm_classifiers.md | 4 +- 9 files changed, 994 insertions(+), 1943 deletions(-) create mode 100644 applications/dynaclr/docs/DAGs/evaluation_matrix.md create mode 100644 applications/dynaclr/docs/DAGs/lot_correction.md diff --git a/applications/dynaclr/docs/DAGs/ai_ready_datasets.md b/applications/dynaclr/docs/DAGs/ai_ready_datasets.md index ceab6b769..f223c2900 100644 --- a/applications/dynaclr/docs/DAGs/ai_ready_datasets.md +++ b/applications/dynaclr/docs/DAGs/ai_ready_datasets.md @@ -1,95 +1,25 @@ -# Data Preparation DAG - -This is stage ① of the full pipeline. For how it connects to parquet build, -predict, and evaluation, see [end_to_end.md](end_to_end.md). - -## Entry point - -`prepare run -c prepare_config.yaml` (from `airtable_utils`) discovers wells and -channels from NFS, generates all configs and SLURM scripts, and submits the pipeline. - -```bash -prepare run 2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV \ - -c /path/to/prepare_config.yaml - -# Dry-run: generate configs/scripts without submitting -prepare run 2025_07_24_A549_SEC61_TOMM20_G3BP1_ZIKV \ - -c /path/to/prepare_config.yaml \ - --dry-run -``` - -## Step-by-step detail - -``` -NFS assembled zarr (intracellular_dashboard/organelle_dynamics/{dataset}/2-assemble/) - │ - ▼ -prepare run # discovers wells + channels from NFS zarr - │ airtable_utils.prepare_cli # validates dataset is in Airtable - │ airtable_utils.prepare # generates all configs and scripts - ▼ -{vast_output_dir}/ - ├── crop_concat.yml # biahub concatenate config (wells × channels) - ├── qc_config.yml # focus-slice QC config - ├── sbatch_overrides.sh # optional SLURM overrides for biahub's internal jobs - ├── 01_concatenate.sh # bash (not SLURM): runs biahub + rsync tracking - ├── 02_qc.sh # SLURM: GPU focus-slice detection - └── 03_preprocess.sh # SLURM: CPU normalization stats - │ - ▼ -bash 01_concatenate.sh # NOT a SLURM job — runs interactively - │ Step 1: conda run biahub concatenate -c crop_concat.yml -o {dataset}.zarr -m - │ biahub submits its own SLURM jobs internally via submitit; -m blocks until done - │ Step 2: rsync tracking zarr (NFS → VAST) - ▼ -{dataset}.zarr (OME-Zarr v0.5 / zarr v3, rechunked) -tracking.zarr (cell tracking results) - │ - ├──► sbatch 02_qc.sh # GPU (~30 min) - │ qc run -c qc_config.yml # focus-slice detection on Phase3D channel - │ → writes focus_slice metadata into {dataset}.zarr - │ - └──► sbatch 03_preprocess.sh # CPU, preempted partition (~4 hrs) - viscy preprocess # computes per-channel normalization stats - --data_path {dataset}.zarr - → writes normalization metadata into {dataset}.zarr -``` - -## Pipeline DAG (process dependency) - -``` -NFS zarr (assembled) - │ - ▼ -prepare run ──── generates configs + scripts - │ - ▼ -01_concatenate.sh (interactive bash, blocks until biahub SLURM jobs finish) - │ - ▼ -{dataset}.zarr + tracking.zarr - │ - ├──► 02_qc.sh (SLURM, GPU) → focus_slice metadata in zarr - └──► 03_preprocess.sh (SLURM, CPU) → normalization metadata in zarr +# Prepare an AI-ready dataset + +This workflow copies an assembled dataset from NFS to VAST, rechunks it as +OME-Zarr, copies tracking data, and adds focus and normalization metadata. + +```mermaid +flowchart TD + A["NFS assembled zarr + tracking zarr"] --> B["prepare run"] + B --> C["01_concatenate.sh
OME-Zarr on VAST + tracking copy"] + C --> D["02_qc.sh
focus_slice metadata"] + C --> E["03_preprocess.sh
normalization metadata"] + D --> F["AI-ready dataset"] + E --> F ``` -02_qc and 03_preprocess run in parallel (no dependency between them). -Both write metadata back to the same zarr; their outputs are checked by -`check_preprocessed()` before downstream training or evaluation. +`prepare run` executes `01_concatenate.sh`, waits for the internal biahub jobs, +then submits `02_qc.sh` and `03_preprocess.sh` in parallel. -## Key commands +## Required config - -| Step | Command | Input | Output | -| ----------------- | ------------------------------------------------- | ------------------ | --------------------------------------------------------------- | -| Generate + submit | `prepare run -c prepare_config.yaml` | NFS assembled zarr | scripts + configs, submits jobs | -| Status check | `prepare status -c prepare_config.yaml` | - | markdown table (NFS/VAST existence, zarr version, preprocessed) | -| Concatenate | `bash 01_concatenate.sh` | crop_concat.yml | {dataset}.zarr + tracking.zarr | -| QC | `sbatch 02_qc.sh` | qc_config.yml | focus_slice metadata in zarr | -| Preprocess | `sbatch 03_preprocess.sh` | {dataset}.zarr | normalization metadata in zarr | - - -## prepare_config.yaml format +Start from +[`applications/airtable/configs/prepare_config.yml`](../../../airtable/configs/prepare_config.yml). ```yaml nfs_root: /hpc/projects/intracellular_dashboard/organelle_dynamics @@ -97,70 +27,80 @@ vast_root: /hpc/projects/organelle_phenotyping/datasets workspace_dir: /hpc/mydata/eduardo.hirata/repos/viscy concatenate: - channel_names: null # null = auto-detect raw channels (Phase3D + "raw " prefix) + channel_names: null chunks_czyx: [1, 16, 256, 256] shards_ratio: [1, 1, 8, 8, 8] output_ome_zarr_version: "0.5" conda_env: biahub - sbatch_overrides: # optional: overrides for biahub's internal SLURM jobs - partition: preempted - mem-per-cpu: 16G qc: channel_names: [Phase3D] NA_det: 1.35 lambda_ill: 0.450 pixel_size: 0.1494 - midband_fractions: [0.125, 0.25] device: cuda - num_workers: 16 preprocess: - channel_names: -1 # -1 = all channels + channel_names: -1 num_workers: 32 - block_size: 32 slurm: qc: partition: gpu gres: gpu:1 cpus_per_task: 16 - mem_per_cpu: 4G time: "00:30:00" preprocess: - partition: preempted + partition: cpu cpus_per_task: 32 - mem_per_cpu: 4G time: "04:00:00" ``` -## Notes +Set dataset-specific acquisition parameters under `qc`. Leave +`concatenate.channel_names: null` to discover Phase3D and raw channels. + +## Run + +Check the current state: + +```sh +uv run --package airtable-utils prepare status \ + -c applications/airtable/configs/prepare_config.yml +``` + +Generate configs and scripts without execution: + +```sh +uv run --package airtable-utils prepare run \ + -c applications/airtable/configs/prepare_config.yml \ + --dry-run +``` -- `prepare run` validates the dataset exists in Airtable before generating anything. -Use `--force` to overwrite an existing VAST zarr (e.g. to upgrade from zarr v2 to v0.5). -- `01_concatenate.sh` is an interactive bash script, not a SLURM job. Run it from a login -node or an interactive session; it blocks until biahub's internal SLURM jobs finish (`-m` flag). -- `02_qc.sh` and `03_preprocess.sh` are independent — submit both immediately after -`01_concatenate.sh` completes; no need to wait for QC before running preprocess. -- Channel auto-detection (`channel_names: null`) keeps channels with prefix `Phase3D` or `raw` . -Virtual stains (`nuclei_prediction`, `membrane_prediction`) and deconvolved channels are excluded. -- `check_preprocessed()` checks for `normalization` key in zarr metadata; used by `prepare status` -and as a gate before evaluation. -- Raw channel names written to `crop_concat.yml` are repeated once per well entry — this is a -biahub concatenate requirement. +Run the complete preparation workflow: -## Path convention +```sh +uv run --package airtable-utils prepare run \ + -c applications/airtable/configs/prepare_config.yml +``` -All AI-ready data lives under `/hpc/projects/organelle_phenotyping/`: +Use `--force` only when an existing VAST zarr must be replaced. +## Outputs -| Directory | Contents | -| -------------------------- | --------------------------------------------- | -| `datasets//` | Zarr v3 store + `tracking.zarr` | -| `datasets/annotations/` | Per-experiment annotation CSVs | -| `models/collections/` | Cell index parquets (one per collection YAML) | -| `models//` | Training runs (checkpoints, WandB configs) | +```text +// +├── .zarr +├── tracking.zarr +├── crop_concat.yml +├── qc_config.yml +├── 01_concatenate.sh +├── 02_qc.sh +└── 03_preprocess.sh +``` +The dataset is ready when `prepare status` reports the expected OME-Zarr +version and both `focus_slice` and `normalization` metadata are present. -Collection YAMLs use `datasets_root: /hpc/projects/organelle_phenotyping` and -`${datasets_root}/datasets/...` placeholders — resolved at load time by `load_collection()`. +Continue with [training.md](training.md) to build a cell index, or +[inference_triplet.md](inference_triplet.md) to predict directly from the zarr +and tracking store. diff --git a/applications/dynaclr/docs/DAGs/end_to_end.md b/applications/dynaclr/docs/DAGs/end_to_end.md index 64ffa0225..396c79b95 100644 --- a/applications/dynaclr/docs/DAGs/end_to_end.md +++ b/applications/dynaclr/docs/DAGs/end_to_end.md @@ -1,184 +1,107 @@ -# End-to-End DAG (new dataset → prediction per-marker embeddings → downstream tasks) +# DynaCLR workflow overview -## Summary: +This page is the entry point from an assembled dataset to reusable embeddings +and downstream evaluation. -This document details the methods to go from zarr datasets -> predictions ready for downstream tasks. - -These are the two methods: - -- **Primary spine:** the **Airtable →** `dynaclr predict-triplet` per-marker route — a -single command takes a git-tracked collection + a checkpoint and writes one embeddings -zarr per (experiment, marker) into a provenance-scoped tree. Use this to get a new -dataset to embeddings fast. -- **Alternate spine:** the **parquet-first** route (`build-cell-index` → `viscy predict` -→ `split-embeddings`) for large, reproducible multi-experiment runs — see -[evaluation.md](evaluation.md). It produces per-marker embeddings too (as -`{experiment}_{marker}.zarr` via `split-embeddings --prefix-by experiment`); the primary -spine writes them into the dataset-centric tree described below. - -## Quickstart — new dataset → embeddings → eval - -Run inference **once** per dataset (GPU), then evaluate the frozen embeddings **any number -of times** (CPU). Worked example: `2026_04_14_A549_SEC61B_DENV`. - -**1. Build the collection** (Airtable → git-tracked recipe). One-time per dataset. - -```bash -# skill: airtable-build-collection → applications/dynaclr/configs/collections/<...>/.yml -``` - -**2. Predict embeddings — all markers, into the dataset's own tree.** Once per dataset (or a -new checkpoint). `--datasets-root` selects the base; the dataset folder + `{marker}.zarr` -filename are derived automatically, so multiple markers co-locate. - -```bash -dynaclr predict-triplet \ - -c applications/dynaclr/configs/collections/organelle-box-denv-zikv/2026_04_14_A549_SEC61B_DENV.yml \ - --checkpoint /hpc/projects/organelle_phenotyping/models/.../epoch=105-step=84800.ckpt \ - --model-family DynaCLR-2D-MIP-BagOfChannels \ - --run 2d-mip-fix-shuffler \ - --ckpt-name epoch105-step84800 \ - --datasets-root /hpc/projects/intracellular_dashboard/organelle_dynamics \ - --z-range 15 45 --z-reduction mip --reference-pixel-size 0.1494 \ - --num-workers 0 # [--markers SEC61B] to subset; [--no-labelfree] to skip Phase3D +```mermaid +flowchart TD + A["Assembled image + tracking zarrs"] --> B["Prepare AI-ready dataset"] + B --> C["Collection YAML"] + C --> D["Train model
(optional when using an existing checkpoint)"] + C --> E["Predict per-marker embeddings"] + D --> E + E --> F["Frozen embedding zarrs"] + F --> G["Evaluation"] + F --> H["LOT correction"] + F --> I["Witness-GMM labels"] + F --> J["Pseudotime"] ``` -Writes (one per marker; dataset is in the path, so the filename is just `{marker}.zarr`): +## 1. Prepare the dataset -``` -//2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/ - SEC61B.zarr viral_sensor.zarr Phase3D.zarr +```sh +uv run --package airtable-utils prepare run \ + -c applications/airtable/configs/prepare_config.yml ``` -**3. Evaluate the embeddings — one command over the cohort.** No GPU, no re-prediction. +This produces an OME-Zarr and tracking store with focus and normalization +metadata. See [ai_ready_datasets.md](ai_ready_datasets.md). -```bash -uv run dynaclr eval \ - --eval-config applications/dynaclr/configs/evaluation/.yaml \ - --model-family DynaCLR-2D-MIP-BagOfChannels \ - --run 2d-mip-fix-shuffler --ckpt-name epoch105-step84800 \ - [--datasets 2026_04_14_A549_SEC61B_DENV ...] # omit = all datasets for this model/run/ckpt -``` +## 2. Create the collection -This builds the `--embeddings_glob` and launches the Nextflow `eval_from_embeddings` entry -(reduce / smoothness / MMD / linear classifiers / append / plots). Equivalent direct call: +Create or update a collection under: -```bash -nextflow run applications/dynaclr/nextflow/main.nf -entry eval_from_embeddings \ - --eval_config .yaml \ - --embeddings_glob '/hpc/projects/intracellular_dashboard/organelle_dynamics/*/2-phenotyping/predictions/DynaCLR-2D-MIP-BagOfChannels/2d-mip-fix-shuffler/epoch105-step84800/*.zarr' \ - -resume +```text +applications/dynaclr/configs/collections/ ``` -**Progressive add:** a new dataset → step 2 for it → re-run step 3 with the same glob; the -cohort grows implicitly (the `*` in the dataset slot picks up the new one). Iterating a -classifier or adding a downstream task re-runs step 3 only — the embeddings are frozen. +The collection is the shared input for cell-index construction and +`predict-triplet`. It must define each experiment's image store, tracking store, +channels, marker names, and well selection. -See [inference_triplet.md](inference_triplet.md) for `predict-triplet` flag detail and -[../../nextflow/README.md](../../nextflow/README.md) for the two Nextflow entries. +## 3. Train or select a checkpoint -## Primary spine — Airtable → per-marker inference +To train a model: -```mermaid -flowchart TD - A["① new dataset
(assembled NFS zarr)"] - A -->|"prepare run → concatenate
→ QC find-Z + preprocess normalize"| B["{dataset}.zarr + tracking.zarr
(AI-ready: focus_slice + normalization in zattrs)"] - B -->|"register / sync — skill: airtable-register"| C["①·5 Airtable records
(well perturbations + zarr metadata:
data_path, tracks_path, channel_names,
focus_slice, norm stats, pixel size)"] - C -->|"skill: airtable-build-collection
(git-commit the YAML)"| D["② collection.yml
(experiments · channels name/marker/wells ·
Provenance stamps base_id + query)"] - D -->|"dynaclr predict-triplet
-c collection.yml --checkpoint … --model-family …
--run … --ckpt-name … --datasets-root …
[--markers …] [--no-labelfree]"| E["③ per-marker embeddings
one zarr per (experiment, marker)"] - E --> F["/2-phenotyping/predictions/
{model_family}/{run}/{ckpt_name}/{marker}.zarr
(AnnData: .X = features, obs = fov_name/track_id/t/…)"] - F --> QC["③·5 embedding-consistency QC
dynaclr embedding-consistency-qc
(per marker · uninfected wells anchor)"] - F --> DOWN["downstream tasks (fan-out below)"] +```sh +uv run dynaclr fit \ + -c applications/dynaclr/configs/training/.yml ``` - - -### Output: - -``` -/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr +See [training.md](training.md). Skip this stage when a compatible checkpoint +already exists. + +## 4. Predict embeddings + +```sh +uv run dynaclr predict-triplet \ + -c applications/dynaclr/configs/collections/.yml \ + --checkpoint /path/to/checkpoint.ckpt \ + --model-family \ + --run \ + --ckpt-name \ + --datasets-root /hpc/projects/intracellular_dashboard/organelle_dynamics \ + --z-range 15 45 \ + --z-reduction mip \ + --reference-pixel-size 0.1494 \ + --num-workers 0 ``` -## Downstream fan-out +The command writes one zarr per experiment and marker: -Every downstream task consumes the per-marker `{marker}.zarr` embeddings. Each is an -independent, Nextflow-able unit that links to its owning DAG. - -```mermaid -flowchart TD - E["…/{model}/{run}/{ckpt}/{marker}.zarr
(.X = features, obs = cell metadata)"] - E --> LC["linear classifiers
dynaclr run-linear-classifiers
→ append-annotations → append-predictions"] - E --> MMD["cross-experiment MMD / LOT correction
dynaclr compute-mmd · fit/apply-lot-correction"] - E --> PT["pseudotime alignment"] - E --> DR["dimensionality reduction + plots
dynaclr reduce-dimensionality · plot-embeddings"] - LC --> LCd["evaluation.md · witness_gmm_classifiers.md"] - MMD --> MMDd["lot_correction.md"] - PT --> PTd["pseudotime.md"] - DR --> DRd["evaluation.md"] +```text +//2-phenotyping/predictions/ + ///.zarr ``` +See [inference_triplet.md](inference_triplet.md) for input requirements and +optional flags. +## 5. Evaluate frozen embeddings +Use the launcher when embeddings already exist: - -## Alternate spine — parquet-first (large reproducible runs) - -```mermaid -flowchart TD - D["collection.yml"] - D -->|"dynaclr build-cell-index "| P1["cell_index.parquet"] - P1 -->|"dynaclr preprocess-cell-index
--focus-channel Phase3D --focus-level fov"| P2["cell_index.parquet (empty frames dropped)"] - P2 -->|"viscy predict (MultiExperimentDataModule)"| P3["combined embeddings.zarr
(obs tags marker + experiment)"] - P3 -->|"dynaclr split-embeddings --group-by marker --prefix-by experiment"| E["{experiment}_{marker}.zarr"] +```sh +uv run dynaclr eval \ + --eval-config applications/dynaclr/configs/evaluation/.yaml \ + --model-family \ + --run \ + --ckpt-name ``` +This launches the Nextflow `eval_from_embeddings` workflow. Reusing frozen +embeddings keeps prediction separate from downstream iteration. See +[evaluation.md](evaluation.md). +## Downstream workflows -The parquet path tags every row with `marker` (bag-of-channels explodes each cell into one -row per channel), so per-marker splitting is a single -`split-embeddings --group-by marker --prefix-by experiment`. See [evaluation.md](evaluation.md). - -## Stage-by-stage - - -| # | Stage | Entry command | Owning DAG | Nextflow-able | -| --- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| ① | **Preprocess** — find focus-Z + normalize | `prepare run -c prepare_config.yaml` → `sbatch 02_qc.sh` + `sbatch 03_preprocess.sh` | [ai_ready_datasets.md](ai_ready_datasets.md) | ❌ (batch tooling, not NF) | -| ①·5 | **Register / sync Airtable** | skill `airtable-register` (well records + zarr `.zattrs` metadata) | [inference_triplet.md](inference_triplet.md) | ❌ human-in-the-loop | -| ② | **Build collection** | skill `airtable-build-collection` → `collection.yml` (git-committed) | [inference_triplet.md](inference_triplet.md) | ❌ human-in-the-loop | -| ③ | **Predict embeddings (per marker)** | `dynaclr predict-triplet -c collection.yml --checkpoint … --model-family … --run … --ckpt-name … [--datasets-root …] [--markers …] [--no-labelfree]` | [inference_triplet.md](inference_triplet.md) | ✅ one process per (exp, marker) | -| ③·5 | **Embedding-consistency QC** | `dynaclr embedding-consistency-qc -c .yml` | `[embedding_consistency_qc.md](../../../../.ed_planning/dynaclr/batch_correction/embedding_consistency_qc.md)` | ✅ | -| ④ | **Run linear classifiers** | `dynaclr run-linear-classifiers -c clf.yml` | [evaluation.md](evaluation.md), [witness_gmm_classifiers.md](witness_gmm_classifiers.md) | ✅ | -| ⑤ | **Append predictions** | `dynaclr append-annotations -c …` → `dynaclr append-predictions -c …` | [evaluation.md](evaluation.md) | ✅ | -| — | **MMD / LOT correction** | `dynaclr compute-mmd` · `dynaclr fit-lot-correction` / `apply-lot-correction` | [lot_correction.md](lot_correction.md) | ✅ | -| — | **Pseudotime** | see owning DAG | [pseudotime.md](pseudotime.md) | ✅ | -| — | **Dim-reduction + plots** | `dynaclr reduce-dimensionality` · `dynaclr plot-embeddings` | [evaluation.md](evaluation.md) | ✅ | - - - - -## Alternate: parquet-first for stage ② - -The alternate spine replaces stages ②–③ with the parquet path — `build-cell-index` (with -`--focus-level fov`) → `preprocess-cell-index` → `viscy predict` → `split-embeddings`. It's -the right choice for large multi-experiment reproducible runs; the collection YAML feeds -both routes. See [training.md](training.md) §build and -[../recipes/build-cell-index.md](../recipes/build-cell-index.md). - -## Nextflow: inference and evaluation are two entries - -Embeddings are written **once** into the dataset-centric tree (stage ③), then evaluation -reads those **frozen** embeddings — no GPU, no re-prediction — so classifiers and downstream -tasks (④/⑤ and the fan-out) can be re-run freely without re-embedding. - -- **`-entry evaluation`** — full spine: predict → split → downstream. Use for the parquet - spine or a one-shot run. -- **`-entry eval_from_embeddings`** — skips predict/split; sources per-experiment zarrs from - `--embeddings_glob` and runs the same shared `DOWNSTREAM` DAG. The glob is the **cohort - selector** (`*` in the dataset slot); the eval config says *what to compute*. Add a dataset → - predict it → re-run the same glob; the cohort grows implicitly. +| Workflow | Command | Documentation | +| --- | --- | --- | +| Batch evaluation | `dynaclr eval` or Nextflow `evaluation` | [evaluation.md](evaluation.md) | +| Evaluation matrix | `dynaclr run-matrix` | [evaluation_matrix.md](evaluation_matrix.md) | +| LOT correction | `dynaclr fit-lot-correction` / `apply-lot-correction` | [lot_correction.md](lot_correction.md) | +| Witness pseudo-labels | `dynaclr witness-gmm-labels` | [witness_gmm_classifiers.md](witness_gmm_classifiers.md) | +| Pseudotime scripts | staged Python CLIs | [pseudotime.md](pseudotime.md) | -One-command launcher: `dynaclr eval` turns a `(model_family, run, ckpt_name)` identity into -the glob and launches the `eval_from_embeddings` entry. See -[../../nextflow/README.md](../../nextflow/README.md) for both entries and the run-once / -eval-many pattern. +Use one stable tuple—`model-family`, `run`, and `checkpoint-name`—for the +prediction directory and every downstream invocation. diff --git a/applications/dynaclr/docs/DAGs/evaluation.md b/applications/dynaclr/docs/DAGs/evaluation.md index 2a7d09f43..9568d9f1a 100644 --- a/applications/dynaclr/docs/DAGs/evaluation.md +++ b/applications/dynaclr/docs/DAGs/evaluation.md @@ -1,688 +1,248 @@ -# Evaluation DAG - -This document assumes a preprocessed dataset and a cell index parquet already -exist. For the upstream stages (new dataset → find-Z + normalize → build parquet), -see [end_to_end.md](end_to_end.md). - -This document describes the **per-run** evaluation pipeline (one model on -one dataset). For the cross-model, cross-dataset matrix layout — including -the central linear-classifier registry that lets Wave-2 datasets fetch LC -pipelines trained on Wave-1 (infectomics-annotated) — see the companion -[`evaluation_matrix.md`](evaluation_matrix.md). - -## Running with Nextflow (recommended) - -```bash +# Evaluate DynaCLR embeddings + +The evaluation workflow predicts or loads embeddings, then fans out +dimensionality reduction, plots, smoothness, MMD, and linear-classifier tasks. +Nextflow is the recommended runner. + +```mermaid +flowchart TD + A["Evaluation YAML"] --> B["prepare-eval-configs"] + B --> C{"Embedding source"} + C -->|"full workflow"| D["predict → split"] + C -->|"frozen embeddings"| E["embedding glob"] + D --> F["per-experiment zarrs"] + E --> F + F --> G["reduce + combined reduce"] + F --> H["smoothness"] + F --> I["MMD"] + F --> J["linear classifiers"] + J --> K["append labels and predictions"] + G --> L["plots"] + K --> L +``` + +## Choose an entry point + +Use `evaluation` when prediction must run from a checkpoint and cell-index +parquet: + +```sh module load nextflow/24.10.5 -nextflow run applications/dynaclr/nextflow/main.nf -entry evaluation \ - --eval_config applications/dynaclr/configs/evaluation/DynaCLR-2D-MIP-BagOfChannels/infectomics-annotated.yaml \ - --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ - -resume -``` - -`-resume` makes Nextflow skip steps whose outputs already exist. Re-run the same command after a failure — Nextflow picks up from where it left off. - -### Local test (no SLURM) - -```bash nextflow run applications/dynaclr/nextflow/main.nf \ - --eval_config applications/dynaclr/configs/evaluation/DynaCLR-2D-MIP-BagOfChannels_test.yaml \ - --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ - -profile local \ - -resume + -entry evaluation \ + --eval_config applications/dynaclr/configs/evaluation/.yaml \ + --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ + -resume ``` -## Pipeline entry point - -`dynaclr prepare-eval-configs` (also aliased as `dynaclr evaluate`) generates all YAML configs -under `output_dir/configs/` and prints a JSON manifest to stdout. Nextflow reads the manifest -to wire steps together. +Use `dynaclr eval` when per-marker embeddings already exist in the standard +prediction tree: -``` -eval_config.yaml - │ - ▼ -dynaclr prepare-eval-configs -c eval_config.yaml # writes configs/ + manifest JSON - │ - ▼ -output_dir/configs/ - ├── eval.yaml # copy of input config (for re-runs) - ├── predict.yml # GPU step: viscy predict - ├── reduce.yaml # template: dynaclr reduce-dimensionality (per-experiment) - ├── reduce_combined.yaml # CPU step: dynaclr combined-dim-reduction (joint) - ├── smoothness.yaml # template: dynaclr evaluate-smoothness (per-experiment) - ├── plot.yaml # template: dynaclr plot-embeddings (per-experiment) — only when "plot" in steps - ├── plot_combined.yaml # CPU step: dynaclr plot-embeddings (all experiments) — only when "plot_combined" in steps - ├── {block_name}.yaml # template: dynaclr compute-mmd (per-experiment, per-block) - ├── {block_name}_cross_exp.yaml # CPU step: dynaclr compute-mmd --combined (per-block) - └── linear_classifiers.yaml # CPU step (optional) +```sh +uv run dynaclr eval \ + --eval-config applications/dynaclr/configs/evaluation/.yaml \ + --model-family \ + --run \ + --ckpt-name ``` -## Step-by-step detail +Optional launcher filters: -``` -checkpoint.ckpt + cell_index.parquet - │ - ▼ -viscy predict -c predict.yml # MultiExperimentDataModule predict mode - │ EmbeddingWriter callback # normalizations + z_reduction, no augmentations - ▼ # obs: fov_name, id, t, track_id, -embeddings/embeddings.zarr # experiment, marker, perturbation, - │ (AnnData: .X=features, # hours_post_perturbation, organelle, well, microscope - │ .obs=cell metadata) - │ - ▼ -dynaclr split-embeddings \ - --input embeddings/embeddings.zarr \ - --output-dir embeddings/ - │ Splits by obs["experiment"], deletes combined zarr - │ Also writes configs/viewer.yaml (datasets: {exp: {hcs_plate, anndata}}) - │ hcs_plate read from obs["store_path"] of each split zarr - ▼ -embeddings/{experiment_A}.zarr -embeddings/{experiment_B}.zarr - ... -configs/viewer.yaml # nd-embedding viewer config (also valid input - ... # for combined-dim-reduction via datasets: key) - │ - ├──► dynaclr reduce-dimensionality # PCA only (per experiment, parallel SLURM jobs) - │ -c reduce.yaml # __ZARR_PATH__ substituted by Nextflow - │ → {experiment}.zarr (obsm: X_pca) - │ NOTE: skip PHATE here to avoid computing it twice - │ - │ (after reduce-dimensionality finishes for ALL experiments) - │ - ├──► dynaclr combined-dim-reduction # joint PCA + PHATE across all experiments - │ -c reduce_combined.yaml # fits on concatenated embeddings - │ → {experiment}.zarr (obsm: X_pca_combined, X_phate_combined) - │ - │ (after combined-dim-reduction finishes) - │ - ├──► dynaclr plot-embeddings # per-experiment PCA scatter (X_pca) — when "plot" in steps - │ -c plot.yaml # parallel SLURM jobs, one per experiment - │ → plots/{experiment}/*.pdf - │ - ├──► dynaclr plot-embeddings # all-experiments combined (X_pca_combined, X_phate_combined) - │ -c plot_combined.yaml # only when "plot_combined" in steps; one job - │ → plots/combined/*.pdf - │ - ├──► dynaclr evaluate-smoothness # temporal smoothness + dynamic range - │ -c smoothness.yaml # parallel SLURM jobs, one per experiment - │ → smoothness/{model}_per_marker_smoothness.csv # one row per marker - │ → smoothness/{model}_smoothness_stats.csv # mean ± std across markers - │ → smoothness/*.pdf # per-marker + per-model plots - │ - ├──► dynaclr compute-mmd # one SLURM job per (experiment, block) - │ -c {block_name}.yaml # __ZARR_PATH__ substituted by Nextflow - │ → mmd/{block_name}/mmd_results.csv - │ → mmd/{block_name}/kinetics.pdf - │ → mmd/{block_name}/activity_heatmap.pdf - │ - ├──► dynaclr compute-mmd --combined # pairwise cross-experiment batch effect detection - │ -c {block_name}_cross_exp.yaml # only generated when combined_mode: true - │ # For each marker shared by a pair of experiments, runs MMD per - │ # (condition, time_bin) after per-pair mean centering. - │ # Conditions are auto-discovered from data intersection. - │ → mmd/{block_name}_cross_exp/combined_mmd_results.csv - │ → mmd/{block_name}_cross_exp/kinetics.pdf - │ → mmd/{block_name}_cross_exp/activity_heatmap.pdf - │ - ├──► dynaclr run-linear-classifiers # logistic regression probe - │ -c linear_classifiers.yaml # reads per-experiment zarrs directory + annotation CSVs - │ # joins annotations on (fov_name, t, track_id); trains one LogisticRegression - │ # per (task, marker); marker_filters omitted → auto-discovers all markers - │ # witness→GMM weak labels: produce an annotation file upstream with - │ # `dynaclr witness-gmm-labels`, then train it here. See witness_gmm_classifiers.md - │ # writes trained pipelines to linear_classifiers/pipelines/ (in-run staging) - │ # if publish_dir is set: atomically promotes the bundle to the central - │ # LC registry as {publish_dir}/vN/ and updates the `latest` symlink. - │ → linear_classifiers/metrics_summary.csv - │ → linear_classifiers/{task}_summary.pdf - │ → linear_classifiers/pipelines/{task}_{marker}.joblib - │ → linear_classifiers/pipelines/manifest.json - │ → {publish_dir}/vN/{task}_{marker}.joblib (when publish_dir set) - │ → {publish_dir}/vN/manifest.json (when publish_dir set) - │ → {publish_dir}/latest -> vN (atomic symlink swap) - │ - ├──► dynaclr append-annotations # persist ground truth labels to per-experiment zarrs - │ -c append_annotations.yaml # reads annotation CSVs + writes task columns to zarr obs - │ # only experiments with AnnotationSource entries are processed; others skipped - │ → {experiment}.zarr (obs: infection_state, organelle_state, ...) - │ - └──► dynaclr append-predictions # apply saved classifiers - -c append_predictions.yaml # predicts on ALL cells per marker, not just annotated ones - # pipelines_dir may be either: - # (a) in-run: {output_dir}/linear_classifiers/pipelines/ (default), or - # (b) external: a `latest` symlink into the central LC registry - # (e.g., /hpc/.../linear_classifiers/{model_name}/latest) - # The symlink is resolved once at startup so the run is consistent - # even if a new bundle is published mid-run. Logs feature_space (= - # registry/{model_name}) and version (= vN) for traceability. - → {experiment}.zarr (obs: predicted_infection_state, ...) - → {experiment}.zarr (obsm: predicted_infection_state_proba, ...) - → {experiment}.zarr (uns: predicted_infection_state_classes, - predicted_infection_state_lc_version, - predicted_infection_state_lc_feature_space, - predicted_infection_state_lc_path, ...) - -checkpoint.ckpt (independent of predict/split — runs in parallel) - │ - ▼ -viscy export -c export_onnx.yml # export backbone to ONNX - │ - ▼ -model.onnx + CTC datasets ({seq}_ERR_SEG/, {seq}/, {seq}_GT/TRA/) - │ - ▼ -dynaclr evaluate-tracking-accuracy \ # ILP tracking on CTC benchmarks - -c tracking_accuracy.yaml # loops over (model, dataset, sequence) - │ builds tracksdata graph from segmentation masks - │ runs ONNX inference on cell crops → dynaclr_similarity edge cost - │ solves ILP; compares to GT via evaluate_ctc_metrics - │ set show_napari: true for interactive inspection - ▼ -tracking_accuracy/results.csv # one row per (model, dataset, sequence) -tracking_accuracy/ # grouped mean summary printed to stdout +```sh +--marker SEC61B +--datasets --datasets +--print-cmd ``` -After all enrichment steps complete, per-experiment zarrs contain: +The equivalent direct frozen-embedding entry is: -- `.obs`: embeddings metadata + annotations (`infection_state`, etc.) + predictions (`predicted_infection_state`, etc.) -- `.obsm`: `X_pca`, `X_pca_combined`, `X_phate_combined`, `predicted_{task}_proba` -- `.uns`: `predicted_{task}_classes`, `predicted_{task}_lc_version`, `predicted_{task}_lc_feature_space`, `predicted_{task}_lc_path` - -This enables plots colored by experiment, perturbation, annotation, and prediction from a single zarr. The `_lc_*` uns fields record exactly which LC bundle produced each predicted column (registry path, version tag, feature_space). - -## Central LC registry - -Linear-classifier pipelines can be **published** to a central per-model -registry instead of (or in addition to) the per-run `output_dir`. This lets -later evaluations on different datasets reuse the same trained classifiers -without retraining. - -### Layout - -``` -/hpc/projects/organelle_phenotyping/models/linear_classifiers/ -├── DynaCLR-2D-MIP-BagOfChannels/ -│ ├── latest -> v3 # symlink (relative target) -│ ├── v1/ {manifest.json, *.joblib} -│ ├── v2/ -│ └── v3/ -├── DynaCLR-2D-BagOfChannels-v3/ { same } -├── DynaCLR-classical/ { same } -├── DINOv3-temporal-MLP-2D-BagOfChannels-v1/ { same } -└── DINOv3-frozen/ { same } +```sh +nextflow run applications/dynaclr/nextflow/main.nf \ + -entry eval_from_embeddings \ + --eval_config applications/dynaclr/configs/evaluation/.yaml \ + --embeddings_glob '/*/2-phenotyping/predictions////*.zarr' \ + --workspace_dir /hpc/mydata/eduardo.hirata/repos/viscy \ + -resume ``` -The directory name (e.g. `DynaCLR-2D-MIP-BagOfChannels`) is the -**feature_space** identifier — pipelines from one model's registry are -*not* applicable to a different model's embeddings (different dim, different -distribution). The model name follows the training-config-stem convention -(see `evaluation_matrix.md` §7). +Add `-profile local` for a local smoke test. Keep `-resume` for recoverable and +incremental runs. -### Publishing (writer) +## Evaluation config -A Wave-1 leaf (training run) sets `linear_classifiers.publish_dir`: +Create model-specific leaves under +[`applications/dynaclr/configs/evaluation/`](../../configs/evaluation/) and +compose shared settings from `recipes/`. ```yaml -linear_classifiers: - publish_dir: /hpc/projects/organelle_phenotyping/models/linear_classifiers/DynaCLR-2D-MIP-BagOfChannels/ - # ... annotations, tasks, ... -``` - -`run-linear-classifiers` writes pipelines to a temp staging directory, -atomically renames to `vN/` (next available version), then atomically -swaps the `latest` symlink. Crash-safe: a partial bundle never appears as -`vN/`. +base: + - ../recipes/predict.yml + - ../recipes/reduce.yml + - ../recipes/plot_infectomics.yml + - ../recipes/infectomics-annotated.yml -### Fetching (reader) +training_config: /path/to/resolved-training-config.yaml +ckpt_path: /path/to/checkpoint.ckpt +cell_index_path: /path/to/cell-index.parquet +output_dir: /path/to/evaluation-output -A Wave-2 leaf (evaluation on a different dataset) sets -`append_predictions.pipelines_dir`: - -```yaml -append_predictions: - pipelines_dir: /hpc/projects/organelle_phenotyping/models/linear_classifiers/DynaCLR-2D-MIP-BagOfChannels/latest -``` - -`append-predictions` resolves the symlink **once** at startup and uses the -resolved `vN/` for the rest of the run, so a publish during the run does -not affect output. The resolved path's parent name (`DynaCLR-2D-MIP-BagOfChannels`) -becomes `feature_space` in the manifest log. - -### Manifest format - -```json -{ - "trained_at": "2026-04-24T15:33:21+00:00", - "pipelines": [ - {"task": "infection_state", "marker_filter": "G3BP1", "path": "infection_state_G3BP1.joblib"}, - {"task": "infection_state", "marker_filter": "SEC61B", "path": "infection_state_SEC61B.joblib"} - ] -} +steps: + - predict + - split + - reduce_dimensionality + - reduce_combined + - smoothness + - mmd + - linear_classifiers + - append_annotations + - append_predictions + - plot + - plot_combined ``` -Lineage (model name + version) lives in the directory structure, not the -manifest. Reproducibility comes from pinning a specific `vN` (instead of -`latest`) in paper-rerun scripts. - -### Pinning vs. latest - -```yaml -# active development — picks up the latest published bundle -pipelines_dir: /hpc/.../linear_classifiers/DynaCLR-2D-MIP-BagOfChannels/latest - -# paper rerun — frozen at submission time -pipelines_dir: /hpc/.../linear_classifiers/DynaCLR-2D-MIP-BagOfChannels/v2 -``` +Only list the steps required by the run. For frozen embeddings, `predict` and +`split` are not executed by `eval_from_embeddings`, even if inherited from a +shared recipe. -## Nextflow DAG (process dependency graph) +Generate and inspect the resolved step configs without launching Nextflow: +```sh +uv run dynaclr prepare-eval-configs \ + -c applications/dynaclr/configs/evaluation/.yaml ``` -checkpoint.ckpt ──────────────────────────────────────────────────────────────┐ - │ │ - ▼ ▼ -PREPARE_CONFIGS EXPORT_ONNX (optional) - │ │ - ▼ ▼ -PREDICT (GPU) model.onnx + CTC datasets - │ │ - ▼ ▼ -SPLIT (CPU light) TRACKING_ACCURACY (CPU) - │ → results.csv - ├─[scatter]─► REDUCE ─[gather]─► REDUCE_COMBINED ─┐ - │ │ - ├─► APPEND_ANNOTATIONS ───────────────────────────►├─[scatter]─► PLOT (only if "plot" in steps) - │ │ [gather] ─► PLOT_COMBINED (only if "plot_combined" in steps) - ├─► LINEAR_CLASSIFIERS ─► APPEND_PREDICTIONS ─────►┘ - │ - ├─[scatter]─► SMOOTHNESS ─[gather]─► SMOOTHNESS_GATHER - ├─[scatter per (exp,block)]─► MMD ─[gather]─► MMD_PLOT_HEATMAP - └─[gather per block]─► MMD_COMBINED -``` - -Key: **scatter** = one SLURM job per experiment (parallel). **gather** = waits for all scatter jobs. - -`TRACKING_ACCURACY` is independent of the embedding pipeline — it reads directly from an ONNX -model and CTC-format data. Run it manually or as a separate Nextflow job alongside the main DAG. - -`PLOT` (per-experiment fan-out) and `PLOT_COMBINED` (single combined figure) are -**independently togglable** via `steps:`. List `plot` for per-experiment scatter only, -`plot_combined` for the joint figure only, both for both, or neither for a metrics-only -run. `APPEND_ANNOTATIONS` and `APPEND_PREDICTIONS` emit a `'skip'` signal when not -present in `steps`, so plotting always proceeds once `REDUCE_COMBINED` finishes — -whichever plotting steps are listed. - -## CTC Tracking Accuracy Benchmark -Standalone benchmark that evaluates whether DynaCLR embeddings improve cell tracking -accuracy on [Cell Tracking Challenge](https://celltrackingchallenge.net/) datasets. -**Not part of the Nextflow embedding pipeline** — run independently after exporting an ONNX model. +Generated YAMLs and the copied input config are written under +`/configs/`. -### Approach +## MMD block -``` -CTC segmentation masks + raw images - │ - ▼ -tracksdata graph (RegionPropsNodes + DistanceEdges) - │ - ├── baseline: IoU edge weights (no model) - │ - └── DynaCLR: ONNX inference on cell crops - → dynaclr_similarity × spatial_dist_weight as ILP edge cost - │ - ▼ -ILPSolver → tracked graph - │ - ▼ -evaluate_ctc_metrics vs. ground truth - │ - ▼ -results.csv (model × dataset × sequence × CTC metrics) -``` - -### Usage - -```bash -dynaclr evaluate-tracking-accuracy -c tracking_accuracy_config.yaml -``` - -### Config format +Each item under `mmd` becomes one per-experiment analysis and, when +`combined_mode: true`, one cross-experiment analysis. ```yaml -models: - - path: /hpc/projects/.../model_ckpt146.onnx - label: DynaCLR-classical - - path: /hpc/projects/.../model_ckpt185.onnx - label: DynaCLR-timeaware - - path: null # baseline: IoU + spatial distance only - label: baseline-iou - -datasets: - - path: /hpc/reference/group.royer/CTC/training/BF-C2DL-HSC - sequences: ["01", "02"] - - path: /hpc/reference/group.royer/CTC/training/Fluo-C2DL-Huh7 - sequences: ["01", "02"] - -crop_shape: [64, 64] # must match the model's training resolution -distance_threshold: 325.0 # spatial candidate edge threshold (pixels) -n_neighbors: 10 -delta_t: 5 # max frame gap for candidate edges -batch_size: 128 -output_dir: /path/to/tracking_accuracy_results -``` - -### Output - -**`results.csv`** — one row per (model, dataset, sequence): - -| Column | Description | -|--------|-------------| -| `model` | Model label | -| `dataset` | CTC dataset name | -| `sequence` | Sequence number (01, 02) | -| `LNK` | CTC Linking metric | -| `TRA` | Tracking metric | -| `DET` | Detection metric | -| `CHOTA` | Cell-specific HOTA | -| `HOTA` | Higher Order Tracking Accuracy | -| `MOTA` | Multiple Object Tracking Accuracy | -| `IDF1` | ID F1 score | -| `BIO(0)` | Biological metric | -| `OP_CLB(0)` | Combined linking+bio score | - -Prints a grouped summary (mean over sequences) at the end. - -### Prerequisites - -1. Export the model to ONNX: - ```bash - viscy export -c export_onnx.yml - ``` -2. CTC datasets must have `{seq}_ERR_SEG/`, `{seq}/`, and `{seq}_GT/TRA/` subdirectories. -3. Install eval dependencies: `uv sync --all-packages --extra eval` - -## Pseudotime alignment benchmark - -Standalone benchmark that quantifies how well DTW on DynaCLR embeddings recovers per-cell biological -event onsets (e.g. infection onset from the NS3 sensor channel). **Not part of the Nextflow embedding -pipeline** — runs after `linear_classifiers` + `append_predictions` so it can use either human -`infection_state` or model-predicted `predicted_infection_state` as ground truth. - -Full pipeline (template build + DTW alignment) lives in `applications/dynaclr/scripts/pseudotime/` — -see [`pseudotime.md`](pseudotime.md). The scoring step described below consumes the Stage 2a -alignment parquet that pipeline produces. - -### Approach - -``` -embeddings/{experiment}.zarr (with ground-truth obs) - │ - ▼ -pseudotime/2-align_cells/alignments/ - {template}_{flavor}_on_{query_set}.parquet - │ (per-frame: pseudotime, estimated_t_rel_minutes, alignment_region) - ▼ -score_alignment.py --method {dtw | no_align} - │ - │ Per cell: onset_error_minutes = estimated_t_rel_minutes at the first - │ aligned positive frame preceded by a negative frame - │ Population: AUROC + F1@0 over (cell, frame) pairs in aligned region - ▼ -scoring/{template}_{flavor}_on_{query_set}_{method}_per_cell.parquet -scoring/{template}_{flavor}_on_{query_set}_{method}_summary.md -scoring/results.csv (one row per run, accumulates across runs) - │ - ▼ -compare_methods.py - │ - ▼ -scoring/compare_methods.md (paper table) -scoring/compare_methods.png (4-panel bar chart: med|Δt|, IQR, AUROC, F1@0) +mmd: + - name: perturbation + group_by: perturbation + comparisons: + - cond_a: control + cond_b: perturbed + label: control_vs_perturbed + temporal_bin_size: 4.0 + combined_temporal_bin_size: null + combined_mode: true + embedding_key: null + mmd: + n_permutations: 1000 + max_cells: 5000 ``` -### Method comparison philosophy - -The benchmark compares **alignment methods on the same DynaCLR embedding**, not different -embeddings. Two methods are bundled: - -- **`dtw`** — uses `estimated_t_rel_minutes` from the Stage 2a parquet (DBA template + subsequence - DTW on the embedding trajectory). -- **`no_align`** — substitutes `estimated_t_rel_minutes` with each cell's frame index relative to - its track midpoint, no learning. The lower bound DTW must beat. - -This is the right comparison for the paper claim *"DTW on DynaCLR embeddings recovers infection -onset"* — the embedding is held fixed, so the metric attributes the gain to the alignment step, -not to representation quality. - -### Usage - -```bash -cd applications/dynaclr/scripts/pseudotime/2-align_cells -# Score one alignment parquet (DTW) -uv run python score_alignment.py \ - --datasets ../../../configs/pseudotime/datasets.yaml \ - --config ../../../configs/pseudotime/align_cells.yaml \ - --template infection_nondividing_sensor --flavor raw \ - --query-set sensor_all_07_24 \ - --truth-column infection_state --truth-positive infected \ - --method dtw +For standalone runs, use a generated MMD YAML: -# No-align baseline on the same parquet -uv run python score_alignment.py ... --method no_align - -# Render comparison artifacts -uv run python compare_methods.py +```sh +uv run dynaclr compute-mmd -c mmd.yaml +uv run dynaclr compute-mmd --combined -c mmd_cross_exp.yaml +uv run dynaclr compute-mmd --pooled -c mmd_pooled.yaml ``` -### Output schema (`scoring/results.csv`) - -| Column | Description | -|---|---| -| `template`, `flavor`, `query_set` | identifies the alignment parquet | -| `method` | `dtw` or `no_align` | -| `truth_column`, `truth_positive` | which obs column was the ground truth | -| `n_cells_scored` | cells with a usable negative→positive transition in the aligned region | -| `median_abs_onset_error_minutes` | robust center of \|Δt_onset\|; **primary metric** | -| `iqr_abs_onset_error_minutes` | spread of \|Δt_onset\| | -| `median_signed_onset_error_minutes` | systematic bias (≈0 if unbiased) | -| `auroc` | over aligned (cell, frame) pairs ranked by warped time; chance = 0.5 | -| `f1_at_zero` | F1 at the threshold `estimated_t_rel_minutes ≥ 0` | -| `n_pairs` | aligned (cell, frame) pairs used by AUROC/F1 | -| `timestamp_utc` | run timestamp | - -### Prerequisites - -1. A built template under `1-build_template/templates/` (see `pseudotime.md`). -2. A Stage 2a alignment parquet under `2-align_cells/alignments/`. -3. The query embedding zarrs must carry the requested `--truth-column` (human `infection_state` - on 07_22/07_24; `predicted_infection_state` on 08_26/01_28 — populated by - `APPEND_PREDICTIONS`). - -## Cross-model comparison +## Linear classifiers -After running evals for multiple models, compare results with: - -```bash -python applications/dynaclr/scripts/evaluation/compare_evals.py -c eval_registry.yml -``` - -Registry format: +Classifier tasks use annotation files keyed to cells. Use a group-aware split +when tracks contribute multiple frames. ```yaml -models: - - name: DynaCLR-v3 - eval_dir: /path/to/eval_v3 # = output_dir of that model's eval run - - name: DINOv3-MLP - eval_dir: /path/to/eval_dino -output_dir: /path/to/comparison_output -fdr_threshold: 0.05 -``` +linear_classifiers: + label_source: annotations + annotations: + - experiment: + path: /path/to/annotations.csv + tasks: + - task: infection_state + marker_filters: [Phase3D] + use_scaling: true + use_pca: false + split_train_data: 0.8 + split_groups_by: [experiment, fov_name, track_id] + random_seed: 42 +``` + +The workflow trains one pipeline per task-marker pair. Witness-GMM labels use +the same annotation path; see +[witness_gmm_classifiers.md](witness_gmm_classifiers.md). + +To publish a fitted bundle for later runs: -`eval_dir` is the **`output_dir`** declared in each leaf eval YAML (where -`smoothness/`, `linear_classifiers/`, `mmd/` land). One row per model × dataset -pair you want to compare side-by-side. Auto-discovers results and produces -overlaid plots and summary CSVs for smoothness, linear classifiers, and MMD. - -The shipping registry at `applications/dynaclr/configs/evaluation/eval_registry.yaml` -is checked into git and updated as new (model × dataset) eval runs complete. - -## Key commands - -| Step | Command | Input | Output | -|------|---------|-------|--------| -| Config gen | `dynaclr prepare-eval-configs -c eval.yaml` | eval config | configs/ + manifest JSON | -| Predict | `viscy predict -c predict.yml` | checkpoint + parquet | embeddings/embeddings.zarr | -| Split | `dynaclr split-embeddings --input ... --output-dir ...` | combined zarr | per-experiment zarrs + `configs/viewer.yaml` | -| Dim reduction | `dynaclr reduce-dimensionality -c reduce.yaml` | {experiment}.zarr | zarr with X_pca | -| Combined reduction | `dynaclr combined-dim-reduction -c reduce_combined.yaml` | all {experiment}.zarr | zarrs with X_pca_combined/X_phate_combined | -| Plots (per-exp) | `dynaclr plot-embeddings -c plot.yaml` | {experiment}.zarr | plots/{experiment}/*.pdf | -| Plots (combined) | `dynaclr plot-embeddings -c plot_combined.yaml` | all {experiment}.zarr | plots/combined/*.pdf | -| Smoothness | `dynaclr evaluate-smoothness -c smoothness.yaml` | {experiment}.zarr | per_marker_smoothness.csv, smoothness_stats.csv | -| MMD (per-exp) | `dynaclr compute-mmd -c {block}.yaml` | {experiment}.zarr | mmd/{block}/mmd_results.csv | -| MMD (combined) | `dynaclr compute-mmd --combined -c {block}_cross_exp.yaml` | all {experiment}.zarr | mmd/{block}_cross_exp/combined_mmd_results.csv | -| MMD (pooled) | `dynaclr compute-mmd --pooled -c pooled.yaml` | all {experiment}.zarr | mmd_results.csv | -| Linear probe | `dynaclr run-linear-classifiers -c clf.yaml` | per-experiment zarrs + annotations | metrics_summary.csv, {task}_summary.pdf, pipelines/ | -| Append annotations | `dynaclr append-annotations -c append_annotations.yaml` | per-experiment zarrs + annotation CSVs | zarrs with obs annotation columns | -| Append predictions | `dynaclr append-predictions -c append_predictions.yaml` | per-experiment zarrs + pipelines/ | zarrs with predicted_{task} in obs/obsm/uns | -| Compare models | `python compare_evals.py -c eval_registry.yml` | multiple eval dirs | comparison CSVs + plots | -| CTC tracking | `dynaclr evaluate-tracking-accuracy -c tracking_accuracy.yaml` | ONNX model + CTC datasets | tracking_accuracy/results.csv | - -## Placeholder pattern - -Template YAMLs (`reduce.yaml`, `smoothness.yaml`, `{block}.yaml`, `plot.yaml`) contain `__ZARR_PATH__` -as a placeholder for `input_path`. `plot.yaml` also contains `__PLOT_DIR__`. Nextflow process -scripts substitute these inline with Python one-liners before calling the CLI command: - -```python -import yaml -with open('reduce.yaml') as f: - cfg = yaml.safe_load(f) -cfg['input_path'] = '/path/to/experiment.zarr' -with open('reduce_patched.yaml', 'w') as f: - yaml.dump(cfg, f, default_flow_style=False, sort_keys=False) +```yaml +linear_classifiers: + publish_dir: /path/to/linear_classifiers/ ``` -For `reduce_combined.yaml`, `plot_combined.yaml`, and `{block}_cross_exp.yaml`, Nextflow collects -all zarr paths and writes the `input_paths` list directly. - -## Notes - -- `MultiExperimentDataModule` supports `stage="predict"` since the eval orchestrator was added. - It uses the full cell index (no train/val split), applies only normalizations + z-reduction (no augmentations). -- `BatchedChannelWiseZReductiond` is architecturally required for 2D models even at inference time - (converts 3D z-stack → 2D MIP/center-slice). The orchestrator moves it from `augmentations` - to `normalizations` in the generated predict config. -- Dimensionality reductions (PCA, PHATE) are **not** computed inline during predict. - They run as separate CPU steps after splitting, keeping predict fast. -- The `combined-dim-reduction` step fits reductions on all experiments jointly and writes - `X_pca_combined` / `X_phate_combined` back to each per-experiment zarr. -- PHATE is not computed per-experiment by default (`reduce_dimensionality.phate: null`). Run it only jointly via `reduce_combined`. -- `configs/viewer.yaml` is generated after split and can be passed directly to `dynaclr combined-dim-reduction`. -- MMD reads `.X` (raw backbone embeddings) by default. It can also run on `X_pca` or `X_pca_combined` via `embedding_key`. -- Embeddings obs carries `organelle`, `well`, and `microscope` in addition to `experiment`, `marker`, `perturbation`, `hours_post_perturbation`. - -## MMD config format - -Use `configs/evaluation/recipes/mmd_defaults.yml` as a base to avoid repeating MMD algorithm parameters: +To apply a published bundle without retraining: ```yaml -# Per-experiment (template — __ZARR_PATH__ substituted at runtime) -base: recipes/mmd_defaults.yml -input_path: __ZARR_PATH__ -output_dir: /path/to/evaluation/mmd/perturbation/ -group_by: perturbation -comparisons: - - cond_a: uninfected - cond_b: ZIKV - label: "uninfected vs ZIKV" -embedding_key: null # null = raw .X; or "X_pca", "X_pca_combined" -temporal_bin_size: 4.0 # uniform bin width in hours (null = aggregate) -# temporal_bins: [0, 6, 12, 24] # alternative: explicit bin edges (mutually exclusive) -mmd: - balance_samples: true # subsample larger group to match smaller - share_bandwidth_from: "uninfected vs uninfected" # reuse bandwidth from baseline comparison -map_settings: - enabled: true # compute mAP via copairs alongside MMD - -# Cross-experiment ({block}_cross_exp.yaml — input_paths substituted at runtime) -# No comparisons — conditions auto-discovered from data intersection. -base: recipes/mmd_defaults.yml -input_paths: [__ZARR_PATH__] -output_dir: /path/to/evaluation/mmd/perturbation_cross_exp/ -group_by: perturbation -temporal_bin_size: 4.0 - -# Pooled (standalone CLI only — not generated by orchestrator) -base: recipes/mmd_defaults.yml -input_paths: - - /path/to/exp_A.zarr - - /path/to/exp_B.zarr -output_dir: /path/to/evaluation/mmd/pooled/ -comparisons: - - cond_a: uninfected - cond_b: ZIKV - label: "uninfected vs ZIKV" -condition_aliases: - uninfected: [uninfected, uninfected1, uninfected2] # map variants to canonical name -``` - -## MMD output columns - -### Per-experiment and pooled (`mmd_results.csv`) - -| Column | Description | -|--------|-------------| -| `experiment` | Experiment name (absent in pooled output) | -| `marker` | Organelle marker (e.g., "TOMM20", "G3BP1") | -| `cond_a` | Reference/control condition | -| `cond_b` | Treatment condition | -| `label` | Human-readable comparison label | -| `hours_bin_start` | Start of temporal bin (NaN if no binning) | -| `hours_bin_end` | End of temporal bin (NaN if no binning) | -| `n_a` | Cells from `cond_a` used after subsampling | -| `n_b` | Cells from `cond_b` used after subsampling | -| `mmd2` | Unbiased MMD² estimate | -| `p_value` | Permutation test p-value (Phipson & Smyth smoothed) | -| `q_value` | BH-corrected FDR (pooled mode only) | -| `bandwidth` | Gaussian RBF bandwidth | -| `effect_size` | mmd2 / bandwidth (scale-free) | -| `activity_zscore` | (mmd2 − null_mean) / null_std — normalized against permutation null | -| `map_value` | Mean Average Precision (NaN if map_settings.enabled=false) | -| `map_p_value` | mAP permutation p-value (NaN if map_settings.enabled=false) | -| `embedding_key` | Embedding used ("X" or obsm key) | - -### Cross-experiment (`combined_mmd_results.csv`) - -| Column | Description | -|--------|-------------| -| `marker` | Organelle marker | -| `exp_a` | First experiment in the pair | -| `exp_b` | Second experiment in the pair | -| `condition` | Condition value matched across experiments | -| `hours_bin_start` | Start of temporal bin (NaN if no binning) | -| `hours_bin_end` | End of temporal bin (NaN if no binning) | -| `n_a` | Cells from `exp_a` used | -| `n_b` | Cells from `exp_b` used | -| `mmd2` | Unbiased MMD² estimate | -| `p_value` | Permutation test p-value | -| `bandwidth` | Gaussian RBF bandwidth | -| `effect_size` | mmd2 / bandwidth | -| `activity_zscore` | (mmd2 − null_mean) / null_std | -| `embedding_key` | Embedding used | - -## Linear classifiers output columns - -| Column | Description | -|--------|-------------| -| `task` | Classification task (e.g., `infection_state`) | -| `marker_filter` | Marker used to filter cells (one row per marker per task) | -| `n_samples` | Total annotated cells used | -| `val_accuracy` | Validation accuracy | -| `val_weighted_f1` | Validation weighted F1 | -| `val_auroc` | Validation AUROC (OvR macro for multiclass) | -| `train_*` | Training set counterparts of the above | -| `val_{class}_f1` | Per-class F1 on validation set | +append_predictions: + pipelines_dir: /path/to/linear_classifiers//v2 +``` + +Pin a version directory for reproducible runs. Use `latest` only for active +iteration. A classifier bundle must be applied to the same embedding feature +space in which it was trained. + +## Outputs + +Depending on `steps`, the workflow writes: + +```text +/ +├── configs/ +├── embeddings/.zarr +├── plots/ +├── smoothness/ +├── mmd// +├── mmd/_cross_exp/ +└── linear_classifiers/ + ├── metrics_summary.csv + ├── _summary.pdf + └── pipelines/ + ├── manifest.json + └── _.joblib +``` + +Enrichment steps update each embedding zarr with: + +- annotation and predicted-label columns in `.obs`; +- prediction probabilities in `.obsm`; +- class order and classifier lineage in `.uns`; +- per-experiment and combined reductions in `.obsm`. + +## Direct step commands + +Nextflow normally patches generated configs and calls these commands: + +| Step | Command | +| --- | --- | +| Split combined embeddings | `dynaclr split-embeddings --input --output-dir

` | +| Per-store reduction | `dynaclr reduce-dimensionality -c ` | +| Combined reduction | `dynaclr combined-dim-reduction -c ` | +| Plot embeddings | `dynaclr plot-embeddings -c ` | +| Smoothness | `dynaclr evaluate-smoothness -c ` | +| MMD | `dynaclr compute-mmd -c ` | +| Linear probes | `dynaclr run-linear-classifiers -c ` | +| Append labels | `dynaclr append-annotations -c ` | +| Append predictions | `dynaclr append-predictions -c ` | + +Use the generated configs rather than recreating their placeholder substitution +manually. + +## Validation + +- Confirm every requested experiment appears under `embeddings/`. +- Confirm all requested `steps` produced non-empty outputs. +- Inspect failed or skipped classifier tasks for missing labels or marker + mismatches. +- Check MMD group counts before interpreting scores. +- Rerun the same Nextflow command with `-resume` after correcting a failed step. + +For multi-row submission, see +[evaluation_matrix.md](evaluation_matrix.md). For the workflow implementation, +see [the Nextflow README](../../nextflow/README.md). diff --git a/applications/dynaclr/docs/DAGs/evaluation_matrix.md b/applications/dynaclr/docs/DAGs/evaluation_matrix.md new file mode 100644 index 000000000..2aedae57e --- /dev/null +++ b/applications/dynaclr/docs/DAGs/evaluation_matrix.md @@ -0,0 +1,144 @@ +# Run a prediction and evaluation matrix + +`dynaclr run-matrix` submits prediction and evaluation jobs for multiple +collections, models, or checkpoints. Models must already be trained; the +matrix runner does not submit training jobs. + +```mermaid +flowchart TD + A["Matrix YAML"] --> B["Resolve model identity and checkpoints"] + B --> C["AI-ready preflight"] + C --> D1["row 1: predict"] + C --> D2["row 2: predict"] + C --> DN["row N: predict"] + D1 --> E1["row 1: eval"] + D2 --> E2["row 2: eval"] + DN --> EN["row N: eval"] +``` + +Rows run in parallel. Within each row and checkpoint, evaluation is submitted +with an `afterok` dependency on prediction. + +## Matrix config + +Start from +[`applications/dynaclr/configs/matrix/example.yml`](../../configs/matrix/example.yml). + +```yaml +defaults: + train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/.sh + ckpt_name: epoch105-step84800 + checkpoint: /path/to/epoch=105-step=84800.ckpt + eval_config: applications/dynaclr/configs/evaluation/.yaml + datasets_root: /hpc/projects/intracellular_dashboard/organelle_dynamics + +models: + - collection: applications/dynaclr/configs/collections/.yml + markers: [SEC61B, Phase3D] + - collection: applications/dynaclr/configs/collections/.yml + markers: [G3BP1, Phase3D] +``` + +`train_sbatch` is used only to parse the model `family`, `run`, and training +config identity from its exported variables. It is not submitted. Set +`checkpoint` explicitly when the checkpoint is nested below a logger run +directory. + +To sweep checkpoints in one row: + +```yaml +models: + - train_sbatch: applications/dynaclr/configs/training/DynaCLR-2D/.sh + collection: applications/dynaclr/configs/collections/.yml + ckpt_names: [epoch80-step64000, epoch105-step84800] + checkpoints: + - /path/to/epoch=80-step=64000.ckpt + - /path/to/epoch=105-step=84800.ckpt +``` + +`ckpt_names` and `checkpoints` must have the same length. Each pair creates an +independent `predict → eval` chain. + +Prediction parameters not represented in the matrix schema are controlled by +[`applications/dynaclr/tools/predict.sbatch`](../../tools/predict.sbatch). +Legacy `predict_flags` blocks in existing matrix examples are not currently +consumed by `run-matrix`; do not rely on them. + +## Run + +Always inspect the submission plan first: + +```sh +uv run dynaclr run-matrix \ + -c applications/dynaclr/configs/matrix/.yml \ + --dry-run +``` + +Submit prediction and evaluation: + +```sh +uv run dynaclr run-matrix \ + -c applications/dynaclr/configs/matrix/.yml \ + --stages predict,eval +``` + +Run only one stage when needed: + +```sh +uv run dynaclr run-matrix -c .yml --stages predict +uv run dynaclr run-matrix -c .yml --stages eval +``` + +On real prediction submissions, the runner verifies that every collection has +focus and normalization metadata before queueing jobs. `--skip-preflight` +bypasses this check and should only be used when readiness was verified +separately. + +## Adding datasets to one model + +Rows with the same model, run, and checkpoint write into the same embedding +tree. Their evaluation jobs do not wait for prediction jobs from sibling rows. +For this case, predict the full matrix first: + +```sh +uv run dynaclr run-matrix \ + -c applications/dynaclr/configs/matrix/.yml \ + --stages predict +``` + +After all prediction jobs finish, launch one cohort evaluation: + +```sh +uv run dynaclr eval \ + --eval-config applications/dynaclr/configs/evaluation/.yaml \ + --model-family \ + --run \ + --ckpt-name +``` + +This avoids multiple evaluations writing to the same `output_dir`. + +## Outputs + +Prediction uses the standard provenance tree: + +```text +//2-phenotyping/predictions/ + ///.zarr +``` + +Evaluation writes to the `output_dir` declared by each evaluation config. See +[evaluation.md](evaluation.md) for the output layout. + +## Validation + +- Inspect `--dry-run` output for the resolved collection, checkpoint, model + family, run, marker list, and evaluation config. +- Confirm every prediction job completed before launching a gathered cohort + evaluation. +- Give concurrent evaluation rows distinct `output_dir` values. +- Use pinned checkpoints and classifier bundle versions for reproducible + matrices. + +The maintained multi-dataset example is +[`organelle_remodeling.yml`](../../configs/matrix/organelle_remodeling.yml). diff --git a/applications/dynaclr/docs/DAGs/inference_triplet.md b/applications/dynaclr/docs/DAGs/inference_triplet.md index a394ed354..472cd8cc4 100644 --- a/applications/dynaclr/docs/DAGs/inference_triplet.md +++ b/applications/dynaclr/docs/DAGs/inference_triplet.md @@ -1,246 +1,125 @@ -# Inference DAG (Triplet path) +# Predict per-marker embeddings -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 +`dynaclr predict-triplet` runs a trained checkpoint directly against image and +tracking zarrs defined by a collection. It writes one AnnData zarr per +experiment and marker. ```mermaid -flowchart TD - IN["dataset.zarr (normalization in FOV zattrs)
tracking.zarr/CSV (track_id, t, y, x)
checkpoint.ckpt (trained ContrastiveModule)"] - IN -->|"predict config
(TripletDataModule + ContrastiveModule + EmbeddingWriter)"| PRED["viscy predict --config configs/prediction/predict_triplet.yml"] - PRED --> PREDNOTE["TripletDataModule(fit=False): ONE anchor patch per (cell, timepoint)
• extract z_range window, yx at initial_yx_patch_size
• reference_pixel_size → larger patch, BatchedZoomd to final_yx
• z_reduction → BatchedChannelWiseZReductiond collapses Z to 1 (2D)
ContrastiveModule.predict_step → backbone features (+ projections)
EmbeddingWriter accumulates (features, index) → one combined store"] - PREDNOTE --> EMB["embeddings.zarr
(AnnData: .X = embedding_key array, mirrored to
obsm[X_backbone]/[X_projections]; obs = fov_name/track_id/t/…)"] - EMB -->|"dynaclr split-embeddings --input embeddings.zarr --output-dir embeddings/
groups rows by obs[experiment], one zarr per experiment,
removes the combined store afterwards"| SPLIT["embeddings/{experiment}.zarr
(one per experiment, informatively named)"] - SPLIT --> DOWN["downstream eval
reduce-dimensionality · linear classifiers · MMD · pseudotime …
see evaluation.md / pseudotime.md"] +flowchart LR + A["Collection YAML"] --> D["predict-triplet"] + B["Checkpoint"] --> D + C["Image + tracking zarrs"] --> D + D --> E["Per-marker embedding zarrs"] + E --> F["Evaluation and analysis"] ``` -## Pipeline DAG (process dependency) - -```mermaid -flowchart TD - C["predict config + checkpoint + zarr + tracking"] - C --> P["viscy predict
(GPU, minutes–hours by cell count)"] - P --> S["split-embeddings
(CPU, ~1 min, I/O bound)"] - S --> D["downstream eval
(CPU/GPU, per analysis)"] -``` +Use this workflow for collection-driven inference. The parquet-first prediction +path used by the full evaluation workflow is documented in +[evaluation.md](evaluation.md). -## Key commands +## Inputs -| 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/ [--group-by experiment]` | combined zarr with the `--group-by` column in `obs` | one `{group}.zarr` per group value | +- An AI-ready image zarr with normalization metadata. +- A tracking zarr with `track_id`, `t`, `y`, and `x`. +- A checkpoint compatible with the configured model family. +- A collection YAML under + [`applications/dynaclr/configs/collections/`](../../configs/collections/). -## 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` → `.X` (+ `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: +For each experiment, the collection must provide the data paths and channel to +marker mapping. Optional `wells` restricts a channel entry to part of a plate. ```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: +name: +experiments: + - name: 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 + channels: + - name: Phase3D + marker: Phase3D + - name: raw GFP EX488 EM525-45 + marker: SEC61B + wells: [A/2, B/2] + perturbation_wells: + control: [A/1, B/1] + perturbed: [A/2, B/2] + interval_minutes: 30.0 + pixel_size_xy_um: 0.1494 ``` -## 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` groups by any `obs` column via `--group-by`** (default - `experiment`) and can prefix filenames via `--prefix-by` (e.g. - `--group-by marker --prefix-by experiment` → `{experiment}_{marker}.zarr`). The - requested columns must exist on the combined store. For a single-experiment - predict run the default split is optional — the combined `embeddings.zarr` is - already per-experiment. -- **The triplet `EmbeddingWriter` writes only ultrack index columns** to `obs` - (`fov_name, track_id, t, id, parent_track_id, parent_id, z, y, x`). It does - **not** write `experiment` or `marker` — those come from the parquet path - (`MultiExperimentDataModule`), which carries collection metadata. Therefore - `split-embeddings --group-by marker` cannot split triplet output; see below for - the per-marker recipe. -- 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). - -## Per-marker embeddings (bag-of-channels models) — `dynaclr predict-triplet` - -Bag-of-channels models (e.g. `DynaCLR-2D-MIP-BagOfChannels`) are trained with -`in_channels: 1` — each marker is embedded as its own single-channel sample. On -the triplet path there is no combined store to split by marker (the writer omits -`marker`, see Notes), so the per-marker split is expressed by **running predict -once per marker**, each with a single `source_channel`. - -This is a **single command** — no hand-written per-dataset config generator: +## Run ```sh -dynaclr predict-triplet \ - -c collection.yml \ - --checkpoint /path/to/epoch=105-step=84800.ckpt \ - --model-family DynaCLR-2D-MIP-BagOfChannels \ - --run 2d-mip-...-fix-shuffler \ - --ckpt-name epoch105-step84800 \ - --datasets-root /hpc/projects/intracellular_dashboard/organelle_dynamics \ - --z-range 15 45 --z-reduction mip --reference-pixel-size 0.1494 \ - [--markers SEC61B,TOMM20] [--no-labelfree] +uv run dynaclr predict-triplet \ + -c applications/dynaclr/configs/collections/.yml \ + --checkpoint /path/to/checkpoint.ckpt \ + --model-family \ + --run \ + --ckpt-name \ + --datasets-root /hpc/projects/intracellular_dashboard/organelle_dynamics \ + --z-range 15 45 \ + --z-reduction mip \ + --reference-pixel-size 0.1494 \ + --yx-patch-size 160 160 \ + --batch-size 32 \ + --num-workers 0 ``` -The **collection YAML is the single source of truth**. Each `ChannelEntry` -carries a zarr `name`, a `marker` label, and optional `wells` (empty = all -wells). The command runs predict once per channel entry, restricting to that -channel's `wells` via `fit_include_wells`, and writes one zarr per marker. When a -reporter varies by plate column (multi-organelle "box" plates), list the same -zarr channel once per organelle with its own `wells`: - -```yaml -experiments: - - name: 2026_07_01_A549_SEC61B_TOMM20_G3BP1_ZIKV - channels: - - {name: raw GFP EX488 EM525-45, marker: SEC61B, wells: [A/2, B/2]} - - {name: raw GFP EX488 EM525-45, marker: TOMM20, wells: [A/3, B/3]} - - {name: raw GFP EX488 EM525-45, marker: G3BP1, wells: [A/4, B/4]} - - {name: raw mCherry EX561 EM600-37, marker: pAL17} # all wells +Important options: + +| Option | Purpose | +| --- | --- | +| `--markers SEC61B,TOMM20` | Predict only the listed markers. | +| `--no-labelfree` | Skip phase and brightfield channels. | +| `--z-reduction mip|center` | Collapse the selected z window for a 2D model. | +| `--reference-pixel-size` | Rescale crops to the model's training pixel size. | +| `--no-enrich-obs` | Do not append collection metadata to output `obs`. | + +Keep `--num-workers 0`; multiprocessing can deadlock while reading zarr during +prediction. Prediction is deterministic and does not apply training +augmentations. + +## Outputs + +```text +//2-phenotyping/predictions/ +└── / + └── / + └── / + ├── Phase3D.zarr + ├── SEC61B.zarr + └── .zarr ``` -**Dataset- and provenance-scoped output tree** — so every zarr traces back to -what produced it and two models/checkpoints can coexist for comparison: +Each output is an AnnData store: +- `.X`: selected embedding representation; +- `.obs`: cell identifiers, tracking fields, and collection metadata; +- `.uns`: model, run, checkpoint, collection, marker, and channel provenance. + +The output path is derived from the collection and provenance flags. Use the +same `model-family`, `run`, and `checkpoint-name` in downstream launchers. + +## Batch prediction + +For multiple collections or checkpoints, use the matrix runner instead of +writing per-dataset shell loops: + +```sh +uv run dynaclr run-matrix \ + -c applications/dynaclr/configs/matrix/.yml \ + --stages predict \ + --dry-run ``` -{datasets_root}/{dataset}/2-phenotyping/predictions/{model_family}/{run}/{ckpt_name}/{marker}.zarr -``` -The dataset is derived from each experiment's `data_path`; the directory tree is -the artifact index, so downstream consumers pool matching stores with a glob and -no registry file is required. - -`--markers` selects a subset; `--no-labelfree` skips phase/brightfield channels -(resolved by name via `parse_channel_name`). - -> **Legacy (retired):** older datasets used a per-dataset -> `generate_predict_configs.py` + `predict_triplet_per_marker.sh` copied into -> `2-phenotyping/predictions/configs/` (e.g. the DENV worked example). That -> pattern fragmented — everyone copied and forked it. Prefer `predict-triplet`; -> the generator is kept only for already-run datasets. - -### Provenance & Nextflow - -`predict-triplet` writes the model/run/checkpoint-scoped tree directly. The -parquet path can route its combined store into the same tree with -`split-embeddings --route-by-dataset --model-family M --run R --ckpt-name C`. -Evaluation is decoupled from prediction: `dynaclr eval` builds the cohort glob -and launches the Nextflow `eval_from_embeddings` entry over the frozen stores. -For batch prediction or a full train→predict→eval sweep, use `dynaclr -predict-batch` or `dynaclr run-matrix`. - -For the **parquet path**, one predict run already tags every row with `marker` -(bag-of-channels explodes each cell into one row per channel), so per-marker -splitting is instead a single -`dynaclr split-embeddings --group-by marker --prefix-by experiment`, which writes -`{experiment}_{marker}.zarr` for the same convention. - -## Triplet vs parquet (MultiExperimentDataModule) - -| Aspect | Triplet path (this doc) | Parquet path (evaluation.md) | -| ------------------- | ------------------------------------------------ | -------------------------------------------------- | -| 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 | explicit `z_range` or per-FOV `z_extraction_window` from `focus_slice`; `z_reduction` collapses | per-FOV `z_extraction_window` from `focus_slice` | -| 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 | +Inspect the dry run, then rerun without `--dry-run`. See +[evaluation_matrix.md](evaluation_matrix.md). + +## Validation + +- Confirm one zarr was written for every requested experiment-marker pair. +- Run `uv run dynaclr info .zarr` and verify row count, feature count, + `obs` metadata, and provenance. +- Treat embeddings from different checkpoints as different feature spaces. + +Continue with [evaluation.md](evaluation.md) to evaluate the frozen outputs. diff --git a/applications/dynaclr/docs/DAGs/lot_correction.md b/applications/dynaclr/docs/DAGs/lot_correction.md new file mode 100644 index 000000000..2098ba3f2 --- /dev/null +++ b/applications/dynaclr/docs/DAGs/lot_correction.md @@ -0,0 +1,133 @@ +# Correct embedding batches with LOT + +Fit one Linear Optimal Transport (LOT) map from pooled source and target +reference cells, then apply the same fitted map to every source-domain +embedding store. + +```mermaid +flowchart TD + S["Source reference zarrs"] --> F["fit-lot-correction"] + T["Target reference zarrs"] --> F + F --> P["lot_pipeline.pkl"] + P --> A1["apply to dataset A"] + P --> A2["apply to dataset B"] + P --> AN["apply to dataset N"] + A1 --> V["MMD pre/post validation"] + A2 --> V + AN --> V +``` + +Fit once per embedding feature space and anchor channel. Application jobs are +independent and may run in parallel. + +## Inputs + +- Source- and target-domain AnnData zarrs with embeddings in `.X`. +- A reference-population filter that is available in `.obs` for both domains. +- Matching feature dimensions and model/checkpoint provenance. + +The source and target sides may contain different numbers of cells. `ns_lot` +is a compute cap for each pooled side, not a requirement to balance counts. + +## Fit config + +```yaml +source: + - zarr: /path/to/source_rep1.zarr + filter: + column: fov_name + startswith: ["C/1/"] + - zarr: /path/to/source_rep2.zarr + filter: + column: fov_name + startswith: ["C/1/"] + +target: + - zarr: /path/to/target.zarr + filter: + column: perturbation + equals: control + +channel: Phase3D +n_pca: 50 +ns_lot: 3000 +random_seed: 42 +output_pipeline: /path/to/lot_pipeline.pkl +``` + +Each side is a list of zarr/filter entries. Entries on the same side are pooled. +Omit `filter` to use every cell in a store. Set `n_pca: null` to fit LOT in the +scaled input space and `ns_lot: null` to use all reference cells. + +## Fit + +```sh +uv run dynaclr fit-lot-correction -c fit_lot.yaml +``` + +The fitted artifact contains the scaler, optional PCA, LOT transform, channel, +sampling settings, and explained-variance metadata. + +## Apply + +Apply the same pipeline to each complete dataset, not only the reference subset: + +```sh +uv run dynaclr apply-lot-correction \ + --pipeline /path/to/lot_pipeline.pkl \ + --input /path/to/source_rep1.zarr \ + --output /path/to/corrected/source_rep1.zarr +``` + +Use `--overwrite` only when the output store may be replaced. Repeat the command +for every input that must share the corrected coordinate system. + +The corrected zarr contains: + +- corrected embeddings in `.X`; +- copied `.obs` and `.uns` metadata; +- correction provenance in `.uns["lot_correction"]`. + +Existing `.obsm`, `.varm`, `.obsp`, layers, and dimensionality reductions are +dropped because they belong to the uncorrected feature space. Recompute them +from corrected `.X`. + +## Validate pre/post MMD + +Create a paired over-time MMD config: + +```yaml +output_dir: /path/to/mmd_over_time +input_paths: + - /path/to/source_rep1.zarr + - /path/to/source_rep2.zarr +corrected_paths: + - /path/to/corrected/source_rep1.zarr + - /path/to/corrected/source_rep2.zarr +group_by: perturbation +temporal_bin_size: 6.0 +``` + +Run: + +```sh +uv run dynaclr compute-mmd --over-time -c mmd_over_time.yaml +``` + +Inputs and corrected outputs are paired through `obs["experiment"]`, not list +position. The command writes `over_time_mmd_results.csv` and per-marker pre/post +kinetics plots. + +## Validation rules + +- Use reference filters that select the same type of population on both sides. +- Apply one fitted pipeline to all source datasets that must remain comparable; + do not refit per dataset. +- Fit one map per anchor channel and record that channel in the config. +- Do not use a map across different model/checkpoint feature spaces. +- Confirm at least five pooled reference cells exist on each side. +- Confirm post-correction cross-domain MMD decreases for every channel to which + the map will be applied. + +Continue downstream with [evaluation.md](evaluation.md). Re-run dimensionality +reduction after correction. diff --git a/applications/dynaclr/docs/DAGs/pseudotime.md b/applications/dynaclr/docs/DAGs/pseudotime.md index 298437487..cee1a88bc 100644 --- a/applications/dynaclr/docs/DAGs/pseudotime.md +++ b/applications/dynaclr/docs/DAGs/pseudotime.md @@ -1,731 +1,236 @@ -# Pseudotime DAG +# Run the pseudotime workflow -This document describes how the pseudotime pipeline runs. For why it -runs this way — methodology decisions, claims, falsification protocol — -see the source-of-truth discussion document at -`/home/eduardo.hirata/repos/DynaCLR/.planning/dynaclr_dtw_discussion.md`. +The pseudotime scripts select candidate tracks, build an optional template, +produce three alignment variants, compute per-channel readouts, and compare the +results. -## 1. Goals - -We measure when SEC61 (ER), G3BP1 (stress granules), and quantitative -phase morphology change relative to per-cell NS3 sensor translocation -in single A549 cells, then compare three alignment tracks to see which -sharpens the population timing readouts. - -The pipeline produces three parallel sets of organelle-remodeling -plots, one per track, indexed on the same lineage-reconnected cohort -so the side-by-side comparison is direct. +```mermaid +flowchart TD + A["Dataset and candidate configs"] --> B["0. Select candidate cohorts"] + B --> C["1. Build template"] + B --> D1["2A. Annotation alignment"] + B --> D2["2B. Classifier alignment"] + C --> D3["2C. Embedding DTW alignment"] + D1 --> E["3. Per-channel readouts"] + D2 --> E + D3 --> E + E --> F["4. Compare alignment tracks"] +``` -| Track | Anchor | Alignment | Outputs in | -|---|---|---|---| -| **A-anno** | Human `infection_state` first-positive frame | Per-cell shift, real-time | `3-organelle-remodeling/A-anno/` | -| **A-LC** | Linear classifier `predicted_infection_state` first-positive frame | Per-cell shift, real-time | `3-organelle-remodeling/A-LC/` | -| **B** | NS3 embedding band on transition window | Hybrid DTW: warp transition only, propagated to organelle and phase | `3-organelle-remodeling/B/` | +All commands below run from the repository root. The scripts write into their +stage directories under `applications/dynaclr/scripts/pseudotime/`. -Path B is the methodological contribution. Paths A-anno and A-LC are -baselines. +## Config files -### What `t_rel = 0` means +| Config | Used by | Required content | +| --- | --- | --- | +| [`datasets.yaml`](../../configs/pseudotime/datasets.yaml) | every stage | dataset paths, frame intervals, embedding patterns | +| [`candidates.yaml`](../../configs/pseudotime/candidates.yaml) | candidate selection, A-anno, A-LC, readouts | candidate sets and cohort rules | +| [`build_template.yaml`](../../configs/pseudotime/build_template.yaml) | template build and self-check | template names, input candidate set, channel, preprocessing | +| [`align_cells.yaml`](../../configs/pseudotime/align_cells.yaml) | DTW alignment and scoring | query sets, channel, dataset filters, time requirements | +| [`compare_tracks.yaml`](../../configs/pseudotime/compare_tracks.yaml) | final comparisons | candidate set, tracks, channels, output aggregation | -`t_rel = 0` is when the NS3 protease sensor crosses our chosen anchor: -either the human `infection_state` first-positive frame (Path A-anno), -the LC threshold-crossing (Path A-LC), or the NS3 embedding's -half-rise band on the transition window (Path B). All three are -landmarks downstream of viral entry, polyprotein translation, and ER -remodeling — `t_rel = 0` is a fiducial clock, not the start of -infection. See discussion §1.1 and §2.1 for biological framing. +Keep dataset IDs, candidate-set names, template names, and query-set names +consistent across the configs. -## 2. Pipeline stages +## 0. Select candidate cohorts -``` -0-select_candidates → 1-build_template → 2-align_cells → 3-organelle-remodeling → 4-compare_tracks - (lineage-reconnect, (Path B only — (Path A-anno, (per-track per-organelle (side-by-side - cohort tagging, NS3 transition A-LC, B readouts: SEC61 comparison, - manual + auto) template) alignments) cosine-distance, warp-vs-no-warp, - G3BP1 oscillation, bimodality test, - phase distance) robustness) +```sh +uv run python applications/dynaclr/scripts/pseudotime/0-select_candidates/select_candidates.py \ + --datasets applications/dynaclr/configs/pseudotime/datasets.yaml \ + --config applications/dynaclr/configs/pseudotime/candidates.yaml \ + --candidate-set ``` -Stages 0 and 1 share data across tracks. Stages 2, 3, and 4 fork -per-track, then re-converge in Stage 4 for comparison. - -## 3. Directory layout +Outputs: -``` -applications/dynaclr/ -├── configs/pseudotime/ -│ ├── datasets.yaml # datasets + embedding patterns -│ ├── candidates.yaml # candidate sets, cohort tags, lineage rules -│ ├── build_template.yaml # Stage 1 (Path B template build) -│ ├── align_cells.yaml # Stage 2 (per-track query sets) -│ └── compare_tracks.yaml # Stage 4 (cross-track headlines) -├── docs/DAGs/pseudotime.md # this file -├── src/dynaclr/pseudotime/ # library -│ ├── dtw_alignment.py # template fit + warp solver -│ ├── io.py # template-zarr layout + provenance -│ ├── alignment.py # lineage reconnection, daughter handling -│ ├── signals.py # extract annotation / LC / embedding signals -│ ├── metrics.py # onset, t_50, peak, oscillation stats -│ └── plotting.py # response curves, heatmaps, comparisons -└── scripts/pseudotime/ - ├── 0-select_candidates/ - │ ├── select_candidates.py # auto path (from annotations) - │ ├── manual_candidates.py # manual path (hand-picked tracks) - │ ├── reconnect_lineages.py # mother+daughter stitching, divides flag - │ ├── tag_cohorts.py # productive / bystander / abortive / mock - │ ├── inspect_candidates.py # per-track image montage QC - │ └── candidates/ # output: lineage-aware annotation CSVs - ├── 1-build_template/ # Path B only - │ ├── build_template.py # NS3 transition template via DBA - │ ├── evaluate_template.py # self-align sanity check - │ ├── templates/ # output: template_*.zarr - │ └── plots/ # output: build-set diagnostics - ├── 2-align_cells/ - │ ├── align_anno.py # Path A-anno: real-time shift on infection_state - │ ├── align_lc.py # Path A-LC: real-time shift on LC predictions - │ ├── align_embedding.py # Path B: hybrid DTW + warp propagation - │ ├── A-anno/alignments/ # per-track output parquets - │ ├── A-LC/alignments/ - │ └── B/alignments/ - ├── 3-organelle-remodeling/ - │ ├── readout_sec61.py # cosine-distance-from-baseline - │ ├── readout_g3bp1.py # oscillation excursion stats - │ ├── readout_phase.py # phase embedding distance - │ ├── A-anno/ # per-track per-organelle outputs - │ ├── A-LC/ - │ └── B/ - └── 4-compare_tracks/ - ├── compare_onsets.py # side-by-side SEC61, G3BP1, phase headlines - ├── compare_phase_to_fluor.py # claim (a') Spearman ρ - ├── warp_vs_no_warp.py # mandatory comparator (Path B only) - ├── bimodality_check.py # dip-test on every back-projected distribution - └── headline_figure.py # the figure-1 of the paper +```text +applications/dynaclr/scripts/pseudotime/0-select_candidates/candidates/ +├── _productive.csv +├── _bystander.csv +├── _abortive.csv +├── _unannotated_productive.csv +├── _mock.csv +└── _funnel.md ``` -Status of stage scripts as of this revision: - -- **Implemented and current:** `0-select_candidates/select_candidates.py`, - `manual_candidates.py`, `inspect_candidates.py`, - `1-build_template/build_template.py`, `evaluate_template.py`, - `2-align_cells/align_cells.py` (current name covers what becomes - `align_embedding.py` after split), per-stage plotting scripts. -- **Implemented but in worktree, not in DAG structure:** - `annotation_remodeling.py` and `prediction_remodeling.py` cover - Path A-anno and Path A-LC respectively; live in - `.claude/worktrees/cytoland-virtual-staining-examples/applications/dynaclr/scripts/pseudotime/`. - Need refresh against new directory structure and configs. -- **TODO: not yet implemented:** `reconnect_lineages.py`, - `tag_cohorts.py`, the per-track split in Stage 2, per-organelle - readouts as separate scripts, all of Stage 4. - -The current scripts still produce useful output but operate on a -single track (Path A-anno-equivalent) without lineage reconnection, -cohort tagging, or cross-track comparison. The phases below describe -the migration. - -## 4. Cohorts - -Stage 0 emits one annotation CSV per cohort. All four cohorts share the -schema. Each cell carries a `cohort` column. - -| Cohort | Definition | Used by | -|---|---|---| -| `productive` | Lineage with NS3 rise within imaging window; manual `[t_before, t_after]` and `t_key_event` from `manual_candidates.py` | Primary cohort; all three tracks | -| `bystander` | Lineage in infected wells with no NS3 rise across imaging duration | Negative control for claims (a), (a'), (b) | -| `abortive` | Lineage with NS3 channel embedding present, no rise | Claim (b) bifurcation comparison (caveat: censored-data category, see discussion §3.2) | -| `mock` | Lineage from uninfected wells | Per-frame null distribution for organelle distance comparisons | - -Mock cells do not get a synthetic `t_zero`. Every mock cell × frame -contributes to an FOV-stratified per-frame null distribution. - -## 5. Stage 0 — Select candidates and reconnect lineages - -Stage 0 emits per-cohort annotation CSVs. Two entry points feed it: -`select_candidates.py` (auto, from existing annotations) and -`manual_candidates.py` (hand-picked tracks). - -The single output artifact is `{cohort}_annotations.csv`, one row per -`(dataset_id, fov_name, lineage_id, t)` over the per-cell crop window. - -### 5.1 Auto path - -```bash -cd applications/dynaclr/scripts/pseudotime/0-select_candidates -uv run python select_candidates.py \ - --datasets ../../../configs/pseudotime/datasets.yaml \ - --config ../../../configs/pseudotime/candidates.yaml \ - --candidate-set infection_transitioning_nondiv -``` +Review the funnel report and cohort row counts before continuing. -Filters tracks per `config["candidate_sets"][NAME]["filter"]` (anchor -label, anchor_positive, min_pre/post_minutes, crop_window_minutes), -expands each track into per-frame rows, copies real annotation labels -onto each row, then runs lineage reconnection (§5.3) and cohort tagging -(§5.4) before writing. +Template construction uses a separate artifact contract: +`_annotations.csv`. The current +`select_candidates.py` command writes cohort CSVs, not this template annotation +file. Use an existing reviewed annotation file in the candidates directory or a +manual candidate generator before Stage 1. -### 5.2 Manual path +## 1. Build and check a template -```bash -cd applications/dynaclr/scripts/pseudotime/0-select_candidates -python manual_candidates.py +```sh +uv run python applications/dynaclr/scripts/pseudotime/1-build_template/build_template.py \ + --datasets applications/dynaclr/configs/pseudotime/datasets.yaml \ + --config applications/dynaclr/configs/pseudotime/build_template.yaml \ + --template ``` -Each track spec is `{t_before, t_after, labels: {...}}` in a Python -dict. Frames in `[t_before, t_after]` get the positive label if they -fall inside an interval. The CSV schema is the only contract with -downstream stages. - -### 5.3 Lineage reconnection - -**TODO: implement.** Stitches mother + daughter chains into single -lineages using `parent_track_id`. Daughter handling regime-dependent: - -- Division before `t_zero`: keep both daughters as paired observations; - siblings are biologically equivalent at infection. -- Division after `t_zero`: keep the daughter with more pre-`t_zero` - footage. Daughters at this stage carry different viral loads. -- Division during the transition window: tag as separate cohort outside - DTW alignment; mitotic ER fragmentation distorts templates. - -Each lineage record carries `divides ∈ {none, pre, during, post}` and -`lineage_id` (replaces `track_id` as the canonical unit downstream). - -### 5.4 Cohort tagging - -**TODO: implement.** For each lineage, derive `cohort` from the -NS3 channel signal: - -- `productive`: lineage has manual `t_key_event` and survives window - cropping in Stage 1. -- `bystander`: in infected well, no NS3 rise across imaging window. -- `abortive`: NS3 channel embedding present, no rise. -- `mock`: from uninfected wells. +The selected template entry identifies its input candidate set and channel. +The script reads: -### 5.5 Inspect - -```bash -cd applications/dynaclr/scripts/pseudotime/0-select_candidates -uv run python inspect_candidates.py \ - --datasets ../../../configs/pseudotime/datasets.yaml \ - --config ../../../configs/pseudotime/candidates.yaml \ - --candidate-set infection_transitioning_nondiv +```text +applications/dynaclr/scripts/pseudotime/0-select_candidates/candidates/ + _annotations.csv ``` -Renders a per-cell-anchored image montage with `t_key_event` marked. -Also writes a sidecar `_qc.csv` with per-track stats (n_frames, -pre_frames, post_frames, fov, divides) for non-visual QC. - -## 6. Stage 1 — Build NS3 transition template (Path B only) - -Stage 1 produces the canonical NS3 transition template against which -Path B aligns query cells. Paths A-anno and A-LC do not use a -template. - -### 6.1 Template build +and writes: -```bash -cd applications/dynaclr/scripts/pseudotime/1-build_template -uv run python build_template.py \ - --config ../../../configs/pseudotime/build_template.yaml \ - --template infection_nondividing_zikv +```text +applications/dynaclr/scripts/pseudotime/1-build_template/templates/ + template_.zarr ``` -The builder: - -1. Reads `productive` cohort annotations. -2. Crops each lineage to `[t_zero - h_pre, t_zero + h_post]` real-time. - Default: `h_pre = 240 min`, `h_post = 360 min` (`540 min` for G3BP1 - readout downstream). See discussion §3.6. -3. Pulls NS3 channel embeddings within the transition sub-window - `[t_zero - k_pre, t_zero + k_post]`. Default: `k_pre = 60 min`, - `k_post = 120 min`. Use the 10 min/frame cohort if available - (target frame count ≥ 12 for DBA stability). -4. Computes per-cell pre-baseline = mean NS3 embedding across pre-window - frames. Cosine distance against this per-cell baseline. -5. Runs DBA on the transition sub-window only. -6. Saves the template zarr. - -### 6.2 Template zarr contents - -| Path | Type | Description | -|---|---|---| -| `template` | (T, D) array | DBA template in the transition window | -| `time_calibration` | (T,) array | mean `t_relative_minutes` per template position | -| `template_labels/{col}` | (T,) array | per-position label fractions | -| `tau_event_band` | (2,) array | `[τ_lo, τ_hi]`: half-rise band of `||dT/dτ||`. The event identifier per discussion §3.4. | -| `lineage_ids` | list (attrs) | `[dataset_id, fov_name, lineage_id]` per build-set lineage | -| `aggregator` | str (attrs) | `"dba"` | -| `template_duration_minutes` | float (attrs) | `time_calibration[-1] - time_calibration[0]` | -| `build_frame_intervals_minutes` | dict (attrs) | per-dataset frame interval at build time | -| `viscy_git_sha`, `dtaidistance_version`, `scikit_learn_version`, `numpy_version` | str (attrs) | provenance | - -**TODO:** add `tau_event_band` to the zarr writer. Currently the -template stores derivative-argmax as a point. - -### 6.3 Self-consistency check - -```bash -uv run python evaluate_template.py \ - --config ../../../configs/pseudotime/build_template.yaml \ - --template infection_nondividing_zikv -``` - -Re-aligns the build-set lineages onto the template they built. Sanity -check, not generalization evidence. - -## 7. Stage 2 — Align query cells per track +Run the self-alignment check: -Stage 2 forks per track. Each track produces an alignment parquet with -the same schema (described in §7.4) so Stage 3 readouts and Stage 4 -comparisons read uniformly. - -### 7.1 Path A-anno: annotation-anchored real-time shift - -**TODO: implement** as `align_anno.py`. Replaces `annotation_remodeling.py`'s -alignment step (currently in -`.claude/worktrees/.../annotation_remodeling.py`). - -Per-cell `t_zero` = first frame where `infection_state == "infected"` -in the manual or auto annotation CSV. Real-time per-cell shift: -`t_rel = (t - t_zero) * frame_interval_minutes`. No DTW, no template, -no warping. - -```bash -# TODO: command shape -uv run python align_anno.py \ - --datasets ../../../configs/pseudotime/datasets.yaml \ - --config ../../../configs/pseudotime/align_cells.yaml \ - --query-set zikv_07_24 +```sh +uv run python applications/dynaclr/scripts/pseudotime/1-build_template/evaluate_template.py \ + --datasets applications/dynaclr/configs/pseudotime/datasets.yaml \ + --config applications/dynaclr/configs/pseudotime/build_template.yaml \ + --template \ + --flavor raw ``` -### 7.2 Path A-LC: LC-anchored real-time shift +Use `--flavor pca` to check the stored PCA flavor. -**TODO: implement** as `align_lc.py`. Replaces `prediction_remodeling.py`. +## 2. Generate alignments -Per-cell `t_zero` = first frame of the longest run of `predicted_infection_state == "infected"` in the NS3 channel embedding zarr. `min_run` parameter (default 3) prevents single-frame flickers from defining `t_zero`. +Annotation-anchored alignment: -```bash -# TODO: command shape -uv run python align_lc.py \ - --datasets ../../../configs/pseudotime/datasets.yaml \ - --config ../../../configs/pseudotime/align_cells.yaml \ - --query-set zikv_07_24 --min-run 3 +```sh +uv run python applications/dynaclr/scripts/pseudotime/2-align_cells/align_anno.py \ + --datasets applications/dynaclr/configs/pseudotime/datasets.yaml \ + --config applications/dynaclr/configs/pseudotime/candidates.yaml \ + --candidate-set \ + --anchor-label infection_state \ + --anchor-positive infected ``` -### 7.3 Path B: hybrid DTW + warp propagation - -Existing `align_cells.py` covers most of this. Renaming and feature gaps -listed below. - -```bash -cd applications/dynaclr/scripts/pseudotime/2-align_cells -uv run python align_embedding.py \ - --datasets ../../../configs/pseudotime/datasets.yaml \ - --config ../../../configs/pseudotime/align_cells.yaml \ - --template infection_nondividing_zikv \ - --query-set zikv_07_24 \ - --min-match-minutes 360 --max-skew 0.7 -``` +Classifier-anchored alignment: -The aligner: - -1. Loads `templates/template_{name}.zarr` and the cohort-tagged query - annotations. -2. Pulls NS3 channel embeddings, applies build-time L2 normalization - (no refit at alignment time). -3. For each query lineage, runs subsequence DTW on the transition - sub-window. The template (length T) must match fully; the query - floats. Returns a warp path, best-match window `[q_start, q_end]`, - cost, and `path_skew`. -4. **TODO:** propagate the warp path to organelle and phase channel - embeddings within the transition sub-window. Pre and post stay in - real-time. -5. **TODO:** for each cell, back-project the τ_event band to a - per-cell real-time interval. Report median + IQR per cohort. -6. Applies guards (§7.6) and writes alignment parquet. -7. Writes a sidecar `{template}_on_{qset}.drop_log.json` with - per-filter drop counts. - -### 7.4 Alignment parquet schema (all three tracks) - -One row per `(dataset_id, fov_name, lineage_id, t)`. Per-lineage columns -are repeated on every frame so downstream scripts can filter rows -without a separate join. - -| Column | Type | Per-frame? | Tracks | Notes | -|---|---|---|---|---| -| `dataset_id`, `fov_name`, `lineage_id`, `t` | ids | yes | all | identifiers (`lineage_id` replaces `track_id`) | -| `cohort` | str | yes | all | `productive` / `bystander` / `abortive` / `mock` | -| `divides` | str | yes | all | `none` / `pre` / `during` / `post` | -| `t_zero` | int | per-lineage | all | per-cell anchor frame | -| `t_rel_minutes` | float | yes | all | real-time minutes from `t_zero` | -| `track_path` | str | per-lineage | all | `A-anno` / `A-LC` / `B` | -| `pseudotime` | float ∈ [0, 1] | yes | B only | warp-path template position | -| `alignment_region` | str | yes | B only | `pre` / `aligned` / `post` | -| `t_rel_minutes_warped` | float | yes | B only | back-projected real-time at template position; equals `t_rel_minutes` outside `aligned` | -| `dtw_cost` | float | per-lineage | B only | raw DTW cost | -| `length_normalized_cost` | float | per-lineage | B only | `dtw_cost / len(warp_path)` | -| `path_skew` | float ∈ [0, 1] | per-lineage | B only | mean deviation from ideal diagonal | -| `match_q_start`, `match_q_end` | int | per-lineage | B only | absolute query frames bounding the matched window | -| `template_id` | str | per-lineage | B only | UUID linking to template zarr | - -`t_rel_minutes` is shared across tracks. For Paths A, it's the only -time coordinate. For Path B, it covers pre/post windows; the transition -window also has `t_rel_minutes_warped` from the back-projection. - -### 7.5 Diagnostic plots per track - -```bash -# Path B only — same scripts as before, renamed -uv run python rank_by_cost.py --query-set zikv_07_24 -uv run python plot_top_n_montage.py --query-set zikv_07_24 --top-n 30 --worst-n 10 -uv run python plot_pcs_aligned.py --query-set zikv_07_24 --top-n 50 +```sh +uv run python applications/dynaclr/scripts/pseudotime/2-align_cells/align_lc.py \ + --datasets applications/dynaclr/configs/pseudotime/datasets.yaml \ + --config applications/dynaclr/configs/pseudotime/candidates.yaml \ + --candidate-set \ + --pred-column predicted_infection_state \ + --positive-value infected \ + --min-run 3 ``` -### 7.6 Guards and frame-rate invariance +Template/embedding alignment: -DTW with generous psi can collapse the template onto a single query -frame. Five guards prevent and surface this: - -| Guard | CLI flag | Default | Rejects | -|---|---|---|---| -| Non-finite cost | always on | — | tracks too short for the solver | -| Path skew | `--max-skew` | 0.7 | degenerate non-diagonal warps (primary gate per discussion §3.8 #2) | -| Length-normalized cost gate | `--cost-gate` | sweep | stereotypy filter; sweep `{0, 10, 20, 30, 50}%` | -| Minute-based match window | `--min-match-minutes` | 360 | template compressed onto tiny real-time window | -| Pre/post headroom | per query-set YAML | 0 | lineages without real footage on either side | - -Path skew is the primary gate; cost is secondary (per discussion §3.8 -#2: skew rejects DTW failures without rejecting biological variance). -**TODO:** wire path-skew-as-primary into the existing two-pass filter -(currently cost-only). - -`--max-psi-minutes` defaults to half the template duration, read from -template attrs. Per-track psi is `round(max_psi_minutes / dataset_frame_interval_minutes)`. - -## 8. Stage 3 — Organelle remodeling readouts - -Stage 3 forks per track per organelle. Each readout reads its track's -alignment parquet and produces population curves and per-cell timing -metrics. The plotting scripts in this stage replace the current -`plot_organelle_remodeling.py` with per-organelle scripts. - -### 8.1 SEC61 readout - -**TODO: implement** as `readout_sec61.py`. Cosine distance of SEC61 -embedding from per-cell baseline (= mean SEC61 embedding across -pre-window frames). Per-cell trajectory binned by `t_rel_minutes`. -Population curve = binned median + IQR. - -```bash -uv run python readout_sec61.py \ - --track {A-anno|A-LC|B} \ - --query-set zikv_07_24 +```sh +uv run python applications/dynaclr/scripts/pseudotime/2-align_cells/align_embedding.py \ + --datasets applications/dynaclr/configs/pseudotime/datasets.yaml \ + --config applications/dynaclr/configs/pseudotime/align_cells.yaml \ + --template \ + --flavor raw \ + --query-set \ + --candidate-set \ + --min-match-minutes 360 \ + --max-skew 0.8 ``` -Headline metric: real-time `t_rel` at which productive median exceeds -FOV-paired mock 95th percentile. Reported per-cohort. For Path B, -back-projected real-time IQR is reported alongside. - -### 8.2 G3BP1 readout - -**TODO: implement** as `readout_g3bp1.py`. Oscillation-aware metrics -on the post-window (real-time, never warped per discussion §3.6 — stress -granule kinetics are sub-frame, warping by NS3 is meaningless): - -| Metric | Definition | -|---|---| -| `excursion_count` | Number of distance threshold crossings in the post-window | -| `dwell_time_minutes` | Total time above threshold | -| `largest_excursion_amplitude` | Max distance above baseline | -| `largest_excursion_duration` | Duration of the longest contiguous excursion | - -Threshold = mock pulsation 95th percentile, FOV-stratified. - -### 8.3 Phase readout - -**TODO: implement** as `readout_phase.py`. Cosine distance of phase -embedding from per-cell baseline. Same structure as SEC61. +The outputs are: -For claim (a'), phase per-cell `t_50` (or onset-time metric) is -extracted and matched to the same cell's SEC61 fluorescence onset -time. The pair `(t_phase, t_sec61)` per cell feeds Stage 4's Spearman -ρ comparison. - -### 8.4 Per-cell timing metrics - -**TODO: implement** per-track `compute_timing_metrics.py`. Replaces -the existing single-track version under `3-organelle-remodeling/`. +```text +applications/dynaclr/scripts/pseudotime/2-align_cells/ +├── A-anno/alignments/.parquet +├── A-LC/alignments/.parquet +└── B/alignments/

Q=BCYbx`_! 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 ZT~}lOcziTa^&i18#s=m$u{Z9-{SSOR<68g# delta 25729 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 L>u#+BV_PTgO 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 E3n;A-IsgCw diff --git a/applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.png b/applications/dynaclr/docs/DAGs/visuals/mock_metrics_bar.png index d0ad9940927a7b05f9a1a8375a24ab50cb685323..76800bdca8a9e6876e18a25c611ff7a5b3a60b35 100644 GIT binary patch literal 48137 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 IRjdO4FTlI(UjP6A literal 44174 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