docs(tutorial): default to precomputed mask + guard against empty segmentation - #54
Closed
xuefei-wang wants to merge 308 commits into
Closed
docs(tutorial): default to precomputed mask + guard against empty segmentation#54xuefei-wang wants to merge 308 commits into
xuefei-wang wants to merge 308 commits into
Conversation
…-safe B2
Resolves two silent-correctness blockers surfaced by the 2026-05-26 deep
review. Both affected the published-baseline numerics path; both produced
no error signal.
* scripts/predict.py CT abstention: the per-(tissue, modality) IQR fence
read tissue/modality metadata via root.group_keys(), which on v8+
nested archives (modality/tissue/cohort/sample/fov) returns modality
directories, not dataset leaves. The downstream merge then filled
tissue/modality with "unknown" for every row, collapsing the
per-group fence into a single global fence. Replaced with
_discover_fov_keys, which handles both flat (v7) and nested (v8+)
layouts. (errors.md F1)
* model.py mean-intensity CLS residual: scatter_ with safe_idx[~valid]=0
aliased every padding write to column 0, and last-write-wins semantics
then zeroed the real mean intensity of whichever marker sits at index 0
of marker2idx on every forward pass with mean_intensity_mode in
("cls_residual","both") — the paper-default operating point. Fixed
structurally with a sink column at index n_markers; padding writes
land there and the sink is sliced off before the projection. To
preserve inference parity with v0.1.0 checkpoints (which were trained
against the buggy zero-marker-0 contract), the forward pass also zeros
column 0 explicitly when compat_marker0_zero=True (default). Pass
compat_marker0_zero=False to a future v0.2.0 retrain to recover the
real marker-0 signal. (numerical-stability.md HIGH-1)
Also tightens the train/inference module boundary:
* compute_iqr_fence + ABSTENTION_LABEL move to deepcell_types/abstention.py
(numpy-only public module). deepcell_types/predict.py no longer imports
from training/abstention; the [train] extra is no longer load-bearing
on the inference path. training/abstention.py keeps a re-export for
backward compatibility. (complexity.md BLOCKER)
* fusion linear's bias was reaching padding token positions despite the
zeroed inputs (W@0 + b = b); padding tokens are masked from attention
so nothing leaks today, but the CHANGELOG-documented "padding produces
zero output" invariant was violated at the tensor level. Added a
post-fusion masked_fill so the invariant holds. (numerical-stability.md
MEDIUM)
* scripts/predict.py abstention sentinel was -1; Python API uses
"Unknown". CLI now also writes "Unknown" so downstream tooling sees a
single contract. (api.md HIGH-3)
* predict.py: stale "Unknown tissue_exclude=" error message named the
deprecated kwarg and gave no hint of valid tissues — now reads
"Unknown tissue_filter=...; Valid tissues: [...]". Deprecation warning
for the tissue_exclude alias switched DeprecationWarning -> FutureWarning
so end-user scripts actually see it. (api.md HIGH-1, MEDIUM-7)
* Tests updated for the new error message and warning class.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…p-review pass)
Resolves the long tail of release-readiness items surfaced by the
2026-05-26 deep review. Nothing here changes inference outputs; the
correctness fixes ride in the prior commit.
Legal / packaging:
* LICENSE: fill the Apache appendix placeholder
("Copyright [yyyy] [name of copyright owner]" -> Caltech, 2024-2026).
* pyproject.toml: switch authors email to [email protected] (was a
personal institutional address); add License classifiers ("Apache
Software License" + "Other/Proprietary License" — the LICENSE is a
modified Apache 2.0 with non-commercial / academic carve-outs); add a
setuptools>=61 floor so the build-system actually understands
pyproject-driven [tool.setuptools].
* docs/conf.py: replace literal "%Y" copyright string (Sphinx doesn't
strftime that) with "2024-2026"; switch author to "Van Valen Lab".
Public-facing docs:
* README "--zarr_path" claim corrected to "--zarr_dir" (every training
script declares the latter; the env-var pickup is inference-only).
* docs/index.md: same correction; fix [dc_org] link to the actual
users.deepcell.org login URL (was pointing at the Sphinx page); drop
the stale "torchvision" mention in the [train]-extras list (the
dataset code explicitly avoids torchvision).
* docs/site/tutorial.md: zarr instructions unified on >=3 (the inline
sanity check raised against "zarr>2" pip hint); typo "nad" -> "and";
napari.Viewer(show=True) -> show=False so the notebook executes in
headless docs builds.
Archive version (canonical = v10):
* dct_kit/config.py ARCHIVE_CANDIDATE_NAMES now lists v10 first, v9 and
v8 retained for backward compat.
* docs/index.md, docs/site/tutorial.md, training/archive.py docstrings
updated to reflect v10 as the current canonical.
Public API surface:
* deepcell_types.__all__ adds download_model (the mandatory first call
for any user) and PreprocessedFov (the dataclass returned by
preprocess_fov, which was already exported).
* utils.download_training_data dropped the documented-but-ignored
version= kwarg — the asset is pinned to public_data_v1.1.zip.
R&D residue / lab-internal references:
* training/config.py: stale "deepseek-r1-70b" defaults on
get_channel_embedding / get_celltype_embedding / load_marker_embeddings_array
-> "text-embedding-3-large" (matches docs and the actual pipeline).
* training/config.py: "hubmap-to-zarr migrate_archive_v2.py" references
in user-visible warning strings -> generic "archive-ingestion
pipeline" wording.
* training/__init__.py: drop torchvision from the gated-deps list (not
actually in [train]).
* training/abstention.py: drop dead docs/audits/... cross-reference
from a docstring that ships in the wheel.
* scripts/generate_openai_embeddings.py: usage example used a personal
/data/xwang3/... path and pointed at svd_512_v7.npz (canonical is
svd_512_v6.npz) — fixed both.
* scripts/validate_archive_contract.py, scripts/generate_splits.py,
scripts/benchmark_gold_standard.py: drop the
"tissuenet-caitlin-labels.zarr" default from --zarr_dir / --help text
(a researcher-named lab-internal filename).
* scripts/benchmark_gold_standard.py: "deepcelltypes installed" doc
references the long-retired no-hyphen package name -> "deepcell-types".
Security:
* utils/_auth.py: the 403-on-bad-token branch interpolated the user's
DEEPCELL_ACCESS_TOKEN into the ValueError message — any traceback
logger (Sentry, wandb, CI) would have captured it. Now reads "The
provided DEEPCELL_ACCESS_TOKEN is not valid." with no token echo.
* utils/_auth.py: the streaming download left a truncated file on disk
if the request was interrupted (no atomic-write, no cleanup); now
wrapped in try/except that unlinks fpath on any exception.
* utils/_auth.py: when file_hash is provided, the function now verifies
the downloaded bytes against it before returning (previously the hash
was used only for cache-skip, never as a post-download integrity
gate). Catches truncated downloads and silent CDN corruption.
* utils/_auth.py: drop the emoji from the success log line (breaks on
some HPC log parsers; inconsistent with the rest of the codebase).
* scripts/train.py:395, scripts/benchmark_gold_standard.py:526,
scripts/fold_lora_into_proj.py:59: torch.load(weights_only=False)
-> True. The inference path was already weights_only=True; these
three checkpoint loads ship as part of public scripts that take
CLI-supplied paths, so a malicious .pt would execute arbitrary
pickle on the user's machine. Checkpoints only contain tensor /
primitive state, so weights_only=True is sufficient.
Tests:
* tests/test_canonical_inference.py: the two tests whose expectations
shifted with the new error message and FutureWarning class are
updated in the prior commit (kept together with the production
change).
.gitignore: ignore /.claude/ (per-project local CLI settings).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Adds the public-release CI that was missing entirely. Pairs with the
test guards needed to make the inference/[train] dep separation
actually work on a bare ``pip install deepcell-types`` checkout (the
prior 2026-05-26 audit found 11 of ~24 test files collection-erroring
on inference-only installs because of bare top-level imports of
zarr / pandas / torchmetrics).
CI (.github/workflows/ci.yml):
* inference-only job: ``pip install -e .`` + pytest. Guards that the
bare install has every dep it needs and that no [train]-only module
has slipped into the predict() import graph
(tests/test_inference_deps.py is the active probe).
* full job: ``pip install -e .[train]`` + pytest. Catches drift
between training-side code and the tests that exercise it.
* lint job: ruff over deepcell_types/, scripts/, tests/.
* Matrix on Python 3.11 / 3.12; concurrency cancels stale runs on push.
tests/conftest.py:
* collect_ignore-based skip for the 13 test files that legitimately
need a [train]-extra at module-import time (zarr, pandas, ...). On
an inference-only install these now skip cleanly instead of
collection-erroring, which means CI's inference-only matrix row
actually runs to green and verifies the bare-install invariant.
When the [train] extra is installed the same files run normally.
tests/test_post_v7_strip.py:
* _help_output() used check=False; on an inference-only install the
``python -m scripts.predict --help`` subprocess crashed with
ImportError (torchinfo/torchmetrics), the traceback was returned as
a string, and the ``assert flag not in out`` assertions trivially
passed. Switched to check=True + ``sys.executable`` and guarded the
whole module with importorskip("torchinfo") / importorskip("torchmetrics").
tests/test_inference_deps.py:
* Added click, plotly, kaleido, openai, tifffile to TRAINING_ONLY_MODULES
so the dep-separation guard catches future module-level imports of
any [train]-only package — not just the original 8.
.gitignore: ignore /.claude/ (per-project local CLI settings).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
k=0 yields fence=Q1 (abstains bottom quartile), not a no-op. The "off" behavior is enforced caller-side via a k>0 guard. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ipts - embeddings/svd_512_v6.npz -> embeddings/svd_512.npz (4 files) - delete scripts/fold_lora_into_proj.py (LoRA adapter removed, no longer needed) - delete scripts/generate_openai_embeddings_v8.py (one-off, superseded by generate_openai_embeddings.py) Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…pace Moves the Rumberger et al. 2025 Pan-Multiplex Gold-Standard benchmark + Nimbus baseline runner + dataset ingest + download helper to deepcell-types-research-workspace/scripts_local/. These are evaluation/research tools, not part of the core train/predict/pretrain user-facing CLI surface. - scripts/benchmark_gold_standard.py -> workspace/scripts_local/ - scripts/run_gold_standard_nimbus.py -> workspace/scripts_local/ - scripts/ingest_gold_to_zarr.py -> workspace/scripts_local/ - scripts/download_gold_standard.sh -> workspace/scripts_local/ Drop the corresponding bullets from README/docs/index, tidy the pyproject tifffile comment, and refresh the dataset.py code comment that referenced the old path. CHANGELOG.md is left untouched (historical record). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Drops the vestigial one-deep `dct_kit/` subdir (legacy artifact from a pre-monorepo "library" split) by moving its contents up to the package root: - `dct_kit/config.py` → `deepcell_types/config.py` (DCTConfig) - `dct_kit/config/channel_mapping.yaml` → `deepcell_types/channel_mapping.yaml` - `dct_kit/image_funcs.py` → folded into `deepcell_types/preprocessing.py` Only `patch_generator` is imported externally (by `dataset.py`); all helper functions are now `_`-prefixed private. Two unused helpers (`histogram_normalization`, `combine_raw_mask`) had zero call sites and were dropped. The new and legacy preprocessing pipelines are intentionally kept side by side in `preprocessing.py` — `_percentile_threshold` (NaN-percentile) is the canonical ingest path used by `preprocess_fov`, while `_percentile_threshold_nonzero` (nonzero-indexed) is what the published checkpoint was trained against and is wired into `patch_generator`. A comment block at the merge boundary calls this out so a future reader doesn't try to dedupe them without retraining. Inference + training test suites green (58 passed, 3 skipped). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The archive is being rebranded from the lab-internal `caitlin-labels` name. This sweep covers tests, all four baseline submodules' README + CLI defaults, and bumps the submodule pointers (cellsighter, nimbus, xgboost) to their renamed-doc commits. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
CI lint job (ruff check) was failing across the fork. This pass fixes
all 75 reported errors without changing behavior:
- scripts/{predict,pretrain,train}.py: move ``logger = ...`` below the
import block (was E402 x25 from being sandwiched between imports).
- deepcell_types/training/config.py: drop the duplicate bottom-of-file
``from .archive import (...)`` block — the top-of-file import already
exposes those names as module attributes (F811 x8). Keep the
``from .patch import (...)`` re-export. Add ``# noqa: F401`` on the
archive import for the names not used in-file.
- Remove confirmed-unused imports across deepcell_types/ and tests/
(F401 x20).
- Convert ``assert x == True/False`` on numpy bool entries to direct
truthiness in tests/test_{v2,zero_channel_masking}.py (E712 x14).
- Drop unused local vars (``ct_label``, ``first_size``, ``epoch1_a``,
``scaler``) (F841 x4).
- Drop ``f`` prefix on placeholder-less f-strings (F541 x2).
- Convert one ``skip = lambda k: ...`` to a ``def`` (E731).
- Rename ambiguous loop var ``l`` -> ``logit`` (E741).
Smoke-checked the inference import path; ``ruff check`` is now clean.
``apply_abstention`` writes ``ABSTENTION_LABEL`` (= "Unknown") to the ``predicted_ct`` column of abstained rows, but ``test_k_0_5_aggressive_on_1000_cells`` was still asserting ``== -1`` — the original numeric sentinel from before the column went string-typed. Pre-existing CI failure, surfaced once the ruff job stopped masking it. Import ``ABSTENTION_LABEL`` and compare against it instead.
…default Clarify how to run the canonical inference pipeline relative to the upstream/self-contained flow: - index.md: add a "TissueNet archive (required)" section — the registry is now read from a tissuenet-v*.zarr archive (was bundled YAML), via zarr_path= / DEEPCELL_TYPES_ZARR_PATH, with the FileNotFoundError / ValueError failure modes spelled out. - tutorial.md: note the on-by-default low-confidence abstention (ct_abstention_k=0.2 -> "Unknown"), how to disable it (=0) for raw argmax labels, and the return_probabilities / PredictionResult option. - CHANGELOG.md: record the abstention default as a breaking change. - preprocessing.py: convert preprocess_fov docstring to NumPy style so numpydoc renders it cleanly (clears the sphinx -W warning). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Address PR #41 review items: - Strip cell-type quality reporting to macro-F1 only. LossesAndMetrics.compute() now returns ct_macro_f1 (drops ct_macro_accuracy / ct_weighted_accuracy / ct_weighted_f1); marker-positivity (mp_*) and domain_accuracy are separate heads and untouched. - scripts/train.py: remove the --best_metric flag; log/select on macro-F1 only. Rename early-stopping state to best_val_macro_f1, with a legacy best_val_macro_acc fallback on resume so old checkpoints still load. - scripts/predict.py: CT abstention eval reports hierarchical macro-F1 pre/post instead of macro/weighted accuracy. - abstention.py: remove the misplaced macro_weighted_accuracy + hierarchical_correct helpers; replace with hierarchical_macro_f1, which delegates to the canonical adjust_conf_mat_hierarchy + _conf_mat_summary so the number is comparable to the model's ct_macro_f1 and the baseline reports. - Tests: repoint test_v2 CT-metric tests to macro-F1 (incl. assertion that the removed accuracy keys are gone); add hierarchical_macro_f1 coverage. - Restore Dockerfile (was deleted, and stale): install from pyproject.toml with a DCT_EXTRAS build arg covering inference / [train] / [baselines] / [all]. baseline_features._conf_mat_summary still computes all four metrics; the baselines/* submodules depend on those keys, so it is intentionally unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…lltypes
The per-dataset `celltype_mapping` was an identity `{ct: ct}` dict — a
leftover from before cell types were canonicalized at zarr ingestion.
Every consumer used it as `.get(label, label)`, a no-op. Replace it with
`dataset_celltypes: Dict[str, List[str]]` (the annotated cell-type names
per dataset) and drop the dead remapping:
- `FullImageDataset` index build and `_extract_all_dataset_features`
no longer reference any mapping — a cell is kept iff its label is in
`ct2idx` (unchanged behavior; the lookup always returned the input).
- `build_tissue_mapping_from_split` — the one consumer that genuinely
needs per-dataset names — iterates `dataset_celltypes` directly.
Also extract the metadata aggregation out of `_compute_all_mappings`
(welded to a ProcessPoolExecutor reading ~1GB of zarr.json) into the
pure static `_aggregate_metadata`, making it unit-testable without an
archive. New `tests/test_dataset_celltypes.py` pins the aggregation and
`build_tissue_mapping_from_split` behavior.
Behavior-preserving: `dataset_celltypes` retains all annotated names
(including those outside `ct2idx`); the `ct2idx` filter stays in the
consumers and tissue mapping exactly as before.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
`scripts/predict.py` defaults `--ct_abstention_k=0.2`, but test_default_k_0_5_abstention_is_on named, documented, and exercised k=0.5 as "the default". Rename to test_default_k_0_2_abstention_is_on and pass k=0.2. On the seed=99 1000-cell synthetic frame this abstains 17.8% (vs 10% at k=0.5), still within the existing 0.03–0.30 band. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…om model output Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Behavior-preserving cleanups surfaced while reviewing the v0.1.0 monorepo merge (PR #41). No change to model numerics or public inference outputs; ruff clean, full test suite green (224 passed, 10 skipped). - config.py: drop never-read constants HIST_NORM_KERNEL_SIZE and MAX_CHUNK_PER_CT_PER_DATASET - utils/_auth.py: remove dead extract_archive() and its now-unused tarfile/zipfile imports (no caller anywhere in the repo) - training/dataset.py: remove dead _build_centroid_tree() wrapper and its import; drop unused self._idx2marker - training/utils.py: add shared load_matching_state_dict() helper - scripts/{train,pretrain}.py: replace 3 copy-pasted checkpoint warm-start loops with the shared helper - training/baseline_features.py: hoist set(dataset_keys) out of a comprehension Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Approved design for folding the four baseline submodules into an in-repo deepcell_types.baselines package behind a unified click-group runner, with per-method extras. Vertical slice (xgboost + nimbus) first; verbatim move + characterization unit tests guarantee no computation change. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
TDD plan: byte-identical (sha256-verified) move of xgb/{run,tuning}.py and
nimbus run.py into deepcell_types.baselines behind a LazyGroup runner, with
per-method extras and a hand-derived characterization test for the nimbus
metric reducer. Removes the two submodules. maps/cellsighter deferred to round 2.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
DCTConfig (deepcell_types/config.py): - self.SEED, self.BATCH_SIZE instance attrs - self._core_celltypes backing field and the core_celltypes property TissueNetConfig (deepcell_types/training/config.py): - SEED, BATCH_SIZE class constants - NUM_TISSUES property - get_marker_positivity method (marker_positivity_labels property kept; used by dataset.py) - combined_celltype_mapping property - color_mapping property - core_tree property - lineage_mapping property - get_channel_embedding method - get_celltype_embedding method Each removed member was verified (grep -rlw across deepcell_types / scripts / tests / baselines) to have no reader outside its own definition. Kept members with readers: marker_positivity_labels, tissue2idx, load_marker_embeddings_array, celltype_mapping, domain_mapping, ct2idx, domain2idx, marker2idx. Pure deletions (128 lines, 0 insertions); ruff clean; 180 tests pass (15 pre-existing zarr-import collection errors, identical with and without this change). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…guard, py3.12 note) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Extract cohesive symbol groups from the 1875-line training/dataset.py into focused sibling modules using the extract-and-re-export pattern. Behavior is preserved byte-for-byte; every public (and private) symbol historically importable from deepcell_types.training.dataset is re-exported there, so all existing imports and `python -m scripts.*` entry points keep working unchanged. New modules and the symbols moved into each: - transforms.py: _Compose, _RandomHorizontalFlip, _RandomVerticalFlip, AugmentedDataset, DropOutChannels - samplers.py: compute_sample_weights, FOVGroupedSampler, SequentialFOVGroupedSampler - splits.py: CellIndexRecord, _ADVISORY_SPLIT_METADATA_KEYS, _find_sole_source_fovs, _build_fov_strata, create_fov_splits, _split_metadata_for_dataset, _format_fov_examples, save_fov_splits, load_fov_splits - dataloader.py: create_dataloader, DataLoaderConfig, create_dataloader_from_config dataset.py keeps the core FullImageDataset and _archive_fingerprint, imports CellIndexRecord from splits (no cycle: splits never imports dataset), and re-exports the full public surface via __all__. dataset.py: 1875 -> 884 lines. behavior-preserving; all symbols re-exported from training.dataset for back-compat; ruff clean, 224 tests pass / 10 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…nner
Byte-identical copy of xgb/{run,tuning}.py (sha256-verified) behind a
LazyGroup click runner. Submodule still present; removed in a later commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Documents the remaining simplification work with a per-task method contract (behavior-preserving, ruff+pytest gates, Bash-edit to avoid formatter churn, no force-push): - TODO C: promote model **kwargs to explicit params (lowest risk; do first) - TODO B: CellTypeAnnotator.forward() -> NamedTuple (arity-preserving) - TODO A: split training/config.py god-file (extract-and-re-export) - TODO D: behavior-changing / public-API items needing sign-off Pins the verified current model.py signatures/return shapes at a126fac. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Byte-identical copy of nimbus run.py (sha256-verified); registered in the runner. Adds a hand-derived characterization test for the pure marker- positivity metric reducer. Submodule removed in the next commit. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Removes the two folded-in submodules and their .gitmodules stanzas, adds self-contained baseline-xgboost / baseline-nimbus extras, registers the new packages, and carries third-party attribution into deepcell_types/baselines/NOTICE. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Reverts the non-commercial/academic carve-out in LICENSE back to plain OSI Apache-2.0. Per request, only LICENSE is reverted; NOTICE, pyproject.toml, and __init__.py retain their carve-out wording. Co-Authored-By: Claude Fable 5 <[email protected]>
Co-Authored-By: Claude Fable 5 <[email protected]>
Removes the prior Frozen-CLS headline entry from _model_registry; the 2026-06-15 resMLP checkpoint is the sole released model. Co-Authored-By: Claude Fable 5 <[email protected]>
…mlp head support No backward compatibility is kept for the retired 2026-05-17.pt checkpoint. - predict.py: remove the canonical-ct2idx SHA anchor and its helper; a checkpoint that bundles no ct2idx is now rejected outright. Arch keys (n_heads, compat_marker0_zero) are required unconditionally since every supported checkpoint self-describes. - model.py/predict.py/train.py/scripts: remove the legacy 3-layer 'mlp' cell-type head entirely; ResidualMLPHead is the only head. Drop the --ct_head_arch CLI option and the ct_head_arch parameter. - retrain_head.py: load the backbone with ct_head.* keys stripped and strict=False (the head is discarded and retrained), instead of scaffolding an mlp head to strict-load. - tests: reject-without-ct2idx test replaces the canonical-ordering test; drop the legacy-mlp-head load test. Co-Authored-By: Claude Fable 5 <[email protected]>
…pre-guard checkpoints (#63) Current predict.py / scripts/predict.py enforce validate_checkpoint_vocabulary, which requires a checkpoint to bundle its own `ct2idx` so a permuted vocabulary cannot silently mislabel every cell. train.py and retrain_head.py already write `ct2idx` + `canonical_channels` for new checkpoints, but the released 2026-06-15 resMLP asset predates that convention (it holds only {model, config}) and therefore fails the guard on current master — `download_model("2026-06-15")` + `predict(...)` raises for every user. Add a helper that back-fills those keys from DCTConfig (packaged canonical vocab by default, or an explicit `--zarr_path`), with the same n_celltypes guard the count check would apply. The pure `bundle_vocabulary()` is unit tested. Verified end-to-end: the re-packaged 2026-06-15 asset loads via predict() on current master and yields a plausible distribution on the tutorial's HBM994_PDJN_987 placental FOV. Completing the fix is a release step (maintainer): run this script, upload the output to users.deepcell.org, and pin the uploaded file's md5 in _model_registry (torch.save is non-deterministic, so the hash must be the actual uploaded artifact's). Co-authored-by: Claude Fable 5 <[email protected]>
#64) * chore(release): reconcile stale defaults/docs + model_name convenience Public-release audit of the default-facing surface (defaults, docs, flags, fallbacks). The default inference path already resolves to the canonical model; these changes remove internal inconsistencies a public user could hit. Abstention framing — the paper headline is full-coverage / no abstention; k=0.2 is a historical opt-in ablation. Reconcile the stragglers that still called k=0.2 "the paper headline", matching the authoritative CLI help and README: - deepcell_types/abstention.py, deepcell_types/predict.py (docstrings) - scripts/predict.py (inline comment; it also wrongly said abstention is "on by default" — the CLI default is 0.0 / off) - docs/site/tutorial.md (shipping tutorial) - tests/test_ct_abstention_cli.py (rename the test that asserted a k=0.2 "default" the CLI no longer has; it never checked the real default) Env-var consistency — source the archive path from DEEPCELL_TYPES_ZARR_PATH (the canonical var used by config/inference) with DATA_DIR kept as a non-breaking fallback, across the training scripts and baseline runners. predict() model_name convenience — accept a registry version string (e.g. "2026-06-15") or "latest" and auto-download/cache it, so a caller can reach the canonical checkpoint without knowing the on-disk filename. Minor — export download_training_data at top level (was only in utils.__all__ + reference.rst); clarify in the README that the resMLP head is auto-detected at inference (no ct_head_arch flag). Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(predict): drop deprecated device_num alias The `device_num=` keyword was a back-compat alias for `device=` that emitted a DeprecationWarning. For the public release there is no prior published API to stay compatible with, so remove the alias, its resolution branch, and its docstring entry. `device=` is now the single device argument (still required). Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(archive): drop zarr-3.0-alpha metadata monkeypatch `_patch_zarr_v3_alpha_metadata` globally monkeypatched GroupMetadata.from_dict and ArrayV3Metadata.from_dict so a zarr 3.0.0a* alpha could parse the `consolidated_metadata` / `storage_transformers` keys emitted by newer writers. The pin is now `zarr>=3.1` (stable), which parses these natively — verified by reading the real 292-marker archive (root group + a preprocessed array's v3 metadata) with the shim removed. Remove the monkeypatch and its back-compat re-export from training/config.py. Co-Authored-By: Claude Fable 5 <[email protected]> --------- Co-authored-by: Claude Fable 5 <[email protected]>
v0.1.0: monorepo merge — unified training + inference, canonical CellTypeAnnotator, archive-free inference, vendored baselines
…ckerignore README hardcoded cuda:0 with no CPU-fallback guidance for GPU-less users. Add list_supported_markers()/list_supported_cell_types() (mirroring list_model_versions()/list_baseline_names()) so users can pre-flight-check their marker panel before downloading a checkpoint, and document them in docs/index.md and the API reference. Add a .dockerignore so `docker build .` stops baking .git/.venv/.env*/local artifacts into image layers. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01BFEzZSEzzxkFgfMSU1XFHE
Add the v4 preprint URL and bump the year/pages to match the 2026-07-06 posting date. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01NnpBk4LMEG6MiUrEgt8khK
Replace the "and others" truncation with the remaining authors (Keren, Yue, Barnowski, Van Valen) from the bioRxiv v4 metadata. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01NnpBk4LMEG6MiUrEgt8khK
bioRxiv v3 and v4 inserted Ahamed Raffey Iqbal as the third author (between Dilip and Bussi); reflect that in the BibTeX author list. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01NnpBk4LMEG6MiUrEgt8khK
docs: update citation to bioRxiv v4
…#65) - PatchDataset.__init__ now rejects raw/mask spatial-shape mismatches, matching the check already present in preprocess_fov. - validate_checkpoint_vocabulary now hard-requires canonical_channels on any checkpoint dict, the same way it already hard-requires ct2idx — a checkpoint missing it previously skipped the marker-ordering guard entirely, letting a permuted marker vocabulary silently mislabel predictions. All in-repo checkpoint writers (train.py, retrain_head.py, repackage_release_checkpoint.py) already bundle it. - Remove the now-unreachable legacy state_dict branch in scripts/predict.py: validate_checkpoint_vocabulary raises before that branch could ever be reached. Claude-Session: https://claude.ai/code/session_01BFEzZSEzzxkFgfMSU1XFHE Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…ptional patient-level split grouping (#66) --resume_path with an increased --epochs silently discarded the new OneCycleLR schedule: LRScheduler.load_state_dict() is a raw self.__dict__.update(), so loading the checkpoint's scheduler state overwrote total_steps/_schedule_phases back to the OLD (shorter) run. resume_onecycle_schedule() (deepcell_types/training/utils.py) now rebuilds the schedule at the current run's total_steps and fast-forwards it to the resumed step count instead, applied in both scripts/train.py and scripts/pretrain.py. Also add an opt-in group_by_patient mechanism to create_fov_splits / save_fov_splits (deepcell_types/training/splits.py) and a --group-by-patient flag on scripts/generate_splits.py: FOVs sharing a parsed "...Patient<N>..." id are kept entirely on one side of the split, preventing patient-level leakage (verified in splits/fov_split_valsubset.json: mccaffrey_tb_mibi Patient2/10/13 appear in both train and val today). Default is off, so existing split files are unaffected unless explicitly regenerated with the flag. Claude-Session: https://claude.ai/code/session_01BFEzZSEzzxkFgfMSU1XFHE Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…#67) Nimbus's per-dataset load loop silently dropped FOVs on missing `preprocessed` data or a `load_fov_data` error, biasing the baseline comparison (abstention.py documents that baselines must be scored at full coverage). Mirror training/dataset.py's failed_keys/fail-rate convention: always report "Skipped N of M datasets" before metrics are finalized, and raise RuntimeError above a 1% failure-rate threshold. Also narrow the bare `except Exception` around load_fov_data to the exception types it can actually raise. Claude-Session: https://claude.ai/code/session_01BFEzZSEzzxkFgfMSU1XFHE Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…/dataloader integration (#69) Three critical paths had zero or only indirect coverage: - resolve_gold_metadata (Pan-M Gold-Standard tissue/modality resolution): known-key resolution, unknown-key errors, strict-mode refusal of non-canonical substitutions, and the dct_config vocab-validation branch. - scripts/predict.py::main() (the eval CLI generating reported metrics): end-to-end run via CliRunner against a synthetic training-shaped zarr archive, covering CSV schema, ct_abstention_k on/off, and the hierarchical macro-F1 pre/post reporting. - FullImageDataset.__getitem__ / create_dataloader: real archive-backed construction and indexing (tensor shapes/dtypes, FOV-zero-channel masking), plus a real DataLoader batch via create_dataloader. Claude-Session: https://claude.ai/code/session_01BFEzZSEzzxkFgfMSU1XFHE Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
`compute_iqr_fence` was exported in `deepcell_types.abstention.__all__` and documented in the public API reference, but it is an internal helper: the supported entry points are `predict()` (via `ct_abstention_k`) and the batched `apply_abstention`. IQR-fence abstention is also off by default and dropped from the paper headline, so surfacing the low-level fence math as public API overstates its role. Rename to `_compute_iqr_fence`, drop it from `__all__`, and remove it from `docs/site/reference.rst`. Internal callers (`predict.py`, `apply_abstention`) and tests are updated to the private name; behavior is unchanged. Claude-Session: https://claude.ai/code/session_01AVaWu3LRbCmBDBkB64Y3cH Co-authored-by: Claude Fable 5 <[email protected]>
The two runnable README snippets hardcoded device="cuda:0", forcing GPU 0. Derive the device from the environment (default GPU if available, else CPU) and note how to target a specific GPU, so users pick their own device. This also subsumes the separate "pass device=cpu" note, which is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01HfHQX9gC2no4WamCF4H8PD
… 2026-06-23 SSL arm) (#70) * chore(models): bump 2026-06-15 registry md5 to the vocab-bundled asset The pinned 704616a1 checkpoint predates the vocab guard (no bundled ct2idx), so download_model() -> predict() raises "does not bundle a ct2idx" for every user. Point the registry at the re-packaged asset (adds ct2idx + canonical_channels; weights byte-identical). DRAFT: do not merge until the bundled .pt is uploaded at the same asset path (md5 b819a7e0b177ad5330394eab3c6c7ad8), else download_model hash-check fails. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01BFEzZSEzzxkFgfMSU1XFHE * chore(models): default registry to vocab-bundled 2026-06-23 ptft resMLP Add the 2026-06-23 pretrain->finetune (SSL) resMLP arm as a registry entry and make it the default (_latest), matching deepcell-auth's default DCT version. Its md5 tracks the vocab-bundled repackage (402e94c1...) of deepcell-types_2026-06-23_resmlp_ptft.pt: weights byte-identical to the original (0 tensors differ), config identical, ct2idx (51) + canonical_channels (278) added so it passes validate_checkpoint_vocabulary. The un-bundled original (067f558b...) fails the guard, like the 2026-06-15 asset. 2026-06-15 (paper Fig-3c headline) is retained as a selectable version. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_016oVttaRJScBzjoJDB8ZkpA * chore(models): default registry to the 2026-06-15 from-scratch resMLP Set _latest back to 2026-06-15 (paper Fig-3c headline, vocab-bundled md5 b819a7e0...) as the default download_model() checkpoint, keeping the 2026-06-23 pretrain->finetune (SSL) resMLP arm (bundled md5 402e94c1...) as a selectable version. Aligns the deepcell-types default with the deepcell-auth default so both packages resolve to the same checkpoint. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_016oVttaRJScBzjoJDB8ZkpA --------- Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
… re-hosted nimbus (#74) The baseline download registry pinned the pre-resume50 generation (two generations stale: d06f8aee/d2d1930d/e3a54e5a/00d110cc/0d94609a), which no longer matches the released baseline artifacts. Update cellsighter, maps (+ _stats.npz), and xgboost (+ .remap.json) to the 2026-06-30 DCT-sampler retrains -- the release-of-record that produced the published baseline prediction CSVs and headline numbers. Also drop the "nimbus" entry: Nimbus is inference-only and its pretrained weights are produced and distributed upstream (angelolab/Nimbus-Inference); this project should not redistribute them. The nimbus baseline runner is unaffected (it loads weights via the official nimbus-inference library). download_baseline_checkpoint("nimbus") now raises with a pointer to the official source instead of serving a re-hosted copy. New md5s require re-uploading the 2026-06-30 files to users.deepcell.org before they are served (see PR checklist). Claude-Session: https://claude.ai/code/session_016oVttaRJScBzjoJDB8ZkpA Co-authored-by: Claude Fable 5 <[email protected]>
feat(usability): CPU-fallback note, marker/cell-type listing API, .dockerignore
- tutorial.md: auto-select inference device (cuda if available else cpu), matching the README change in d039da0 instead of hardcoding cuda:0 - API-key.md: drop "nimbus" from download_baseline_checkpoint options (it now raises; nimbus weights are distributed upstream) and note the [baseline-nimbus] install path Claude-Session: https://claude.ai/code/session_012KP2MDvQ2tiSYssggv7ygK Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
* refactor(api): default ct_abstention_k=0 (opt-in), matching the CLI predict() now defaults ct_abstention_k to 0 instead of None. Behavior is unchanged -- the gate was already `k > 0`, so both None and 0 mean "no abstention" -- but 0 is now the documented off sentinel and the Python API matches scripts/predict.py, whose --ct_abstention_k already defaults to 0. Abstention stays fully opt-in: pass a positive k to enable the per-FOV IQR fence. Updates the docstrings, the signature-default test, and the tutorial note. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_016rLgKLZ4Z2h4fGDwdyy8Gc * docs: strip stderr from tutorial output + trim index page - conf.py: nb_output_stderr = "remove" so the executed tutorial no longer renders build-machine file paths, the NVML warning, tqdm bars, or the masked-channel UserWarning in its cell output. - index.md: drop the "TissueNet archive (optional)" section (covered on the Model and Datasets page), drop the scripts/train.py internals parenthetical, and replace the tissuenet-v8/v9/v10 version terms with expanded-tissuenet.zarr. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_016rLgKLZ4Z2h4fGDwdyy8Gc --------- Co-authored-by: Claude Fable 5 <[email protected]>
…mentation
A fresh user following the tutorial's cellSAM path could get an empty or
degenerate segmentation mask (CPU/OOM, an arbitrary membrane_channel, or a
cellSAM install mismatch). The only post-segmentation check verified mask
*shape*, which an all-background mask passes -- predict() then returns [] and
the cell-type DataFrame renders empty ("prediction result appears empty").
- Default to the archive's precomputed CellSAM mask so the pipeline runs
end-to-end without a local segmentation setup.
- Sanity-check both shape AND cell count (mask.max() > 0), failing loudly
instead of silently producing empty predictions.
- Keep the cellSAM path intact but optional/reference-only (plain code blocks,
not executed at build), so the docs build no longer requires cellSAM/GPU.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Contributor
|
We do not want to replace the computation here - the point of the example is to demonstrate a full end-to-end (i.e. raw image -> segmentation -> celltype prediction) pipeline. Note also that this serves as the only real test that the components of this package are working as expected. |
Collaborator
Author
|
@rossbar Agreed — closing this in favor of #55, which keeps the computation in place. #55 leaves the raw image → cellSAM → cell-type prediction path fully executed and instead:
This PR is also unmergeable independent of the review — its merge base is 616d4b5, so GitHub reports it as 144 files / ~47k lines against |
Collaborator
Author
|
Superseded by #55. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A user reported that "the cell type prediction result appears empty" on the tutorial. The currently-published outputs render fine, but walking the tutorial as a fresh user surfaced the real failure mode:
mask.shape == img.shape[1:]).membrane_channel, or a cellSAM version mismatch) has the correct shape and passes that check.predict()then returns[]for a mask with no cells, so the cell-type DataFrame renders empty — exactly the reported symptom, with no error to explain it.Changes
docs/site/tutorial.md:ds["segmentations/cellsam"]) so the pipeline runs end-to-end without a local segmentation setup (no cellSAM/GPU required).{code-cell}to plain code blocks, so the docs build no longer requires cellSAM and can't overwrite the default mask.Net: the "empty prediction result" symptom can no longer occur silently, and the tutorial is completable without a GPU/cellSAM.
Verification
nb_execution_mode=offstructure check passes;cellsam_pipelineno longer appears in any executed input cell).ds["segmentations/cellsam"]→relabel_sequential→predict(...)returns a populatedlistof 1646 labels, and the DataFrame renders non-empty. The two new asserts pass trivially (shape matches,max=1646 > 0).🤖 Generated with Claude Code