fix(segmentation): handle empty FOVs + migrate off deprecated skimage min_size=#44
Merged
alxndrkalinin merged 3 commits intoMay 29, 2026
Conversation
… min_size= Two unrelated cleanups around `cleanup_segmentation`: 1. `check_labeled_binary` previously `assert`-ed `len(unique_values) > 1`, so passing an all-zero label mask (a legitimate "no cells detected" FOV) crashed mid-pipeline with AssertionError. Asserts are also stripped under `python -O`. Drop that check (downstream steps are no-ops on constant images) and convert the dtype assert to a proper `TypeError`. Keep the single-label warning. 2. `morphology.remove_small_objects(..., min_size=)` emits a FutureWarning in skimage 0.26+ (the kwarg was renamed to `max_size` with inverted threshold semantics, slated for removal in 0.28). cucim 26.02 still uses `min_size=`. Add a tiny `_remove_small_objects` compat helper that bypasses the cubic.skimage proxy for this one call and dispatches per-device with the right kwarg — `max_size=min_size - 1` for skimage ≥0.26, `min_size=` for cucim and older skimage. Preserves "keep size ≥ min_size" semantics across both backends. Verified end-to-end on CPU (empty FOV no longer crashes; bad-dtype now raises TypeError; no FutureWarning leaks through) and GPU (cucim path still routes through `min_size=`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Reviewer's GuideUpdates segmentation utilities so empty/constant labeled images no longer assert, and introduces a backend-aware helper around skimage/cucim Sequence diagram for _remove_small_objects CPU/GPU dispatchsequenceDiagram
participant Caller
participant _remove_small_objects
participant get_device
participant cucim_remove_small_objects
participant skimage_remove_small_objects
participant inspect
Caller->>_remove_small_objects: _remove_small_objects(label_img, min_size)
_remove_small_objects->>get_device: get_device(label_img)
get_device-->>_remove_small_objects: device
alt device is GPU
_remove_small_objects->>cucim_remove_small_objects: remove_small_objects(label_img, min_size=min_size)
cucim_remove_small_objects-->>_remove_small_objects: label_img
else device is CPU
_remove_small_objects->>inspect: inspect.signature(skimage_remove_small_objects)
inspect-->>_remove_small_objects: signature
alt max_size in signature.parameters
_remove_small_objects->>skimage_remove_small_objects: remove_small_objects(label_img, max_size=min_size - 1)
else
_remove_small_objects->>skimage_remove_small_objects: remove_small_objects(label_img, min_size=min_size)
end
skimage_remove_small_objects-->>_remove_small_objects: label_img
end
_remove_small_objects-->>Caller: label_img
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
_remove_small_objects, callinginspect.signatureon every invocation adds avoidable overhead in hot paths; consider feature-detectingmax_sizevsmin_sizeonce at import time and reusing that result. _remove_small_objectstranslatesmin_sizetomax_size=min_size - 1without handling edge cases likemin_size <= 0; it may be safer to explicitly guard or short-circuit for non-positive values to preserve prior behavior and avoid surprising arguments to skimage.- The
_remove_small_objectsandcleanup_segmentationtype hints usenp.ndarraywhile the code explicitly supports CuPy arrays (GPU path), so widening the annotations (e.g., toArrayLikeor a union) would better reflect the accepted input types and avoid confusion for callers and type checkers.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_remove_small_objects`, calling `inspect.signature` on every invocation adds avoidable overhead in hot paths; consider feature-detecting `max_size` vs `min_size` once at import time and reusing that result.
- `_remove_small_objects` translates `min_size` to `max_size=min_size - 1` without handling edge cases like `min_size <= 0`; it may be safer to explicitly guard or short-circuit for non-positive values to preserve prior behavior and avoid surprising arguments to skimage.
- The `_remove_small_objects` and `cleanup_segmentation` type hints use `np.ndarray` while the code explicitly supports CuPy arrays (GPU path), so widening the annotations (e.g., to `ArrayLike` or a union) would better reflect the accepted input types and avoid confusion for callers and type checkers.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
This PR updates segmentation cleanup utilities to tolerate empty labeled masks and avoid small-object removal deprecation warnings across CPU/GPU backends.
Changes:
- Converts labeled-image dtype validation from
asserttoTypeErrorand allows constant/empty FOV masks. - Adds backend-aware
_remove_small_objectscompatibility helper for skimage/cuCIM API differences. - Routes cleanup and public small-object removal through the new helper.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…mall_holes helper Two follow-ups from PR review: 1. **Hoist inspect.signature to import time** (Sourcery): the skimage `max_size`-vs-`min_size` feature detection now happens once at module load as `_SKIMAGE_USES_MAX_SIZE`, instead of on every CPU call of `_remove_small_objects`. No per-call inspect cost. 2. **Cover `remove_small_holes` too** (code-review gap): skimage 0.26 deprecated `area_threshold` → `max_size` on `remove_small_holes` with the same pattern (cucim 26.02 still uses `area_threshold`). Add `_remove_small_holes` helper following the same per-device dispatch and wire it into the two existing callsites (`cleanup_segmentation` and `fill_holes_slicer`). Unlike `remove_small_objects`, both old `area_threshold=N` and new `max_size=N` keep identical semantics (no off-by-one). Verified end-to-end on CPU + GPU with FutureWarning set to error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…wargs CI's mypy was running against a skimage version that doesn't expose `max_size` on `remove_small_objects` / `remove_small_holes`, so the runtime-feature-detected branches (guarded by `_SKIMAGE_USES_MAX_SIZE`) were flagged as `[call-arg]` errors. Add narrow `# type: ignore[call-arg]` on just those two call sites — the legacy branches still type-check against the older API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
alxndrkalinin
added a commit
that referenced
this pull request
Jun 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two unrelated cleanups around
cleanup_segmentation:Empty FOVs no longer crash.
check_labeled_binarypreviously didassert len(unique_values) > 1, \"Image is constant.\"— so an all-zero label mask (a legitimate "no cells detected" FOV) crashed mid-pipeline withAssertionError, andassertis stripped underpython -Oanyway. Drop that check (downstream steps are no-ops on constant images) and convert the dtypeassertto a properTypeError. The single-labelUserWarningstays.No more
min_size=deprecation noise.skimage.morphology.remove_small_objectsdeprecatedmin_size=in 0.26 in favor ofmax_size=(with inverted threshold semantics — removal target is 0.28). cucim 26.02 still usesmin_size=. Added a tiny_remove_small_objectscompat helper that bypasses thecubic.skimageproxy for this one call and dispatches per device with the right kwarg:max_size=min_size - 1for skimage ≥0.26 (preserves the old "keep size ≥ min_size" semantics),min_size=for cucim and older skimage. Bothcleanup_segmentationand the publicremove_small_objectswrapper route through it.Test plan
cleanup_segmentationwithmin_obj_size/border_buffer_sizeand returns unchangedcheck_labeled_binary(float32)now raisesTypeError(wasAssertionError)cleanup_segmentationon a real label image with one below-threshold and one above-threshold object emits noFutureWarningand removes the small onemin_size=tests/segmentation/test_clear_border.pypasses🤖 Generated with Claude Code
Summary by Sourcery
Handle empty field-of-view label masks without errors and adapt small-object removal to API changes in skimage and cucim.
Bug Fixes:
Enhancements: