Skip to content
Merged

Fixes #216

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
f80b261
Register cellposev4 in benchmark run scripts
dariarom94 Jul 19, 2026
1a2fa09
fix anndata version mismatch with txsim
dariarom94 Jul 19, 2026
82add80
add segger to workflow (test)
dariarom94 Jul 19, 2026
53e1728
duplicates when FOV stiching cleaned up
dariarom94 Jul 19, 2026
1186b7a
chunks issue atera
dariarom94 Jul 20, 2026
18644d7
segger update image
dariarom94 Jul 20, 2026
ecb302d
claude fix for segger
dariarom94 Jul 20, 2026
7d66898
Merge branch 'main' into fixes
dariarom94 Jul 20, 2026
d400ebe
atera version fix
dariarom94 Jul 20, 2026
64d7b4e
wf for the custom rnaseq scripts
dariarom94 Jul 20, 2026
3edfbf1
adjust the loader image name
dariarom94 Jul 20, 2026
cbd2f12
adjust the memory
dariarom94 Jul 20, 2026
184260e
troubleshootig edges
dariarom94 Jul 20, 2026
9fa9a33
Merge branch 'main' into fixes
dariarom94 Jul 20, 2026
36631c4
segger update
dariarom94 Jul 21, 2026
0626127
cell type label correction
dariarom94 Jul 21, 2026
3186435
fix boundaries
dariarom94 Jul 21, 2026
d8a7d93
Merge branch 'main' into fixes
dariarom94 Jul 21, 2026
3505718
OOM fixes
dariarom94 Jul 21, 2026
d6e110a
fix code
dariarom94 Jul 21, 2026
4660f26
RCTD
dariarom94 Jul 21, 2026
5abd651
segger to RAPIDS
dariarom94 Jul 21, 2026
fe2e90a
Merge branch 'main' into fixes
dariarom94 Jul 21, 2026
0b23474
fix rctd
dariarom94 Jul 22, 2026
196ff1f
segger debug (torchvision)
dariarom94 Jul 22, 2026
4be7bd4
Merge branch 'main' into fixes
dariarom94 Jul 22, 2026
b8d3d7b
save the xenium version
dariarom94 Jul 22, 2026
202ac49
add atera to datasets
dariarom94 Jul 22, 2026
14be8d0
Add gene efficiency correction as a separate pipeline stage (#183)
dariarom94 Jul 22, 2026
0cf0243
moscot to pca and segger troubleshooting
dariarom94 Jul 22, 2026
d7afb84
added fastreseg
dariarom94 Jul 23, 2026
f87a1d9
segger bug new fix
dariarom94 Jul 23, 2026
123e112
fastreseg to workflow
dariarom94 Jul 23, 2026
7549589
add fastreseg test
dariarom94 Jul 23, 2026
9fa9604
Merge branch 'main' into fixes
dariarom94 Jul 23, 2026
ff04467
optimized fastreseg build
dariarom94 Jul 23, 2026
7ffc514
Merge branch 'main' into fixes
dariarom94 Jul 23, 2026
a7404d8
add s3 paths
dariarom94 Jul 23, 2026
19e5f83
troubleshoot comseg/segger
dariarom94 Jul 24, 2026
aaca151
segger update
dariarom94 Jul 25, 2026
c9bdb91
data loader bug
dariarom94 Jul 25, 2026
1df9834
Merge branch 'main' into fixes
dariarom94 Jul 25, 2026
4467d32
rctd adjustment (raw counts)
dariarom94 Jul 26, 2026
58912e4
fix segger and comseg
dariarom94 Jul 26, 2026
0a5aa99
optimize cosmx
dariarom94 Jul 26, 2026
298e666
Merge branch 'main' into fixes
dariarom94 Jul 26, 2026
c921937
parameter test for cellpose4
dariarom94 Jul 26, 2026
57c2d79
add atera
dariarom94 Jul 26, 2026
cf67e09
add a test in pciseq and dynamic memory for bruker
dariarom94 Jul 27, 2026
9f71692
add test to vizgen data
dariarom94 Jul 28, 2026
d795f33
Merge branch 'main' into fixes
dariarom94 Jul 28, 2026
fefaadc
param sweep
dariarom94 Jul 28, 2026
4dc08d3
add params to segmentation
dariarom94 Jul 28, 2026
e9505f1
adjust segger mem
dariarom94 Jul 29, 2026
3a155be
update fastreseg to tacco
dariarom94 Jul 29, 2026
acdd6c7
Add annotation + expression-correction parameter sweeps (rctd, ssam, …
dariarom94 Jul 30, 2026
fa462b8
Add moscot + split parameter sweeps (annotation, expression correction)
dariarom94 Jul 30, 2026
fd37aee
singler: read par['celltype_key'] instead of hardcoding "cell_type"
dariarom94 Jul 30, 2026
3449bd0
fastreseg
dariarom94 Jul 30, 2026
331d219
Merge branch 'main' into fixes
dariarom94 Jul 30, 2026
5961d0a
adjust labels
dariarom94 Jul 30, 2026
857e16a
bruker nsclc
dariarom94 Jul 31, 2026
54f023e
adjust bruker nsclc loader
dariarom94 Jul 31, 2026
6e8b9ea
setup
dariarom94 Jul 31, 2026
4eec493
Merge branch 'main' into fixes
dariarom94 Jul 31, 2026
44ad7af
method correction
dariarom94 Jul 31, 2026
b351148
Merge branch 'main' into fixes
dariarom94 Jul 31, 2026
2b8177c
pin anndata
dariarom94 Aug 1, 2026
6c16707
mirror nsclc
dariarom94 Aug 1, 2026
5618fb8
sync the vizgen files
dariarom94 Aug 2, 2026
b68c100
adjust mem for allen brain
dariarom94 Aug 2, 2026
98dbc69
claude notes
dariarom94 Aug 2, 2026
fa23294
claude notes
dariarom94 Aug 2, 2026
ac9492c
fix mirror script
dariarom94 Aug 2, 2026
26d292e
Merge branch 'main' into fixes
dariarom94 Aug 2, 2026
1ee371a
adapt fastreseg requirements
dariarom94 Aug 3, 2026
c53016a
merscope kuppe script update
dariarom94 Aug 3, 2026
d479db6
fix nsclc loader
dariarom94 Aug 3, 2026
6c54220
adjust mem for new test resources
dariarom94 Aug 4, 2026
9494fc5
adjust the nsclc loader for test resources
dariarom94 Aug 4, 2026
a459afc
change processor to avoid spatialdata 0.8.0 bug
dariarom94 Aug 4, 2026
8f7b0c1
pin spatialdata version
dariarom94 Aug 4, 2026
f18296b
memory fix
dariarom94 Aug 5, 2026
fc96dcb
Merge branch 'main' into fixes
dariarom94 Aug 5, 2026
e75042f
subsampling code
dariarom94 Aug 5, 2026
594bb25
remove unexisting dataset
dariarom94 Aug 5, 2026
90207aa
fix data extraction bag
dariarom94 Aug 5, 2026
a94ec38
transcript assignment edits
dariarom94 Aug 5, 2026
905b166
segger: simplify transcript-assignment OOB handling to an edge clamp
dariarom94 Aug 5, 2026
b368d5e
modify proseg (param sweep)
dariarom94 Aug 5, 2026
0f57e2f
add scale0
dariarom94 Aug 6, 2026
5f09575
Merge branch 'main' into fixes
dariarom94 Aug 6, 2026
c64f7d2
fix code bug
dariarom94 Aug 6, 2026
7317d57
expand test dataset space
dariarom94 Aug 6, 2026
fe3ad75
Merge branch 'main' into fixes
dariarom94 Aug 6, 2026
a0a3d43
fix stardist params
dariarom94 Aug 6, 2026
6669752
process_dataset: opt-in tissue-centered crop (fixes ABCA whole-brain …
dariarom94 Aug 6, 2026
cc07559
memory adjust
dariarom94 Aug 6, 2026
fcda921
stardist tiling error
dariarom94 Aug 6, 2026
f26d173
Merge branch 'main' into fixes
dariarom94 Aug 6, 2026
4af3df3
fix labels
dariarom94 Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/data_processors/process_dataset/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,61 @@ def crop_shapes_by_global_xy(shapes, x0, x1, y0, y1):
set_transformation(new, trans, set_all=True)
return new

def rasterize_boundaries_to_labels(sdata, shapes_key="cell_boundaries", image_key="image"):
"""Rasterize polygon cell boundaries into a ``cell_labels`` labels element.

Some loaders (e.g. allen_brain_cell_atlas_merfish) provide the vendor
segmentation only as polygon shapes and never rasterize it into a label
image. The ``custom_segmentation`` method hard-requires ``labels["cell_labels"]``,
so synthesize it here from the boundaries when it is absent — done post-crop
(see call site) so only the retained region is rasterized.

Mirrors the vizgen_merscope loader: ``sd.rasterize`` labels regions 1..N
positionally and caps a single pass at 65535 regions, so rasterize in chunks
and offset each chunk's labels past that; then promote to a multiscale pyramid
so downstream ``["scale0"]`` indexing on the copied segmentation works.
"""
import dask.array as da
from spatialdata.models import Labels2DModel

# sd.rasterize(return_regions_as_labels=True) numbers regions 1..n positionally
# into a uint16 array, so a single pass encodes at most 65535 distinct cells.
UINT16_MAX = 65535
img_extent = sd.get_extent(sdata[image_key])
n_cells = len(sdata[shapes_key])
n_iter = n_cells // UINT16_MAX + bool(n_cells % UINT16_MAX)

rasterize_args = {
"min_coordinate": [int(img_extent["x"][0]), int(img_extent["y"][0])],
"max_coordinate": [int(img_extent["x"][1]), int(img_extent["y"][1])],
"target_coordinate_system": "global",
"target_unit_to_pixels": 1,
"return_regions_as_labels": True,
}

if n_iter <= 1:
labels_image = sd.rasterize(sdata[shapes_key], ["x", "y"], **rasterize_args)
else:
combined = None
template = None
for i in range(n_iter):
start = i * UINT16_MAX
end = min((i + 1) * UINT16_MAX, n_cells)
chunk = sd.rasterize(sdata[shapes_key].iloc[start:end], ["x", "y"], **rasterize_args)
chunk_np = np.asarray(chunk.data)
if combined is None:
combined = chunk_np.astype("uint32")
template = chunk
else:
mask = chunk_np > 0
combined[mask] = chunk_np[mask].astype("uint32") + start
labels_image = template.copy(data=da.from_array(combined, chunks=template.data.chunksize))

# rasterize tags the labels with a shape->category map that the Labels model
# does not expect; drop it before parsing (matches the vizgen loader).
labels_image.attrs.pop("label_index_to_category", None)
return Labels2DModel.parse(labels_image, scale_factors=[2, 2, 2, 2])

def rechunk_sdata(sdata, CHUNK_SIZE=1024):
"""Rechunk the sdata to the given chunk size

Expand Down Expand Up @@ -383,6 +438,15 @@ def subsample_adata_group_balanced(adata, group_key, n_samples, seed=0):
else:
sdata_output = sdata

# Synthesize cell_labels from polygon boundaries for loaders that only provide
# shapes (e.g. allen_brain_cell_atlas_merfish). Done here — post-crop — so we
# rasterize only the retained region (rasterizing whole-brain labels would OOM)
# and avoid re-running the expensive stitching loader. custom_segmentation
# requires labels["cell_labels"]; loaders that already provide it are untouched.
if "cell_labels" not in sdata_output.labels and "cell_boundaries" in sdata_output.shapes:
print("No cell_labels found; rasterizing cell_boundaries -> cell_labels", flush=True)
sdata_output["cell_labels"] = rasterize_boundaries_to_labels(sdata_output)

# Rechunk to uniform chunks before writing (NOTE: rechunking currently needed,
# https://github.com/scverse/spatialdata/issues/929). Run unconditionally so
# that uncropped datasets (e.g. 10x Atera, whose store has rectilinear chunk
Expand Down
86 changes: 52 additions & 34 deletions src/methods_segmentation/stardist/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,31 +48,41 @@ detector**: the script feeds it `image[0]` only (see Tier 0 below).
(prob=0.479071, nms=0.3 for that model) as the fallback thresholds.
4. **Percentile normalizer** (`:64-77`) — a csbdeep `Normalizer` subclass that min-max
scales the image to its **1st / 99.8th percentiles** (`normalize_mi_ma`), the
recommended StarDist preprocessing but with fixed percentile bounds. `block_size`
and `context` are derived from the image width so a large panel is processed in
tiles; **`min_overlap` is derived from object size, not block size** (see step 6 and
the min_overlap gotcha below).
recommended StarDist preprocessing but with fixed percentile bounds. The **image
size then selects the segmentation path** (single-pass vs tiled — see step 6 and the
min_overlap gotcha below).
5. **Build eval-params** (`:85-89`) — collects the newly exposed tunables
(`prob_thresh, nms_thresh, scale`) from `par`, **dropping any that are `None`**.
A dropped key ⇒ `predict_instances` uses the model's own optimized value (so the
no-args call is byte-for-byte the pre-tuning behaviour). Mirrors the cellposev4
eval-params pattern.
6. **Segment** (`:92-131`) — `model.predict_instances_big(image[0], axes='YX',
block_size=…, min_overlap=…, context=…, normalizer=…, **eval_params)`.
`predict_instances_big` splits the image into `block_size` blocks, calls
`predict_instances` on each (forwarding `**eval_params` unchanged — it only
overrides `axes/overlap_label/return_labels/return_predict`), and reassembles the
labels into global coordinates. `image[0]` = first channel → a single 2D plane.
**The stitching invariant is that every predicted object is smaller than
`min_overlap`** (an object bigger than the overlap can span a block seam and can't be
uniquely assigned → `RuntimeError: ...violates the assumption of being smaller than
'min_overlap'`). So `min_overlap` is set from an **object-size** bound
(`max_object_diameter`, default **192 px**), *not* from `block_size` (the old
`block_size // 5.5` shrank it to 64 px on small panels while real blobs reached
~110 px → crash). `block_size` is then grown if needed to satisfy
`min_overlap + 2*context < block_size`, and the call is wrapped in a **retry that
doubles `min_overlap` on that specific error** so a rare oversized blob self-heals
instead of failing the run.
6. **Segment** (`:91-141`) — **two paths chosen by image size** (`image[0]` = first
channel → a single 2D plane):
- **Fits (largest side ≤ `BIG_PX`=4096) → `model.predict_instances(...)`.** One pass,
**no block stitching**, so there is *no* `min_overlap`/block-geometry constraint and
objects of any size are fine. `n_tiles` only sub-tiles the **forward pass** to bound
GPU memory (that tiling has its own automatic context and no overlap requirement).
This is the path all current benchmark panels take.
- **Large whole-slide (largest side > 4096) → `model.predict_instances_big(...)`.**
Tiles into `block_size` blocks and stitches, under **two** constraints: (1) a
*stitching* invariant that every object be smaller than `min_overlap` (else
`RuntimeError: ...violates the assumption of being smaller than 'min_overlap'`), and
(2) a *block-geometry* one — per-axis stride is `size − (min_overlap + 2·context)`
and stardist's `Block.cover` **asserts** consecutive write-regions overlap by ≥
`min_overlap`, which requires `block_size` **comfortably** larger than
`min_overlap + 2·context`. Here `block_size = BIG_PX = 4096 ≫ min_overlap(192) +
2·context`, so the geometry holds. `min_overlap` is **object-size-based**
(`max_object_diameter`, default 192 px, measured in original px — `predict_instances`
undoes `scale`), and the call is wrapped in a **retry that doubles `min_overlap`**
on that `RuntimeError` so a rare oversized blob self-heals.

Why the split: the old code *always* used `predict_instances_big` with
`block_size = image.shape[1] // 3` (~336 px on a ~1000 px panel), which (a) forced
tiling even on tiny images and (b) left only a ~16 px margin over `min_overlap +
2·context` → `Block.cover` `AssertionError`. A too-small `min_overlap` (`block_size //
5.5` = 64 px) had earlier caused the *stitching* `RuntimeError` on a 110 px blob. Both
bug classes only exist on the tiled path; single-pass `predict_instances` sidesteps
them entirely.
7. **Post-process** (`:100-104`) — `convert_to_lower_dtype` downcasts the label array
to the smallest uint that holds `max label`; wrap as an `xarray.DataArray`,
`Labels2DModel.parse` with the copied transform, store as
Expand Down Expand Up @@ -129,10 +139,11 @@ wrong for a different `--model` whose optimized thresholds differ. **They need
`viash ns build` + a container rebuild to take effect** (see `check-component`).

`max_object_diameter` is a **geometry/robustness knob, not a quality knob** — it only
sizes `predict_instances_big`'s `min_overlap`; it does not change which pixels get
segmented. It is **not part of the quality sweep**; leave it at the 192 px default
unless you hit the min_overlap `RuntimeError` (the script also auto-doubles it), or your
nuclei are unusually large.
sizes `min_overlap` on the **tiled (>4096 px) path**; on the single-pass path it is
unused, and it never changes which pixels get segmented. It is **not part of the quality
sweep**; leave it at the 192 px default unless a large whole-slide image hits the
min_overlap `RuntimeError` (the script also auto-doubles it) or its nuclei are unusually
large.

Not exposed:
- `--n_tiles` — a pure GPU-memory tiling knob. `predict_instances_big` **already** tiles
Expand Down Expand Up @@ -219,16 +230,23 @@ That is exactly the sweep encoded in `scripts/run_benchmark/stardist_params.yaml
`viash ns build` + a container rebuild; a stale image silently ignores them. The sweep
has **not yet been run end-to-end** with the new args — validated only by
`viash config view` + a script `ast.parse`.
- **`min_overlap` must exceed the largest object.** `predict_instances_big`'s block
stitching asserts every object is smaller than `min_overlap`; a bigger object throws
`RuntimeError: ...violates the assumption of being smaller than 'min_overlap'`. The old
code tied it to `block_size // 5.5`, so on small/narrow panels it fell to 64 px while
real blobs reached ~110 px → crash. Now `min_overlap` is **object-size-based**
(`max_object_diameter`, default 192 px), `block_size` is grown to keep
`min_overlap + 2*context < block_size`, and a **retry doubles `min_overlap`** on that
error. `scale` is forwarded per-block via `**kwargs` but objects are measured in
original pixels (predict_instances undoes `scale`), so `min_overlap` is in original px
regardless of `scale`; still sanity-check masks at block seams for extreme `scale`.
- **The `predict_instances_big` tiling has TWO independent failure modes — which is why
small images now bypass it entirely** (single-pass `predict_instances`, see step 6):
1. *Stitching* — `RuntimeError: ...violates the assumption of being smaller than
'min_overlap'` when an object is bigger than `min_overlap`. The old
`min_overlap = block_size // 5.5` fell to 64 px on small panels while blobs reached
~110 px.
2. *Block geometry* — `AssertionError` in `Block.cover` (per-axis
`stride = size − (min_overlap + 2·context)`; consecutive write-regions must overlap
by ≥ `min_overlap`). Fires when `block_size` is only *marginally* above
`min_overlap + 2·context`. Over-correcting fix #1 to `min_overlap=192` with
`block_size = image//3 ≈ 336` left a 16 px margin → this assertion tripped.

On the surviving tiled path both are avoided by construction: `block_size = 4096 ≫
min_overlap(192) + 2·context`, and the retry doubles `min_overlap` for a rare huge
object. `scale` is forwarded per-block but objects are measured in original pixels
(`predict_instances` undoes `scale`), so `min_overlap` is scale-independent; still
sanity-check masks at block seams for extreme `scale`.
- **Only channel 0 is segmented** (`image[0]`). Fine for single-channel iST morphology;
StarDist2D has no multi-channel mode anyway.
- **Whole image loaded into RAM** (`:53`) — full-res plane; big panels are why the label
Expand Down
103 changes: 62 additions & 41 deletions src/methods_segmentation/stardist/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def do_after(self):
mi, ma = np.percentile(image, [1,99.8])
normalizer = MyNormalizer(mi, ma)

# Tunable knobs forwarded through predict_instances_big -> predict_instances.
# Tunable knobs forwarded to predict_instances / predict_instances_big.
# A value left as None (i.e. omitted from par) means "use the model's own optimized
# value": thresholds.json for prob_thresh/nms_thresh, no rescaling for scale. This
# keeps the default (no-args) call identical to the pre-tuning behaviour.
Expand All @@ -86,46 +86,67 @@ def do_after(self):
for k in ("prob_thresh", "nms_thresh", "scale")
if par.get(k) is not None
}
print(f"predict_instances_big overrides: {eval_params}", flush=True)

# predict_instances_big tiles the image and stitches the per-block predictions. Its
# stitching invariant is that EVERY predicted object is smaller than `min_overlap`;
# an object spanning a block seam that is larger than the overlap can't be assigned to
# a single block, which raises "Found object of shape (...), which violates the
# assumption of being smaller than 'min_overlap'". So `min_overlap` must be
# OBJECT-SIZE-based, not block-size-based — the old `block_size // 5.5` shrank the
# overlap below real nuclei/blobs on small panels (min_overlap fell to 64 px while
# objects reached ~110 px). Objects are measured in ORIGINAL image pixels
# (predict_instances undoes `scale` internally), so this bound is in original px and is
# independent of `scale`. `context` is only the receptive-field margin discarded around
# each block, so deriving it from the image size is fine.
block_size = min(image.shape[1] // 3, 4096)
context = int(min(block_size // 5.5, 128))
min_overlap = int(par.get("max_object_diameter") or 192) # px; must exceed largest object
# predict_instances_big asserts: min_overlap + 2*context < block_size.
block_size = max(block_size, min_overlap + 2 * context + 1)

# Self-heal: if a rare oversized blob (merged nuclei / debris) still exceeds
# `min_overlap`, double it (and grow block_size to keep the geometry constraint) and
# retry, rather than failing the whole segmentation.
while True:
try:
labels, _ = model.predict_instances_big(
image[0, :, :], axes='YX', block_size=block_size,
min_overlap=min_overlap, context=context,
normalizer=normalizer, **eval_params, # n_tiles left to block_size
)
break
except RuntimeError as e:
if "min_overlap" not in str(e) or min_overlap >= 2048:
raise
min_overlap *= 2
block_size = max(block_size, min_overlap + 2 * context + 1)
print(
"predict_instances_big: an object exceeded min_overlap; retrying with "
f"min_overlap={min_overlap}, block_size={block_size}",
flush=True,
)
print(f"stardist overrides: {eval_params}", flush=True)

# Segmentation strategy — two paths, chosen by image size:
#
# * predict_instances_big TILES the image and stitches the per-block predictions
# under TWO strict constraints. (1) a *stitching* invariant that every object be
# smaller than `min_overlap`; and (2) a *block-geometry* one, since the per-axis
# stride is `size - (min_overlap + 2*context)` and stardist's `Block.cover`
# asserts consecutive blocks' write-regions overlap by >= min_overlap — which
# needs `block_size` to be *comfortably* larger than `min_overlap + 2*context`,
# not just larger (the old `block_size = image.shape[1] // 3` made ~336 px blocks
# on a ~1000 px panel, leaving a 16 px margin, so Block.cover's assertion failed).
# * predict_instances processes the whole image in ONE pass: no block stitching, so
# NEITHER constraint exists and objects of any size are fine. `n_tiles` only
# sub-tiles the forward pass to bound GPU memory (that tiling has its own automatic
# context and no min_overlap requirement).
#
# So: if the image fits (largest side <= BIG_PX) use the constraint-free single-pass
# `predict_instances`; only genuinely large whole-slide images take the tiled path,
# where block_size=BIG_PX >> min_overlap+2*context keeps Block.cover's geometry valid.
# `min_overlap` (the tiled path only) is OBJECT-SIZE-based: it must exceed the largest
# object, measured in ORIGINAL image pixels (predict_instances undoes `scale`), so it is
# independent of `scale`.
def _n_tiles(px):
# Forward-pass tiling to bound GPU memory (~2048 px/tile); no stitching constraint.
n = max(1, int(px) // 2048)
return (n, n)

BIG_PX = 4096
max_dim = int(max(image.shape[1], image.shape[2]))

if max_dim <= BIG_PX:
labels, _ = model.predict_instances(
image[0, :, :], axes='YX', normalizer=normalizer,
n_tiles=_n_tiles(max_dim), **eval_params,
)
else:
# Large image -> tile + stitch. Self-heal: if a rare oversized blob (merged nuclei /
# debris) exceeds `min_overlap`, double it (and grow block_size to keep the geometry
# constraint) and retry, rather than failing the whole segmentation.
block_size = BIG_PX
min_overlap = int(par.get("max_object_diameter") or 192) # px; must exceed largest object
context = int(min(min_overlap, 128))
while True:
try:
labels, _ = model.predict_instances_big(
image[0, :, :], axes='YX', block_size=block_size,
min_overlap=min_overlap, context=context, n_tiles=_n_tiles(block_size),
normalizer=normalizer, **eval_params,
)
break
except RuntimeError as e:
if "min_overlap" not in str(e) or min_overlap >= 2048:
raise
min_overlap *= 2
block_size = max(block_size, min_overlap + 2 * context + 256)
print(
"predict_instances_big: an object exceeded min_overlap; retrying with "
f"min_overlap={min_overlap}, block_size={block_size}",
flush=True,
)



Expand Down
Loading