Skip to content

v0.5.0 release - #45

Merged
mcmero merged 58 commits into
mainfrom
dev
Sep 8, 2026
Merged

mcmero merged 58 commits into
mainfrom
dev

Conversation

@mcmero

@mcmero mcmero commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Bump for 0.5.0 release.

Branch protection CI fails due to bug (this can be ignored).

PR checklist

  • This comment contains a description of changes (with reason).
  • If you've fixed a bug or added code that should be tested, add tests!
  • If you've added a new tool - have you followed the pipeline conventions in the contribution docs
  • Make sure your code lints (nf-core pipelines lint).
  • Ensure the test suite passes (nextflow run . -profile test,docker --outdir <OUTDIR>).
  • Check for unexpected warnings in debug mode (nextflow run . -profile debug,test,docker --outdir <OUTDIR>).
  • Usage Documentation in docs/usage.md is updated.
  • Output Documentation in docs/output.md is updated.
  • CHANGELOG.md is updated.
  • README.md is updated (including new tool citations and authors/contributors).

mikemcka and others added 30 commits July 23, 2026 11:36
…f by default (#33)

* Expose resumable polygon extraction in CELLMEASUREMENT

* Default neighbors to 0

* Split resource profiles into public and WEHI heavy-compute sets

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: mcmero <7799828+mcmero@users.noreply.github.com>
* cellmeasurement fix: channel detection for OPAL OME-TIFFs

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: mcmero <7799828+mcmero@users.noreply.github.com>
KRONOSEMBEDDINGS was called three times, once inside each of
mesmer_segment, sopa_segment and cellsam_segment, with the _wbacksub
variants forwarding its emits. That placement makes any cohort-wide
channel operator unsound: a collect() inside mesmer_segment only ever
observes the Mesmer samples, so a statistic computed across the cohort
would silently differ depending on which segmenter produced a sample.

Each segmentation subworkflow now emits kronos_input
([meta, tiff, whole_cell_mask]) and the top-level workflow mixes all six
and invokes KRONOSEMBEDDINGS once. KRONOS is segmenter-agnostic, so this
also removes the same ~30-line block duplicated three times.

Mixing across subworkflows exposed a latent pairing hazard: the module
takes the image/mask and the GeoJSON as two separate channels, which
Nextflow consumes positionally. Both inputs are now derived from a single
channel joined on meta, so a sample's mask can no longer be paired with
another sample's annotations.

Pipeline outputs are unchanged. SEGMENTATIONREPORT now receives the
CELLMEASUREMENT GeoJSON rather than the KRONOS-merged one, which does not
affect the report: its template selects measurement columns by anchored
regex (^(Cell|Nucleus): (Area|Length|...) and : (Mean|Median|Min|Max|
Std.Dev)$), and kronos_emb_* matches neither, with no broad column sweep
anywhere in the template.

Subworkflow emit names changed (kronos_embeddings/kronos_marker_report ->
kronos_input), so nf-test snapshots need regenerating.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All 7 tests across the 6 changed subworkflows were executed and PASSED;
every snapshot here comes from a successful run.

Two independent changes appear in this diff:

1. Caused by this PR: kronos_embeddings/kronos_marker_report are replaced
   by kronos_input, which carries [meta, tiff, whole_cell_mask] rather
   than the previously-empty channels (KRONOS is disabled by default, so
   the old emits always snapshotted as []).

2. NOT caused by this PR: the test.geojson.gz and versions.yml md5s
   change. This is pre-existing drift, reproduced on unmodified dev
   (ef47666) in the same environment, which fails against its own
   committed snapshot with the identical new md5
   (f3d8c2ee -> cf3e776b). The cause is that the cellpose model weights
   are not pinned: the container is pinned to sopa:2.1.11-cellpose, but
   the 1.2GB cpsam checkpoint is downloaded from cellpose.org at runtime,
   so segmentation output drifts as that file changes upstream.

Note cellsam_segment.snap retains an obsolete entry
("cellsam_segment - with background subtraction") from a test that no
longer exists; it predates this branch and is left untouched, though it
is now the last reference to the removed emit names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Swaps the KRONOS1 extractor for KRONOS2 (768-d, marker-aware) and fixes
three normalisation defects carried by the v1 script. Outputs from the two
versions are NOT comparable: both the model and the patch content change.

Behaviour changes
-----------------
* 384-d -> 768-d embeddings.
* Cell isolation: pixels outside the target cell's polygon are zeroed, so an
  embedding describes that cell rather than the ~18 um neighbourhood a 64 px
  box spans at 0.28 um/px. Verified empirically -- isolated vs raw-box
  embeddings differ (L2 mean 9.4, cosine mean 0.973), so this is material.
* The intensity divisor is derived from the image dtype (uint8=255,
  uint16=65535, float=400) rather than hardcoded to 65535, matching KRONOS2's
  own sp_image._scaling_factor. A float32 backsub output was previously
  divided by 65535 instead of 400.
* The samplesheet's nuclear_channel is passed as preferred_dapi. This is
  KRONOS2's ONLY marker-alias mechanism, so DRAQ5/Hoechst slides previously
  received default normalisation stats.
* kronos_batch_size defaults to 16 (was 32): KRONOS2 is fp32 and cuBLAS picks
  batch-dependent kernels, so 16 reproduces the published values.
* The per-cell CSV is gone. It held the same vectors already merged into the
  GeoJSON, which is now the single embedding artefact.

Marker names are now our responsibility
---------------------------------------
KRONOS2 matches names exactly on a separator-insensitive key with NO alias or
fuzzy step (its marker_utils docstring calls this out as deliberate, to avoid
the "CD24 -> cd4" class of mis-normalisation). An unmatched marker is not
dropped -- it is silently normalised with default stats. Against the real
288-marker vocabulary, 45 of a 49-marker CODEX panel matched; the 4 that did
not ("Collagen 4", "Cytokeritin", "DAPI-01", "VISA") are all naming variants
of markers that ARE in the vocabulary.

So kronos_marker_mapping is retained rather than removed, unmatched markers
fail the run by default, and the error points at mapping first, since mapping
onto a vocabulary name preserves the pretrained stats and text embedding
whereas a novel-marker registration would not.

Structure
---------
bin/kronos2_common.py holds everything that is not the model -- channel names,
marker resolution, cell geometry, lazy patch reads, GeoJSON output -- so a
future encoder differs only in preprocess/forward instead of becoming a second
thousand-line script. Two efficiency changes come with it: centroids are
computed analytically from the polygons (v1 rasterised, and wrote to disk, a
full-slide uint32 mask just to locate cells), and pixels are read through a
lazy zarr view so memory scales with the batch rather than the slide.

Also fixed
----------
* The SOPA_PATCHIFYIMAGE stub wrote an empty patch-count file that the caller
  parses with .toInteger(), so every stub run of the Cellpose path died with
  `For input string: ""`.
* KRONOSEMBEDDINGS passed '--nv' unconditionally, which is invalid under
  Docker; the replacement selects by container engine as CELLSAMSEGMENT does.

Validation
----------
Run against the real gated weights on a GPU: 119 cells -> (119, 768), no
NaN/inf, no all-zero rows, no near-identical pairs, and the 210 existing
cellmeasurement measurements preserved.

The container reference is a PLACEHOLDER: the cu124 torch/xformers stack has
no suitable public image and needs a Seqera Wave build, as KRONOS1's did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Committed as 100644 while every other script in bin/ is 100755. Nextflow puts
bin/ on PATH and invokes the script directly, so the task failed with exit 126
(found, not executable) rather than anything diagnosable from its output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The kronos_input emit added in 79c9b5c re-exports the input image. On the
backsub paths that image is produced by the backsub container, which stamps a
fresh UUID into the OME-XML on every run:

    UUID="urn:uuid:f1aad240-8a5a-11f1-b846-7ee323878ed5" Creator="backsub v0.4.1"

Its md5 therefore changes every execution, so the three *_wbacksub snapshots
committed in 4a761f1 can never pass a second time. They appeared to pass
because they were generated and compared within a single --update-snapshot
run, which always agrees with itself; the defect only surfaces on an
independent verification run.

kronos_input is plumbing for the top-level KRONOS2 call and carries no value
the other emits do not already cover, so it is excluded from the snapshot
assertions rather than the emit being reshaped. Each exclusion carries a
comment explaining why, so it does not read as a muted failure.

Snapshots still need regenerating against these assertions, and then verifying
with a second pass that does NOT use --update-snapshot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The plan records the design decisions and the evidence behind them, read from
the model source rather than assumed -- notably that KRONOS2 matches marker
names exactly with no alias or fuzzy step, which is why kronos_marker_mapping
is retained rather than removed.

The examples are referenced from docs/usage.md and are validated against the
real 288-marker vocabulary: every mapping target is in vocabulary, and the
additional_markers.csv rows use legal compartment/family categories and name
markers that genuinely are absent.

.gitignore blocks the KRONOS2 marker vocabulary and anything derived from it.
The model is CC-BY-NC-ND-4.0, so the table is generated locally from the user's
own model directory instead of being redistributed from this public repo -- which
also removes any question of the committed copy drifting from the model in use.

The handoff is a working document; delete it before merging.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Container
---------
Built from the module's environment.yml with the Wave CLI and frozen to the
community registry, so the digest is reproducible:

    java -jar wave.jar --conda-file modules/local/kronos2embeddings/environment.yml \
        --platform linux/amd64 --freeze --await 30m

Wave's anonymous tier publishes to community.wave.seqera.io -- the same
registry the KRONOS1 image used -- so no Seqera account was needed. Verified to
carry torch 2.6.0+cu124 (not a CPU fallback), a working CUDA runtime, and to
reproduce a standalone run's embeddings to cosine 1.000000.

The reference must stay fully qualified: docker.registry='quay.io' in
nextflow.config silently rewrites anything that is not, producing a confusing
401 from quay.

rasterio -> Pillow
------------------
The first Wave build (14ecc418) imported fine standalone but failed under
Nextflow with:

    ImportError: libtiff.so.6: undefined symbol: jpeg12_write_raw_data

a conda libtiff/libjpeg mismatch reached through rasterio. Rather than pin
around it, rasterio and its GDAL stack are dropped: they were pulled in to fill
one small polygon per patch, which Pillow's C polygon filler does natively.
This is the same choice CORAL makes, for the same reason. Image size drops from
10.1GB to 9.78GB.

The two rasterisers do not agree exactly -- boundary-pixel conventions differ,
and at 64px cell patches the boundary is a meaningful fraction of area. The
effect was measured rather than assumed:

  * Pillow vs rasterio embeddings: cosine 0.99974 (min 0.99133)
  * for scale, cell isolation vs raw box: cosine 0.97267
  * rasterio's own all_touched=True vs False: IoU 0.858, i.e. the two
    rasterisers agree MORE closely with each other than rasterio does with
    itself across its two modes

So the shift is a rasterisation-convention detail, roughly 15x smaller than the
isolation decision, not a change of the same class.

Docs
----
Records a trap that costs real time: staging the weights under /opt breaks the
container. Nextflow bind-mounts an input's parent to make it visible, so a host
/opt shadows the container's /opt/conda and Python disappears -- reported as
"/usr/bin/env: 'python3': No such file or directory" from an image that plainly
has python3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes the snapshot fix in 5040fe3, which was incomplete. workflow.out carries
every channel TWICE -- once positionally ("0", "1", ...) and once by name -- so
filtering on `entry.key != 'kronos_input'` removed the named copy while the
positional duplicate kept the unreproducible value. The assertions now keep only
the named emits, which drops the positional duplicates entirely (they were pure
redundancy) along with kronos_input.

Verified properly this time: each snapshot was regenerated and then checked by a
SECOND pass WITHOUT --update-snapshot. That distinction is the whole point --
--update-snapshot compares against what it just wrote, so it always agrees with
itself, which is how the original defect survived.

  mesmer_segment                PASS
  mesmer_segment_wbacksub       PASS
  sopa_segment                  PASS
  sopa_segment_wbacksub         PASS
  cellsam_segment_wbacksub      PASS
  cellsam_segment               1 of 2 tests PASS  (see below)

Known-flaky, NOT caused by this PR
----------------------------------
`cellsam_segment - tiff` still fails an independent verify. The only differing
values are test_nuclear.tiff and the GeoJSON derived from it; the whole-cell mask
is byte-identical. CellSAM's nuclear segmentation is therefore non-deterministic
between runs. Nothing in this PR touches segmentation. Note these snapshots were
generated with a GPU, while CI has none, so CellSAM runs on CPU there and may
well be deterministic.

`BACKGROUNDSUBTRACT`'s own snapshot has the same class of problem and predates
this branch entirely: it stores the md5 of the backsub OME-TIFF, which carries a
fresh UUID per run (Creator="backsub"), so it can never pass twice. Untouched
here; worth its own issue.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both linting failures on this PR were self-inflicted.

prettier: the docs and JSON added here were written directly rather than
through the formatter -- nextflow_schema.json in particular went through a
json.dump(indent=4), which does not match prettier's style. Reformatted with
prettier 3.2.5, the version pinned in .pre-commit-config.yaml.

rocrate_readme_sync: this PR rewrote the README's KRONOS section, so the
descriptions embedded in ro-crate-metadata.json no longer matched. Regenerated
with `nf-core pipelines lint --fix rocrate_readme_sync` rather than hand-edited,
using nf-core 3.3.2 (the version pinned in .nf-core.yml, which is what CI
installs) and without --release, since CI only passes that when the base branch
is master.

ro-crate-metadata.json sits under both tools' jurisdiction and is not in
.prettierignore, so it was checked for a formatting fight: prettier alters one
line of what nf-core writes, and nf-core still reports 315 tests passed
afterwards. They converge, so no ignore rule is needed.

Verified locally: `nf-core pipelines lint` -> 315 passed, 1 module warning
(smoothmasks meta.yml, pre-existing and unrelated).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It was built and verified during the same session; leaving it in "blocked on
you" would send the next reader chasing work that is already done.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
refactor: invoke KRONOS once at top level instead of per segmenter
Swap kronos1 with kronos2 and validate there was improvement over existing implementation.
Built new wave container for kronos2
Cellpose does not error on a --pretrained-model path that is missing: it
falls back to its built-in weights. A typo therefore produced a complete,
plausible, silently wrong run rather than a failure.

Check cellpose_pretrained_model and cellpose_models_dir in
PIPELINE_INITIALISATION, before any compute is scheduled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every patch task downloaded the ~1.2 GB cpsam checkpoint from cellpose.org
itself -- about 18 identical requests for a single sample. That produced
HTTP 429 rate limiting in CI and a truncated download locally
(PytorchStreamReader failed reading zip archive), and left no guarantee
that the patches of one run were segmented with the same weights.

A new CELLPOSEMODEL process fetches them once and emits a directory that
SOPA_SEGMENTATIONCELLPOSE takes as a staged input, so -resume also reuses
them across runs.

It is invoked at the top level rather than in the Cellpose subworkflows
for the same reason KRONOS was hoisted: SOPA_SEGMENT is instantiated twice
(with and without background subtraction) and instantiates
SOPA_SEGMENT_COMPARTMENT twice again (nuclear and whole-cell), so a call
sited any lower would fetch up to four times. Its trigger is the Cellpose
samplesheet reduced to a constant, so Mesmer- and CellSAM-only runs pay
nothing and the resume key does not depend on sample ordering.

The consumer links the staged contents into a task-local directory rather
than pointing CELLPOSE_LOCAL_MODELS_PATH at the shared stage directly: a
download -- of a missing or newly requested model -- would otherwise be a
concurrent write from every patch task into one directory.

Adds --cellpose_models_dir for sites with a pre-staged copy, which skips
the download entirely and is what you want where compute nodes have no
outbound network.

This pins where the weights come from, not which weights: cellpose.org
still serves whatever is current. Only --cellpose_models_dir makes them
reproducible over time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The upstream backsub container stamps each output OME-TIFF with a fresh
UUID (Creator="backsub"), so its md5 differs on every run and the
backgroundsubtract snapshot could never pass twice. The container is not
ours to change, so drop the file from the snapshot and assert it is
produced by name instead -- the same treatment kronos_input already gets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SOPA_CONVERT gained a second input, val(compartment), in b015b6f, but the
setup block of sopa_patchifyimage - zarr was never updated. The test has
been failing since with:

    Process `SOPA_CONVERT` declares 2 input channels but 1 were specified

Nothing caught it because the snapshot suite has never run in CI.

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

The snapshot suite has never run in CI, so it had rotted. This records what
was repaired and, for what was not, exactly what is known and what is not.

Explicitly unresolved:

1. Whether CellSAM is deterministic on CPU. The first attempt at this was
   invalid and is recorded as such: conf/base.config sets accelerator = 1 for
   withLabel:process_gpu, which Nextflow's docker executor expands into
   `--gpus all`, so `-profile test,docker` runs on the GPU even though no
   `--gpus` appears in any config. Those runs showed cellsam_segment - tiff
   byte-identical across two passes, but on GPU, which says nothing about a
   GPU-less CI runner. The CPU re-run was stopped before completing rather
   than left running overnight.

2. Why segmentationreport's HTML md5 drifts. A Quarto render timestamp is the
   obvious candidate but is unverified, so it is written down as a hypothesis
   rather than a cause.

3. Why the module-level cellsamsegment mask md5s drift. Deliberately not
   regenerated: doing so before (1) is settled risks committing a
   GPU-produced mask that CI could never reproduce.

Also records that CellSAM fetches its 1.70 GB weights per task from DeepCell
(41m38s measured; one task hit the 1h limit at 78%). That is long-standing
behaviour rather than a regression, but it has almost no headroom and is a
poor fit for CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eight tests across five files, none of which had passed in this environment.
All were pre-existing: unmodified dev fails them identically.

Two distinct causes.

versions.yml md5s (sopa_convert x2, sopa_resolvecellpose, and
sopa_segmentationcellpose) were recorded in an environment that no longer
matches. The committed value had flip-flopped across commits --
e1cb9bf9 -> 136a272d -> 451efc07 -> 136a272d -- and 451efc07 is the md5 of a
versions.yml where the sopa version rendered empty, so at least one past
regeneration ran somewhere `sopa --version` produced nothing. They are stable
within an environment (three consecutive runs agree), so this is staleness,
not flakiness.

The sopa_segment_compartment snapshots recorded one patch. b015b6f created
them under the 3000 px default; 18c4b81 then set patch_width_pixel = 250 in
the test profile, which yields nine. patches_file_image moved from the md5 of
"1" to the md5 of "9", and the parquets, shapes and masks all moved with it.
sopa_patchifyimage - zarr - stub was likewise stale since PR #35 fixed its
empty patch-count file.

Also drops two obsolete entries from sopa_segmentationcellpose, left behind by
tests that no longer exist.

Every snapshot here was regenerated in one run and then reproduced by a
separate run WITHOUT --update-snapshot. That second pass is the only thing
that proves reproducibility -- --update-snapshot compares against what it just
wrote, so it always agrees with itself, which is how the backsub-UUID defect
survived the first time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The existing CI runs only `nextflow run`, which proves the pipeline completes
but not that it reproduces its outputs -- so the snapshot suite has never
executed automatically, which is exactly why it had rotted.

This adds the workflow, but wired to workflow_dispatch ONLY. Three tests are
still known-red for reasons nobody has diagnosed (segmentationreport, and the
two module-level cellsamsegment tests -- see docs/nf-test-status.md), and
gating merges on those would make the check noise rather than signal. The
header records what to add back to enable it.

It caches the cellpose weights keyed on the sopa container tag and feeds them
in via --cellpose_models_dir, so a run does not re-fetch 1.2 GB. That is
plumbed through a CELLPOSE_MODELS_DIR env hook in tests/nextflow.config, which
is useful on its own: export it locally and the sopa tests stop downloading
the weights per run.

Note the workflow does not yet resolve the GPU question. process_gpu tasks
request an accelerator via conf/base.config, which Nextflow's docker executor
turns into `--gpus all`, and ubuntu-latest has no GPU. That needs establishing
before any test touching CELLSAMSEGMENT or MESMERSEGMENT runs in CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MESMERSEGMENT was named as requesting a GPU. It does not -- it is
process_multi. The only two processes carrying process_gpu are
CELLSAMSEGMENT and KRONOS2EMBEDDINGS.

Also reframes the point. `accelerator = 1` becoming `--gpus all` is intended
behaviour, not a defect: CellSAM and KRONOS2 are meant to run on the GPU and
do. It is relevant to CI only because ubuntu-latest has no GPU, and the
current CI never reaches those processes anyway -- conf/test.config points at
cellpose_samplesheet.csv.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The status doc still described them as pending phase-2 verification. That
passed and they were committed in 41ad892.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nf-core lint failed on it with two actions_nf_test errors: the workflow was
wired to workflow_dispatch only, and that check requires the standard push /
pull_request / release triggers.

Adding it was a mistake for a more basic reason. .nf-core.yml already lists
.github/workflows/nf-test.yml under lint.files_exist -- the project had
already declared that file intentionally absent. There is no way to add it in
a deliberately-inert state and keep lint green, and with three tests still
known-red, wiring it live would gate merges on undiagnosed failures.

The boilerplate is regenerable. What is not obvious is the weights-cache step,
so that is preserved verbatim in docs/nf-test-status.md, along with why the
workflow is absent.

tests/nextflow.config keeps its CELLPOSE_MODELS_DIR hook, which is useful on
its own and trips no lint rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stage the Cellpose weights once per run, fail fast on bad model paths, and repair the nf-test suite
Moves to a Wave-built container carrying sopa 2.2.6 and cellpose 4.2.1.1,
which is the first release with cpsam_v2, cpdino and cpdino-vitb. The
previously pinned sopa:2.1.11-cellpose shipped cellpose 4.0.8, whose only
built-in model was cpsam, so there was nothing to select.

    community.wave.seqera.io/library/python_pip_sopacellpose_cellpose:06f04d6833aa089b
    sopa 2.2.6 | cellpose 4.2.1.1 | torch 2.13.0 (cuda 13.0) | zarr 3.2.1
    MODEL_NAMES: ['cpsam_v2', 'cpdino', 'cpdino-vitb', 'cpsam']

Verified in the image: CUDA is available and the four sopa CLI commands the
pipeline calls all still exist. The `sopa segmentation cellpose` option
surface is byte-identical between 2.1.11 and 2.2.6, so no call sites changed.

cellpose_pretrained_model becomes the model selector and defaults to
cpsam_v2. It takes a built-in name or a path to a custom model; sopa forwards
the value straight to CellposeModel(pretrained_model=...), which accepts
either. The schema lists all four names.

cellpose_model_type is retired and now errors instead of being ignored.
Cellpose >=4.0.1 discards --model-type, and sopa only reads it when
pretrained_model is unset -- which never happens on v4 because sopa defaults
it to 'cpsam'. The old 'cyto3' default has therefore been producing cpsam
results on every run. Failing loudly beats repeating that.

Two follow-ons from the previous PR that would otherwise have broken:

- The startup path check would have rejected a model *name*. It now
  validates as a path only when the value is not a known built-in.
- CELLPOSEMODEL fetched cellpose's default rather than the model in use.
  Under 4.2 those differ (cpsam_v2 vs sopa's cpsam), so it would have staged
  a checkpoint segmentation never asks for and silently left every patch task
  downloading its own. It now takes the model name.

Also enables the GPU: sopa warns that cellpose >=4 "can be slow without a
GPU", and the pipeline never passed --gpu, so segmentation has been running
on CPU throughout. SOPA_SEGMENTATIONCELLPOSE moves to process_gpu.

Snapshots are NOT regenerated here -- results are expected to move, and that
needs measuring rather than blessing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cpdino and cpdino-vitb are in cellpose 4.2.1.1's MODEL_NAMES, so they passed
validation -- and then failed deep in CELLPOSEMODEL:

    File "cellpose/vit.py", line 171, in __init__
      vit_model = dinov3_vitl16 if self.model_name == 'vitl' else dinov3_vitb16
    NameError: name 'dinov3_vitl16' is not defined

They need Meta's `dinov3` package, which cellpose imports in a bare
try/except and only warns about:

    try:
        from dinov3.hub.backbones import dinov3_vitl16, dinov3_vitb16
    except:
        vit_logger.warning("Could not import CPDINO, run `pip install
        git+https://github.com/facebookresearch/dinov3` ...")

So the failure arrives at model construction, after the 1.13 GB checkpoint
has already been fetched. That is the same silently-deferred failure mode this
pipeline has been removing elsewhere, so reject it in
PIPELINE_INITIALISATION with a message naming the missing package.

dinov3 is not on PyPI, installs from git, and carries a bespoke Meta licence
with gated access to the backbone weights, so adding it to the container is a
decision rather than a detail. The guard is a single list -- delete it if the
container ever carries dinov3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rebuilds the Wave container with Meta's dinov3, which cellpose needs for
cpdino and cpdino-vitb, and removes the startup guard added in 64960f2.

    community.wave.seqera.io/library/python_pip_sopacellpose_cellpose:2bb51160896b005b
    sopa 2.2.6 | cellpose 4.2.1.1 | torch 2.13.0 (cuda 13.0) | dinov3

Verified in the image: dinov3.hub.backbones imports, CPDINO constructs
(backbone dino_vitl, 303.4M params) and evaluates, and the four sopa CLI
commands still work.

Only dinov3's architecture code is used. cellpose builds the backbone with
`pretrained=False` and then loads its own checkpoint; Meta's gated
`dinov3_*_pretrain_lvd1689m` weights are only touched by CPDINO.load_pretrained,
which inference never calls. So no Meta access request is needed.

dinov3 is pinned to commit 6876159a. It has no releases and no tags, so
tracking `main` would have reintroduced exactly the unpinned-dependency
problem this pipeline has been removing. It is also not on PyPI and the image
has no git, so it installs from the commit tarball rather than git+https.

CITATIONS.md records the licence obligation: the DINOv3 License requires that
a copy of the Agreement accompany any redistribution, which now includes this
container, and that publications acknowledge the materials.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documents the model table with the measured differences rather than just
naming the options, so the choice can be made on evidence: holding the model
at cpsam across the old and new stacks gives IoU 0.9987, cpsam_v2 gives
0.9552, and cpdino gives 0.9251 while finding 123 cells instead of 119.

The first of those is the load-bearing number -- it says the version, sopa and
device changes are effectively inert, so any movement is attributable to the
model rather than the upgrade.

Also records that cellpose_model_type is gone and why, the GPU requirement,
and that populating a shared cache now needs the model named explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mikemcka and others added 23 commits July 30, 2026 16:17
…and gaussian

Cellpose normalises by rescaling to the 1st and 99th percentiles and does nothing
else -- sharpen_radius and smooth_radius both default to 0, and it contains no
CLAHE code anywhere. Cellpose-SAM was trained on exactly that normalisation, and
its augmentations covered global brightness/contrast jitter plus blur and noise,
but no spatially varying local remapping of the kind CLAHE applies.

sopa layers two stages on top, applied per patch before cellpose sees anything:
gaussian_filter(sigma=1) and equalize_adapthist(clip_limit=0.2). Both are now
off. Exposes cellpose_clip_limit (0), cellpose_gaussian_sigma (0),
cellpose_clahe_kernel_size (null) and cellpose_tile_norm_blocksize (0, cellpose's
own default) in a new "Cellpose preprocessing options" schema group. Restore the
old behaviour with --cellpose_clip_limit 0.2 --cellpose_gaussian_sigma 1.

Both stages were computed from the patch, and CLAHE's kernel is patch.shape // 8,
so the contrast-enhancement scale tracked patch_width_pixel: at COMET's
0.28 um/px, 52 um at 1500 px, 105 um at 3000 and 210 um at 6000 for identical
tissue. Patch overlap does not help -- it resolves cells straddling a boundary,
not cells normalised differently.

Measured on a real COMET image by segmenting the same region as part of a 1500 px
vs a 3000 px patch and matching cells by IoU, the fraction of cell boundaries
surviving a patch-size change (IoU >= 0.90):

  cpsam_v2 (default)   38.6% -> 81.9%
  cpsam                43.2% -> 83.9%
  cpdino               36.8% -> 86.6%

Mean IoU 0.86 -> 0.93. Three architectures, same direction, similar magnitude.

This changes results and they should not be pooled with earlier runs. Median cell
area falls 23-43% for every model, and counts move +8-35% (cpsam_v2), -3-10%
(cpsam), +28-54% (cpdino) -- CLAHE was suppressing detections in the newer models
and inflating them in cpsam. Per-cell intensities are computed over these masks.
cellpose_diameter needs no adjustment: Cellpose-SAM trained across 0.25x-4x
scale, so the smaller cells sit well inside its range.

Verified end to end on the test profile (30 tasks, nine-patch path), with the
flags confirmed reaching model.eval in the pinned sopa 2.2.6. Run on a COMET
brain-panel ROI with all three models and inspected in QuPath -- sensible and
closely comparable (1742 / 1751 / 1648 cells), cellSAM drawing larger boundaries
around membrane signal.

Config only; no sopa or cellpose code changes. The single invocation site is
unaliased, so withName: 'SOPA_SEGMENTATIONCELLPOSE' covers both compartments.

These numbers measure reproducibility, not accuracy -- no annotations were
scored, and the normalisation measurements are DAPI-only, so the whole-cell
compartment is unmeasured. cellSAM also CLAHEs each block and performs well, but
it was trained with CLAHE applied and pins its kernel at a fixed 128 px;
cellpose's weights were not. Applying it to a model that never saw it is the
problem, not CLAHE itself.

Follow-up: nf-test snapshots for the cellpose path need regenerating. CI does not
run nf-test, so this is repo hygiene rather than a merge gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Match Cellpose's preprocessing contract: disable sopa's CLAHE and gaussian
A CI run failed with

    Failed to pull singularity image
    status : 143
    hint   : Try and increase singularity.pullTimeout (current is "20m")

on community.wave.seqera.io/library/tifffile_pandas_typer -- a 230 MB image
that sat in "Copying blob" for 33 minutes before Nextflow SIGTERMed it. The
3.5 GB Cellpose image pulled concurrently and finished in 4.5. Nothing about
the failing PR is involved; it is a registry stall, and CI re-downloads ~6 GB
on every run, so it gets many chances to hit one.

Cache the converted SIFs in the workspace between runs, keyed on the module
files where the container URIs are declared. A container change invalidates
the key; restore-keys means an unrelated module edit still restores the
previous images and only the changed one is pulled. GitHub evicts past 10 GB
per repo, so a miss is today's behaviour rather than a failure. Restored after
the disk cleanup step, which the several GB of SIFs need.

Also raise pullTimeout to 1h on both the singularity and apptainer profiles.
Nextflow's 20m default is sized for the small images most nf-core pipelines
use; ours are 3.5 GB and 5.8 GB compressed with SIF conversion on top, so a
pull making steady progress can legitimately exceed it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ci: stop re-pulling every container image on every run
Every one of the four tuning parameters was described by restating its own
name -- "Flow threshold for cellpose", "Cell probability threshold for
cellpose" -- which tells nobody which way to turn them. Replace them with the
direction of effect and when to reach for each, in both the schema (what
Seqera renders) and the usage docs.

Checked against the pinned stack rather than written from memory:

- cellpose_diameter is only a resize factor. CellposeModel.eval computes
  `rescale = 30. / diameter`, so the default of 30 resamples nothing and does
  not assert your cells are 30 px. Cellpose-SAM trained on 7.5-120 px cells,
  so it is mostly a speed knob, and it filters nothing.
- cellpose_min_area is sopa's, not Cellpose's: "Minimum area (in pixels^2) for
  a cell to be considered as valid", applied per patch before RESOLVECELLPOSE
  merges them. Units are px^2, so it does not track pixel_size_microns -- worth
  saying, since 200 px^2 is 15.7 um^2 only at COMET's 0.28 um/px.
- flow_threshold rejects on shape: higher keeps more masks, and 0 disables the
  check outright (`if flow_threshold > 0` guards it).
- cellprob_threshold moves the boundaries, not just the count, so it shifts
  cell areas and every intensity measured over them. Lower finds more and
  larger cells.

Sources: cellpose 4.2 models.py/dynamics.py and the Cellpose-SAM settings
docs; sopa 2.2.6's own CLI help, read out of the pinned wheel.

No CHANGELOG entry: nothing here changes behaviour, and the file is already a
conflict surface for the two open branches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cellsam_min_area defaulted to 0 -- no size floor at all -- while
cellpose_min_area defaulted to 200, so the same slide was filtered differently
depending only on which segmenter produced it. Default CellSAM to 200 as well.

This changes CellSAM results: objects under 200 px^2 (15.7 um^2 at COMET's
0.28 um/px, about a 4.5 um disc) are now dropped as debris. --cellsam_min_area 0
restores the old behaviour. Cellpose output is untouched.

The two were already compatible enough for one number to mean one thing.
Checked in both implementations rather than assumed:

- units: both pixels^2. sopa/segmentation/stainings.py:44 says so for the
  Cellpose path, and bin/cellsam_segment.py counts label pixels.
- comparison: both keep area >= threshold. sopa does
  `cells[cells.area >= self.min_area]`; cellsam removes `counts < min_area`.
- measure: not identical. cellsam counts pixels; sopa measures the polygon
  from `find_contours(mask, 0.5)` after `buffer(-r).buffer(2r).buffer(-r)`
  smoothing. Replicating that on round and ragged blobs puts the polygon at
  0.93-1.00x the pixel count (median 0.99), so 200 here cuts at an equivalent
  ~202 px on the Cellpose side. Close enough to share a default, and now
  documented so nobody assumes they are exactly interchangeable.
- timing: cellsam filters once after the WSI tiles are merged, sopa per patch
  during segmentation. Safe because patch_overlap_pixel means a cell truncated
  at one patch edge appears whole in its neighbour.

mesmer_min_nuclei_area is deliberately not touched: it filters nuclei rather
than whole cells, and is applied inside the Mesmer container.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewer caught it: I described `cellpose_diameter` as "not a claim about how
big your cells are" and "mostly a speed knob". Both are wrong, and the second
follows from the first.

It is exactly a claim about how big your cells are. Cellpose's own docstring
is "diameters are used to rescale the image to 30 pix cell diameter": the 30
in `rescale = 30. / diameter` is the diameter the model targets, the image is
resized by that factor before the network sees it, and the masks are resized
back after. Declaring 60 presents a 60 px cell to the network at 30 px. The
default of 30 asserting "already at that scale" is why it resamples nothing --
a consequence of the assertion, not evidence the number is inert.

Speed is a side effect of the downsampling, and Cellpose does document it as a
use ("if you input a diameter of 90, then the image will be downsampled by a
factor of 3, which will increase run speed"), but leading with it buried the
actual mechanism. What is true and worth keeping is that Cellpose-SAM's 7.5-120
px tolerance makes the parameter optional for typical data, unlike earlier
Cellpose versions.

Two other corrections from the same docs page while here:

- cellprob_threshold is not a 0-1 probability. The values are inputs to a
  sigmoid centred at zero, so they run from about -6 to +6 and 0 is the
  midpoint. I had left the range out earlier because I could not verify it;
  the settings page states it, so it is now documented -- it is the difference
  between knowing -2 is a reasonable value and guessing.
- flow_threshold now says why the check exists: nothing stops the network
  predicting flows matching no real shape, so Cellpose recomputes each mask's
  implied flow gradients and takes the MSE against the predicted flows.

Source: https://cellpose.readthedocs.io/en/latest/settings.html, and
cellpose 4.2 models.py.

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

Document the Cellpose tuning parameters, and give CellSAM the same minimum cell area
The trailing clause -- "the default of 30 says they already do" -- restated
something the reader is already looking at. In the Seqera form the value 30 is
displayed directly above the description, so the sentence spent its last third
narrating the field above it.

Wording from the reviewer. The fuller explanation of what the default implies
stays in help_text, which is where someone who wants it will look.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A run on Milton died after an hour with

    Failed to pull singularity image
    FATAL: ... write /home/.../.apptainer/cache/oci-tmp/tmp_2881498929:
    disk quota exceeded

An Apptainer pull writes two separate things. Nextflow places the finished
.img; Apptainer itself unpacks the OCI layers it converts from into
$APPTAINER_CACHEDIR, defaulting to ~/.apptainer/cache. The second is the
larger, and a home quota does not hold tens of GB of it.

Add `container_cache_dir`, which sets apptainer.cacheDir and
singularity.cacheDir. That is the half Nextflow controls. The layer cache is
read from the environment of the process that launched Nextflow -- image
pulls run in Nextflow's own JVM, not in a task -- so nothing in
nextflow.config can redirect it. PIPELINE_INITIALISATION therefore checks it:
setting container_cache_dir while the layer cache is still under $HOME is an
error naming the exact exports to run, and using Apptainer with the layer
cache under $HOME warns. Both fire before any download starts, instead of an
hour into one.

Also bind deepcell_cache_dir into the CELLSAMSEGMENT and MESMERSEGMENT
containers. It never was, and autoMounts covers only $HOME, /tmp and the work
directory -- so pointing it at scratch redirected $HOME inside the container
to a path that was not there. The 1.7 GB CellSAM download went to the
container's ephemeral layer and every task repeated it, which is the cost the
parameter exists to avoid, and MESMERSEGMENT's model-download flock file was
container-local and serialised nothing.

Verified against the pipeline: container_cache_dir without APPTAINER_CACHEDIR
errors at startup with the right suggested path; with both on scratch the run
proceeds silently; unset warns and proceeds. The binds were confirmed present
in the rendered container command.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nf-core lint runs `nextflow config -flat`, which uses the strict config parser,
and it rejected the top-level conditional:

    Error nextflow.config:220:1: If statements cannot be mixed with config

Same class of failure as 85cf35b. Use the elvis operator instead, so the value
is an expression rather than a statement.

That changes what lands in the config when the parameter is unset: `cacheDir`
is now present and null rather than absent. That matters, because CI's image
cache from #39 comes from NXF_SINGULARITY_CACHEDIR and a null that shadowed it
would break the cache silently -- nothing would fail, images would just be
re-pulled every run.

It does not shadow it. Verified with a stub `singularity` on PATH, reading back
the directory Nextflow reports in "Pulling Singularity image ... [cache X]":

  param unset, NXF_SINGULARITY_CACHEDIR set  -> env var wins
  param set, env var also set                -> the parameter wins
  neither set                                -> $workDir/singularity, as before

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keep container images and model caches off the home directory
Adds a way to withhold channels that are not markers -- autofluorescence,
blanks, empty cycles -- from KRONOS2, so they contribute nothing to the
embedding. Until now the only escape for an out-of-vocabulary channel was
--allow-novel-defaults, which still *feeds* it to the model normalised with
default statistics: the opposite of what is wanted for a channel that
carries no biology.

This is scoped to the embedding step and deletes nothing. A withheld
channel is simply never read, so it stays in the image, remains available
to segmentation and every other process, and keeps whatever intensity
measurements CELLMEASUREMENT wrote for it in the GeoJSON. Contrast
remove_markers, which does strip channels out of the background-subtracted
image. The log line, the marker report and the docs all say "not shown to
KRONOS2" rather than "removed", so the scope is not mistakable.

Names are comma separated and case sensitive, matched against the image's
own channel names before any kronos_marker_mapping rename. A name matching
nothing fails the run and lists the channels that are present; the nuclear
channel cannot be withheld because it is the model's preferred_dapi.

While wiring this up, channel indices became load-bearing and exposed a
silent correctness bug on OME-TIFFs that give each channel its own 2D
series. ChannelReader sized itself from series[0] alone, so an 8-series
image read as 1 channel while the patch buffer was still sized from the
8-name marker list -- and numpy broadcast that one channel across all
eight. The run completed, reported 8 channels, and produced 768-d vectors
computed from eight copies of DAPI. Verified on the COMET _fixed images.

The layout is now detected once and shared by the name list and the pixel
reader, each channel is read from its own series, read_window refuses to
return fewer channels than were asked of it, and the run fails outright if
the name count and the image's channel count disagree. Channel names are
also built positionally, so an OME Channel carrying neither Name nor ID no
longer shifts every later marker onto the wrong channel.

The marker report gains the embedded/not-shown split and names withheld
channels in place, and is now written before the novel-marker gate can
exit -- a failing run is when it is most useful.

Adds tests/python/test_kronos2_common.py, the first module under the
tests/python scaffold, plus a pytest job in the linting workflow so it
runs, and a stub nf-test for the module, which had none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat: add kronos_exclude_markers, and fix split-series channel reads
parquet_to_tiff.py hardcoded dtype=np.uint16 when rasterising segmentation
geometries. Label IDs are stored as pixel values, so that caps a mask at 65535
distinct cells -- and rasterio.features.rasterize does not raise on overflow,
it silently saturates at the dtype maximum and drops every geometry past it.

The failure is invisible on the crops used in testing and only surfaces on
whole-slide images. On a 370 mm^2 section (~370k cells at 0.4964 um/px) it
truncated to exactly 65535 labels, with the discarded cells collapsing onto
low label IDs. That leaves single labels whose parts are scattered across the
whole slide, whose bounding boxes span the image, and whose geometries are
correspondingly expensive downstream.

Select the narrowest dtype that fits the geometry count instead, and raise
rather than truncate if even uint32 is too narrow. Masks under 65535 cells are
unchanged, so existing outputs and snapshots stay byte-identical.

Diagnostics go to stderr because stdout carries the TIFF.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test_parquet_to_tiff.py exercises label dtype selection, which lives in
bin/parquet_to_tiff.py. That module imports rasterio, shapely, pyarrow and
typer at import time, so the tests cannot reach the function without them and
collection failed with ModuleNotFoundError.

Skipping the module on missing imports would have made CI green while leaving
the truncation bug unguarded, which is the opposite of the point. These are
plain wheels with no GPU or gated weights, so the job stays within its stated
dependency-light intent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Widening the label dtype doubles these masks: on a 36583x40986 slide the
PARQUETTOTIFF output goes from 3.0 GB to 6.0 GB, and SMOOTHMASKS then reads
that and writes another copy. All of it lands on shared scratch, where a
recent run spent six hours on a single 2.87 GB mask write.

Label masks are long runs of a repeated ID, so deflate more than cancels the
widening -- a dense whole-slide mask goes from 2.87 GB to 31 MB, and the
compressed write is faster than the uncompressed one even on local disk.

Verified end to end through the stdout redirect the module uses, since TIFF
writing needs a seekable stream: 70,000 cells round-trip as uint32 with every
label intact, 12x smaller on disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the uint16/uint32 selection (select_label_dtype + LABEL_DTYPES)
with a single fixed uint32 dtype. uint32 indexes 4.29e9 cells, orders of
magnitude past any real slide, so there is no case where narrowing to
uint16 is worth the extra code -- and it removes the sorted-tuple
assumption raised in review. A fail-fast guard still rejects the
implausible >uint32 case rather than truncating silently.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The smoothed mask is the viewer-facing output and some viewers choke on
compressed-strip TIFFs, so drop compression here entirely and write a
plain TIFF. No option is exposed -- this output is meant to be opened, not
squeezed onto scratch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CellSAM segmenter wrote its label mask uncompressed, unlike the
Cellpose path (parquet_to_tiff). Both are intermediates that get smoothed
downstream, so compress this one the same way -- zlib level 1, ~90x on
label masks. Only the final smoothed mask is left uncompressed for
viewers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0.2.2 carries WEHI-SODA-Hub/cellmeasurement-py#7, the STRtree broad phase for
the GeoJSON export overlap pass — the companion to the mask changes here. It
also carries #6, which drops the geometric descriptors from the erosion and
expansion bins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: label masks silently truncate past 65,535 cells
Document the label-mask dtype overflow fix, resource-profile/neighbors
changes and resumable polygon extraction in CHANGELOG.md, which were
missing from Unreleased, and bump the manifest version.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

nf-core pipelines lint overall result: Passed ✅ ⚠️

Posted for pipeline commit 025e926

+| ✅  93 tests passed       |+
#| ❔  29 tests were ignored |#
!| ❗   5 tests had warnings |!
Details

❗ Test warnings:

  • pipeline_todos - TODO string in README.md: Add bibliography of tools and data used in your pipeline
  • pipeline_todos - TODO string in main.nf: Optionally add in-text citation tools to this list.
  • pipeline_todos - TODO string in main.nf: Optionally add bibliographic entries to this list.
  • pipeline_todos - TODO string in main.nf: Only uncomment below if logic in toolCitationText/toolBibliographyText has been filled!
  • pipeline_name_conventions - Naming does not adhere to nf-core conventions: Contains non alphanumeric characters

❔ Tests ignored:

  • files_exist - File is ignored: conf/igenomes.config
  • files_exist - File is ignored: conf/igenomes_ignored.config
  • files_exist - File is ignored: assets/multiqc_config.yml
  • files_exist - File is ignored: conf/igenomes.config
  • files_exist - File is ignored: conf/igenomes_ignored.config
  • files_exist - File is ignored: conf/test.config
  • files_exist - File is ignored: conf/test_full.config
  • files_exist - File is ignored: assets/multiqc_config.yml
  • files_exist - File is ignored: .github/ISSUE_TEMPLATE/config.yml
  • files_exist - File is ignored: .github/workflows/nf-test.yml
  • files_exist - File is ignored: .github/workflows/awstest.yml
  • files_exist - File is ignored: .github/workflows/awsfulltest.yml
  • files_exist - File is ignored: .github/workflows/download_pipeline.yml
  • files_exist - File is ignored: .github/workflows/fix-linting.yml
  • files_exist - File is ignored: .github/workflows/release-announcements.yml
  • files_exist - File is ignored: .github/workflows/template_version_comment.yml
  • files_exist - File is ignored: .github/actions/get-shards/action.yml
  • files_exist - File is ignored: .github/actions/nf-test/action.yml
  • files_exist - File is ignored: .github/actions/
  • files_exist - File is ignored: assets/nf-core-sp_segment_logo_light.png
  • files_exist - File is ignored: docs/images/nf-core-sp_segment_logo_light.png
  • files_exist - File is ignored: docs/images/nf-core-sp_segment_logo_dark.png
  • files_exist - File is ignored: tests/default.nf.test
  • nextflow_config - nextflow_config
  • files_unchanged - Required pipeline config not found - {'manifest.contributors'}
  • actions_nf_test - '.github/workflows/nf-test.yml' not found
  • actions_awstest - 'awstest.yml' workflow not found: /home/runner/work/sp_segment/sp_segment/.github/workflows/awstest.yml
  • schema_params - schema_params
  • multiqc_config - multiqc_config

✅ Tests passed:

Run details

  • nf-core/tools version 3.3.2
  • Run at 2026-09-07 05:12:28

The bash [[ == ]] comparison hardcoded lowercase wehi-soda-hub/sp_segment
while the real repo is WEHI-SODA-Hub/sp_segment. That mismatch failed the
check for every legitimate dev->main PR, even though the surrounding if:
guard (which uses GitHub Actions' case-insensitive ==) let the step run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@WEHI-SODA-Hub WEHI-SODA-Hub deleted a comment from github-actions Bot Sep 7, 2026
@mcmero
mcmero requested a review from mikemcka September 7, 2026 05:35
@mikemcka

mikemcka commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

I have used both the cellsam and cellpose workflows on real data and are working correctly. Trying to decide if its worth adding H&E and IHC functionality for Lachie in this release. I don't think we will worry for now, we can experiment with it in the future.

@mcmero

mcmero commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

I have used both the cellsam and cellpose workflows on real data and are working correctly. Trying to decide if its worth adding H&E and IHC functionality for Lachie in this release. I don't think we will worry for now, we can experiment with it in the future.

Sounds good. I think we don't worry for now. I'm also still working on the AVITI stuff, which will be (hopefully) next release.

@mcmero
mcmero merged commit 7642791 into main Sep 8, 2026
10 of 11 checks passed
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.

2 participants