Skip to content

fix(core): make per-tile labels globally unique before merging - #43

Merged
lguerard merged 1 commit into
mainfrom
fix/global-unique-labels
Jun 30, 2026
Merged

fix(core): make per-tile labels globally unique before merging#43
lguerard merged 1 commit into
mainfrom
fix/global-unique-labels

Conversation

@lguerard

Copy link
Copy Markdown
Contributor

The bug (core correctness)

Every tile emits local labels `1..N`, so each tile has a `1`. The merge keyed objects by value, so unrelated objects from different tiles that share a value were fused — multi-object, multi-tile segmentations collapsed (in the reported case, everything to a single label → one flat colour in napari, no selectable instances, empty feature table).

Reproduced: 4 separate objects across 4 tiles → 1 label.

Fix

Add a streaming first pass in `zarr_native_merge` (`_make_globally_unique`): renumber each chunk's labels into a fresh contiguous global range before the boundary stitch. Labels become unique and compact (`max_label == n_objects`), so the relabel LUT stays `O(objects)`, not `O(voxels)`.

  • One chunk in RAM at a time → no OOM (never more than segmentation already loads for that tile).
  • Merge output now matches the staged dtype, so block-encoded ids don't truncate.
  • Fixes both `tile_process` and the per-tile API (`stage_tile`) — they share the merge.

Tests

  • New regression: 4 separate objects across 4 tiles stay 4 labels (contiguous 1..4).
  • Existing cross-boundary single-object test still passes (→ 1).
  • Full suite: 19 passed.

🤖 Generated with Claude Code

Tiles each emit local labels (1..N), so every tile has a "1". The merge
keyed objects by value, fusing unrelated objects from different tiles —
multi-object, multi-tile segmentations collapsed (often to a single
label). This is the package's core promise, so it must always hold.

Add a streaming first pass in zarr_native_merge (_make_globally_unique)
that renumbers each chunk's labels into a fresh contiguous global range
before the boundary stitch. Unique AND compact: max_label == n_objects,
so the relabel LUT stays O(objects), not O(voxels). One chunk in RAM at a
time — no OOM (never more than segmentation already loads per tile). The
merge output now matches the staged dtype so wide ids never truncate.

Fixes both tile_process and the per-tile API (stage_tile). Regression
test added: four separate objects across four tiles stay four labels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lguerard
lguerard merged commit 598ceaf into main Jun 30, 2026
@lguerard
lguerard deleted the fix/global-unique-labels branch June 30, 2026 06:29
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.

1 participant