Skip to content

refactor(virtual-stain): rewrite CLI on cytoland predict_sliding_windows#267

Merged
ieivanov merged 18 commits into
mainfrom
refactor/virtual-staining
Jun 12, 2026
Merged

refactor(virtual-stain): rewrite CLI on cytoland predict_sliding_windows#267
ieivanov merged 18 commits into
mainfrom
refactor/virtual-staining

Conversation

@ieivanov

@ieivanov ieivanov commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Rewrites biahub virtual-stain to run virtual staining in-process via cytoland (modular VisCy) instead of shelling out to the viscy predict CLI through a separate conda env. Normalization and test-time augmentation are delegated to VisCy so behavior stays in sync with viscy predict.

  • Add cytoland under the stain extra (sourced from VisCy's feat/vs-nonsquare-tta-and-helpers branch via [tool.uv.sources]).
  • Validate the predict config against VisCy's own VSUNet / HCSDataModule signatures using jsonargparse — biahub keeps no copy of the model schema, so it can't drift from VisCy.
  • Dispatch one GPU job per position; each job runs AugmentedPredictionVSUNet.predict_sliding_windows, looping over timepoints on a single GPU (GPU-bound, no per-timepoint parallelism).
  • Output store contains only the predicted virtual-stain channels.
  • Normalization: reuse the config's instantiated NormalizeSampled transforms + viscy_data.read_norm_meta (reads precomputed stats from the store; errors with a "run viscy preprocess first" message if missing).
  • TTA: reuse AugmentedPredictionVSUNet.with_rotation_tta (rotation TTA, correct for non-square FOVs); configurable, default on.
  • Adopt the deskew --cluster {slurm,local,debug} / --init pattern: cluster() + init_only() parsing decorators, get_submitit_cluster(cluster=...), RESOURCES: emission, and --cluster debug running jobs in-process in the foreground.
  • Add a minimal example config (settings/example_virtual_stain_settings.yml) pointing at the vscyto3d finetune checkpoint.

Dependencies

Depends on mehta-lab/VisCy#450 (non-square rotation TTA + rotation_tta_transforms / with_rotation_tta / read_norm_meta). Pins iohub 0.3.6 (PyPI). Intentionally not built on refactor-deskew-clean, whose iohub fork (old zarr/tensorstore codec architecture) is runtime-incompatible with cytoland's modern iohub; the --cluster/--init infra was instead replicated locally. Once VisCy#450 merges and releases, repoint [tool.uv.sources] cytoland to a stable ref.

Test plan

  • ruff clean; biahub virtual-stain --help shows --cluster/--init.
  • uv lock / uv sync --extra all resolve (cytoland + viscy-* from the VisCy branch).
  • End-to-end --cluster debug on a non-square (64×48) 2-position plate with TTA on → correct (T, 2, Z, 64, 48) finite output; --init initializes the store and exits; RESOURCES: emitted.

🤖 Generated with Claude Code

ieivanov and others added 4 commits June 3, 2026 15:30
Replace the viscy-CLI subprocess pipeline (preprocess/predict/combine via
conda) with in-process cytoland inference:

- Add cytoland (modular VisCy) under the `stain` extra, sourced from the
  modular-viscy-staging branch.
- Validate the predict config against VisCy's own VSUNet/HCSDataModule
  signatures via jsonargparse, so biahub keeps no copy of the model schema.
- Run predict_sliding_windows per position, looping over timepoints on a
  single GPU; read precomputed normalization stats from the store.
- Rotation TTA (default on) is applied at the volume level with square
  padding so it is correct for non-square FOVs.
- Add minimal example config pointing at the vscyto3d finetune checkpoint.

Adopt the deskew --cluster/--init pattern: add cluster() and init_only()
parsing decorators, extend get_submitit_cluster() to take an explicit
cluster string, emit RESOURCES:, support --init, and run --cluster debug
in-process in the foreground.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delegate normalization and test-time augmentation to VisCy instead of
re-implementing them, eliminating the divergence risk (the previous manual
normalization omitted VisCy's divisor epsilon):

- Normalization: apply the config's instantiated NormalizeSampled transforms
  (via Compose) to a per-timepoint sample dict, reading precomputed stats with
  viscy_data.read_norm_meta. Drops _normalization_params and _read_fov_statistics.
- TTA: use AugmentedPredictionVSUNet.with_rotation_tta, which handles non-square
  FOVs natively. Drops the local _predict_volume square-pad workaround.

Depends on the cytoland feat/vs-nonsquare-tta-and-helpers branch
(mehta-lab/VisCy#450).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Picks up mehta-lab/VisCy#450's rename of _read_norm_meta to the public
read_norm_meta (commit eb1ffa1). No biahub source change; the import already
used the public name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Repoints to mehta-lab/VisCy#450 head after its lint fixes (commit 8f42e30).
No functional change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ieivanov and others added 8 commits June 3, 2026 16:57
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arr_version

Add the three optional orchestration knobs to the example config (defaults
shown). output_ome_zarr_version (default None, mirroring DeskewSettings) now
dictates the output store's OME-Zarr version, threaded through _init_output_plate
to resolve_ome_zarr_version; None preserves the input store's version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Timepoints are processed sequentially on a single GPU, so resource needs no
longer depend on dataset size. Use num_cpus=16, gb_ram=4 instead of
estimate_resources.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Base the wall-clock budget on T * Z * 5s (per sliding window) with a 60-minute
floor, assuming median TTA. Measured ~1.4 s/window with TTA, so this gives a
~3-4x margin and accounts for stack depth (number of z-windows), which the
previous T*3 formula ignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
batch_size has no effect in biahub (predict_sliding_windows runs one z-window
at a time); set to 1 with a clarifying comment for parity with viscy predict.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
example_virtual_stain_settings.yml is a viscy predict config validated against
VisCy's classes, not a biahub pydantic Settings model, so it broke
test_all_example_settings_tested's file count. Track it in a separate list,
include it in the expected count, and add a dedicated validation test that
skips when cytoland (the `stain` extra) is not installed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-position SLURM logs previously showed only submitit start/complete lines.
Emit position-prefixed progress: device, checkpoint, TTA status, geometry
(T / Z,Y,X / windows-per-timepoint), per-timepoint timing, and a completion
summary with total elapsed time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ieivanov

ieivanov commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

Ready for review. First tests in /hpc/projects/intracellular_dashboard/refactor_biahub/phase2_dev/mantis_v2_dev/refactor-tests/test_biahub_vs/, we should still do a side-by-side comparison with viscy predict.

@ieivanov

ieivanov commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

Based on rough first speed measurements, Claude says this is 2x faster than viscy predict. Worth looking closer

  ~2.2× faster. This confirms the earlier prediction: the viscy path was ~87% I/O-bound (num_workers=0, re-reading each z-window), whereas biahub
  loads the full ZYX volume once per timepoint and runs all 72 windows on-GPU from memory — eliminating the redundant per-window reads.

@ieivanov

ieivanov commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

FYI @aofei-liu this workflow will significantly simplify how we run virtual staining. For now, we'll do viscy preprocess followed by biahub virtual-stain. Next, I'll try to eliminate the preprocess steps by computing the needed metrics after phase recon, likely optional, on by default.

@ieivanov

ieivanov commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

P.S. When reviewing i'd suggest just looking at the new biahub/virtual_stain.py code, the diff makes no sense since this is a complete rewrite (leveraging @tayllatheodoro predict_sliding_windows function in VisCy)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR rewrites the biahub virtual-stain command to run virtual staining in-process using cytoland/VisCy (via predict_sliding_windows) instead of shelling out to the viscy predict CLI in a separate environment. It also adopts the --cluster {slurm,local,debug} / --init orchestration pattern and adds an example VisCy-compatible predict config.

Changes:

  • Replace the legacy VisCy CLI subprocess workflow with an in-process cytoland-based implementation and per-position GPU job dispatch.
  • Add jsonargparse-based config validation against VisCy’s VSUNet / HCSDataModule, plus a new example virtual-stain config file.
  • Add stain extra dependency plumbing (cytoland source pinning via uv sources) and update example-settings tests to include the new YAML.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
biahub/virtual_stain.py Replaces subprocess-based workflow with cytoland/VisCy in-process prediction, output store init, and submitit-based per-position job dispatch.
biahub/cli/utils.py Extends submitit cluster selection to support explicit cluster= selection (while still forcing debug in CI).
biahub/cli/parsing.py Adds --cluster and --init click parsing decorators used by the new virtual-stain CLI.
tests/test_example_settings.py Ensures the new example YAML is accounted for and validates it via jsonargparse when cytoland is installed.
settings/example_virtual_stain_settings.yml Adds a minimal example VisCy-style predict configuration for virtual staining.
pyproject.toml Adds cytoland to the stain extra and configures an uv git source for cytoland.
uv.lock Locks cytoland + VisCy branch dependencies and related transitive dependency updates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread biahub/virtual_stain.py Outdated
Comment thread biahub/virtual_stain.py Outdated
Comment thread biahub/virtual_stain.py Outdated
@ieivanov

Copy link
Copy Markdown
Collaborator Author

Suggestion on Slack from @mattersoflight:

After thinking about our conversation at lab meeting and brainstorming with claude, I land on adding a tightly scoped viscy.deploy subpackage that uses torch.export that explicitly supports dynamic_shapes for specific arguments. Here is an example:

  • Pick one model (VSCyto2D) and read the nn.Module + state_dict out of its Lightning checkpoint.
  • Write the deployment wrapper: normalize → pad or augment if needed → run inference → crop if needed.
  • Export with torch.export (dynamic H/W) and validate against the viscy predict CLI.

With a verifiable test set of (inputs, predictions) for VSCyto2D and DynaCLR, I think we can have a package. This is preferable over ONNX, because ONNX is its own runtime that can drift from features of core pytorch library.

@ieivanov

Copy link
Copy Markdown
Collaborator Author

I have no strong opinion on this. I asked Claude to compare the two approaches.

What each approach actually is

This PR (#267 + VisCy #450) — "library reuse / in-process." biahub depends on cytoland (modular VisCy) as a library. A viscy predict YAML is validated against VisCy's own VSUNet/HCSDataModule classes via jsonargparse, the Lightning checkpoint's state_dict is loaded into the nn.Module, and all the hard parts are delegated to VisCy: NormalizeSampled + read_norm_meta for normalization, with_rotation_tta for TTA, predict_sliding_windows for the windowed blend. biahub owns only orchestration and keeps no copy of the model schema. A model = (config + checkpoint).

The proposal — "viscy.deploy + torch.export." Produce a self-contained exported artifact per model: a wrapper (normalize → pad/augment → infer → crop) traced with torch.export, dynamic_shapes on H/W, shipped with a golden (inputs, predictions) test set, validated against the viscy predict CLI.

Key framing: #267 changes where orchestration lives; the proposal changes how the model is packaged. They overlap as answers to "how should biahub run VS models," but optimize for different goals — fast iteration inside the VisCy ecosystem vs. portable, frozen, contract-bound distribution.

A subtlety on the ONNX reasoning: "torch.export avoids a second runtime that drifts" is a correct argument for torch.export over ONNX (it keeps real ATen ops, lowers via AOTInductor, tracks PyTorch numerics far more faithfully than the ONNXRuntime op-set). But that's a comparison between two export technologies — it is not an argument against the library-reuse approach in this PR, which has zero drift precisely because it runs the real VisCy code.

Pros and cons

Library reuse (#267) torch.export (viscy.deploy)
Numeric fidelity to viscy predict Exact — it is the same code Close, but a second path to validate/keep in sync
Preprocessing/TTA/sliding-window Reused for free from VisCy Must be re-expressed; the loop + data-dependent windows are the hard part to trace
Adding/switching models Add a YAML + checkpoint, zero code Per-model export + dynamic_shapes spec + golden set + re-export on every retrain
Reproducibility Tied to the installed VisCy version (note the recurring "bump cytoland pin" commits) Tied to the artifact — strong, versioned contract
Portability / env weight Heavy: full Lightning/MONAI/jsonargparse/viscy stack everywhere Light: artifact runs without VisCy installed
Coupling / churn risk Tight — VisCy internal changes can break biahub Decoupled once exported, but the export API itself still matures across PyTorch versions
Engineering cost now Already done, working, ~2.2× speedup Real per-model work, most of it in the parts torch.export traces worst

The honest weak point of the proposal: the difficulty of VS is not the forward pass — it's the sliding-window blend, the per-FOV data-dependent normalization statistics (norm_meta read at runtime), and rotation TTA. Those are Python loops, reductions, and data-dependent shapes — exactly what torch.export handles poorly. You realistically end up either (a) exporting only the per-window forward and keeping the windowing/TTA/normalization glue in Python — which undercuts the "self-contained" claim — or (b) fighting the tracer to bake them in. Normalization stats would also need to become inputs to the exported program rather than baked constants.

Switching between VSCyto2D / VSCyto3D / VSNeuroMast

Library reuse wins decisively for model-switching. Those three differ only in architecture hyperparameters, stack depth, and source/target channels — all in the YAML and validated against VSUNet. Switching or adding a model is a config + checkpoint swap with no code; this PR already handles 2D vs 3D transparently via out_stack_depth.

For torch.export, switching is expensive and brittle in proportion to how the zoo grows:

  • VSCyto2D vs VSCyto3D differ in dimensionality — 3D adds a dynamic Z plus stack-depth constraints, so the dynamic_shapes spec is genuinely different per model, not a parameter tweak.
  • VSNeuroMast has its own stack depth/channel layout → its own wrapper and golden set.
  • Every retrain or architecture tweak forces a re-export + re-validation.

So export is least suited to exactly the multi-model-switching scenario, and most suited to its opposite (a small frozen flagship set distributed widely).

Recommendation

Keep this PR as biahub's production path — for HPC batch inference over plates with an evolving zoo of VS models inside the Biohub Python ecosystem, library reuse is correct and per-model export buys nothing against the real bottleneck. The one cost it carries (the VisCy version pin) is a dependency-hygiene problem, not an architecture problem.

Pursue viscy.deploy/torch.export as a separate productization effort: packaging one or two flagship models (VSCyto2D, DynaCLR) as portable, contract-bound artifacts for external consumers who can't carry the VisCy stack and need numerics frozen across PyTorch upgrades. There the golden (inputs, predictions) test set is the right deliverable and torch.export-over-ONNX is the right call — just scope it honestly: start with the 2D single-window forward + normalization-as-input, prove dynamic-H/W export and golden-set validation end-to-end, and treat sliding-window/TTA as an explicit design decision rather than an afterthought, because that's where the effort actually is.

🤖 Summary generated with Claude Code.

@ieivanov
ieivanov requested a review from mattersoflight June 11, 2026 18:21
@ieivanov

Copy link
Copy Markdown
Collaborator Author

P.S. This PR will be useful right away. I'll merge tomorrow, and then we can refactor again if needed.

@mattersoflight

Copy link
Copy Markdown
Collaborator

It makes sense to use core VisCy to re-use the test-time-augmentation and blending. Please merge.

ieivanov and others added 2 commits June 12, 2026 10:59
…ning

# Conflicts:
#	biahub/cli/utils.py
#	pyproject.toml
#	uv.lock
The feat/vs-nonsquare-tta-and-helpers branch was removed; pin to the
underlying commit 7fb3bc1 (VisCy PR #465) instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ieivanov and others added 4 commits June 12, 2026 11:26
- Remove module-level torch.multiprocessing.set_start_method("spawn",
  force=True): it was a global import-time side effect and is unused. The
  inference path has no DataLoader workers or torch multiprocessing, and
  submitit launches jobs as fresh interpreters (or in-process for debug),
  so the start method never applied.
- Delegate checkpoint loading to VisCy: route the top-level ckpt_path into
  the model's init args so VSUNet.__init__ extracts the Lightning
  checkpoint["state_dict"] itself, instead of biahub assuming that layout.
- Rename the misleading in_stack_depth log variable to z_window_depth and
  document that it mirrors VisCy's predict_sliding_windows (which reads
  model.out_stack_depth as the input Z-window depth); guard the window
  count with max(0, ...).
- Reject cluster='local' with >1 position: local jobs run concurrently
  without a parallelism cap and all target cuda:0, oversubscribing a single
  GPU. Point users to slurm (multi-GPU) or debug (sequential).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- virtual_stain_cli: default monitor to False (matching deskew_cli /
  flat_field_correction_cli; the library functions keep monitor=True) and
  document the SLURM fan-out / --init / --cluster debug usage examples.
- Fix flat_field_correction_cli docstring: it used a raw string, which
  turned click's `\b` no-wrap markers into literal text and rewrapped the
  usage examples. Use a normal docstring with `# noqa: D301`, matching
  deskew. virtual-stain follows the same (correct) pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ieivanov

Copy link
Copy Markdown
Collaborator Author

Speed: biahub virtual-stain vs viscy predict

Benchmarked on mantis v2 data (2026_03_26_A549_CAAX_H2B_DENV_ZIKV, 4 positions, each 5 timepoints, volume (Z,Y,X)=(86,1600,1370)). Both paths do identical work — 360 window-inferences per position (5 timepoints × 72 sliding windows) — with the same checkpoint, fcmae model, fov_statistics normalization, and median TTA. The only variable is the inference path: biahub's in-process predict_sliding_windows vs viscy's DataLoader-driven HCSPredictionWriter.

As-run (⚠️ different GPUs)

biahub virtual-stain viscy predict
GPU H100 / H200 A6000
Time/position ~124 s (predict loop) · ~138 s (job) ~1006 s · ~1038 s

~8× — but not apples-to-apples: biahub set no --constraint and landed on H100/H200; the viscy control was constrained to a100\|a6000\|a40 and landed on A6000.

Same GPU class (A6000 vs A6000)

Comparing a prior biahub A6000 run against the viscy A6000 run (total job elapsed):

biahub virtual-stain (A6000) viscy predict (A6000)
Time/position ~368 s ~960–1070 s

~2.5–2.8× faster on equal hardware.

Bottom line

biahub virtual-stain is ~2.5× faster than viscy predict on equal hardware. It loads the full ZYX volume once per timepoint and runs all 72 windows on-GPU from memory, eliminating viscy's per-window DataLoader round-trips.

Note on GPU constraints: the ~8× wall-clock gap in the as-run numbers came almost entirely from hardware — biahub grabbed H100/H200 because it was unconstrained. We should not pin reconstruction/prediction jobs to a specific (weaker) GPU when a more powerful one is available; leaving them unconstrained lets SLURM schedule the fastest free GPU. (The --constraint on the viscy run was only there to set up a controlled comparison.)

Caveats: the A6000 biahub number is n=1 and from a slightly older revision (pre per-timepoint logging), so it's total job elapsed rather than the isolated predict loop — if anything that understates biahub, since viscy's predict loop (~1006 s) is nearly its full job time.

@ieivanov

Copy link
Copy Markdown
Collaborator Author

Side-by-side comparison here:
biahub virtual-stain: /hpc/projects/intracellular_dashboard/refactor_biahub/phase2_dev/mantis_v2_dev/refactor-tests/test_biahub_vs/
viscy predict: /hpc/projects/intracellular_dashboard/refactor_biahub/phase2_dev/mantis_v2_dev/refactor-tests/test_biahub_vs/viscy_predict/

Other clear wins - biahub virtual-stain writes zarr stores chunked in XYZ. viscy predict chunks in XY only currently

Effect of TTA bug fixed in mehta-lab/VisCy#465

Screenshot 2026-06-12 at 2 42 12 PM

green - biahub virtual-stain nuclei and membrane through predict_sliding_windows coda path
magenta - viscy predict nuclei and membrane without the TTA fix as proposed in mehta-lab/VisCy#451

@edyoshikun It looks like the TTA fix helps reduce artifacts at the edge of the images.

@ieivanov

Copy link
Copy Markdown
Collaborator Author

One more example

Screenshot 2026-06-12 at 2 48 06 PM

@ieivanov
ieivanov merged commit 15684a7 into main Jun 12, 2026
5 checks passed
ieivanov added a commit that referenced this pull request Jun 23, 2026
* feat(nextflow): integrate virtual-stain into mantis-v2 pipeline

Recreate the Nextflow virtual-stain integration against the current
in-process cytoland CLI (post-#267), replacing the #259 approach which
was built on the old `viscy predict` + `--copy` temp-zarr flow.

- New `nextflow/modules/virtual_stain.nf`: init → preprocess → fan-out
  per-position GPU prediction. Per-position work is a single
  `biahub virtual-stain --cluster debug` call (no temp zarr, no --copy).
- `viscy preprocess` runs over the whole input plate to compute the
  normalization statistics the model reads via read_norm_meta.
- Both `biahub virtual-stain` and `viscy` run under biahub's `stain`
  extra (cytoland → viscy-utils provides the `viscy` console script).
- Add a `gpu` process label (queue=gpu) to nextflow.config.
- Wire virtual_stain_wf after reconstruct in mantis-v2.nf.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix biahub virtual-stain mem request

* refactor: unify per-position resource requests across step CLIs

Every step CLI (deskew, flat-field, apply-inv-tf, virtual-stain) now emits
its per-position resource request through a single shared helper,
biahub.cli.utils.echo_resources, as a JSON payload:

    RESOURCES:{"cpus": 16, "mem_gb": 64, "time_min": 480}

The same (cpus, total mem_gb, time_min) values feed both the CLI's own
slurm_* submission args and, via parse_resources in nextflow, the Nextflow
per-position task directives — so the SLURM fan-out and the Nextflow fan-out
can no longer request different resources.

- Add echo_resources() and parse the JSON in common.nf::parse_resources,
  which now returns time_min alongside cpus/mem_gb.
- Emit memory as a TOTAL (mem_gb) and request it via slurm_mem rather than
  slurm_mem_per_cpu, fixing virtual-stain's per-cpu over-request.
- Wire each CLI's slurm_time from time_min; drop the hardcoded `time`
  literals in the run_* processes for `meta.time_min * task.attempt`.
- virtual-stain computes time_min before the init_only return so --init
  emits it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* payload bugfix

* debug viscy predict

* refactor: rename time_min to time_minutes for clarity

Avoids confusion with "minimum time"; renames the resource payload key
and Nextflow meta field consistently across both languages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* update iohub dep

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aofei-liu added a commit that referenced this pull request Jul 9, 2026
Adopt merged-PR decisions: JSON echo_resources protocol (cpus/mem_gb/
time_minutes) migrated across stabilize/track/concatenate/estimate-crop/
nf init-resources; take main's deskew/flat-field/reconstruct/virtual-stain
(#255/#257/#258/#267/#271); nextflow.config preempted-partition hardening
(#275); iohub>=0.3.8 (#272). Remove obsolete virtual-stain CLI test; move
example_pipeline_steps.yml to nextflow/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tayllatheodoro added a commit that referenced this pull request Jul 9, 2026
Bring PR #260 (track --init/--cluster + rename-channels + cellpose) up to date
with main. main had already merged the deskew/flat-field/reconstruct (#255/#257/
#258), cytoland virtual-stain (#267/#271), and stitch-vendor (#238) refactors,
which the refactor-* stack implemented in parallel; those subsystems are
resolved in favour of main.

Conflict resolution:
- Took main's versions of deskew.py, flat_field_correction.py,
  apply_inverse_transfer_function.py, virtual_stain.py (cytoland rewrite,
  supersedes the stack's VisCy --init/--copy), cli/utils.py, and the
  deskew/flat_field/reconstruct/virtual_stain Nextflow modules + tests.
- Took main's nextflow.config (adds the gpu label/queue that tracking.nf's
  run_track needs) and common.nf (JSON resources contract), and mantis-v2.nf.
- pyproject.toml/uv.lock resolved to main (identical after taking iohub>=0.3.8).
- Adopted main's JSON resources contract in track.py: --init now calls
  echo_resources(num_cpus, mem_gb, time_minutes) instead of the old
  space-separated 'RESOURCES:' line, so parse_resources (common.nf) works.
- Preserved PR #260's unique work (tracking.nf, rename_channels.nf, the
  refactored/cellpose track.py, example_track_settings.yml).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants