Skip to content

fix(segmentation): handle empty FOVs + migrate off deprecated skimage min_size=#44

Merged
alxndrkalinin merged 3 commits into
mainfrom
fix/cleanup-segmentation-empty-fov-and-deprecated-min-size
May 29, 2026
Merged

fix(segmentation): handle empty FOVs + migrate off deprecated skimage min_size=#44
alxndrkalinin merged 3 commits into
mainfrom
fix/cleanup-segmentation-empty-fov-and-deprecated-min-size

Conversation

@alxndrkalinin

@alxndrkalinin alxndrkalinin commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

Two unrelated cleanups around cleanup_segmentation:

  1. Empty FOVs no longer crash. check_labeled_binary previously did assert len(unique_values) > 1, \"Image is constant.\" — so an all-zero label mask (a legitimate "no cells detected" FOV) crashed mid-pipeline with AssertionError, and assert is stripped under python -O anyway. Drop that check (downstream steps are no-ops on constant images) and convert the dtype assert to a proper TypeError. The single-label UserWarning stays.

  2. No more min_size= deprecation noise. skimage.morphology.remove_small_objects deprecated min_size= in 0.26 in favor of max_size= (with inverted threshold semantics — removal target is 0.28). cucim 26.02 still uses min_size=. Added 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 (preserves the old "keep size ≥ min_size" semantics), min_size= for cucim and older skimage. Both cleanup_segmentation and the public remove_small_objects wrapper route through it.

Test plan

  • Empty FOV (all-zero label mask) round-trips through cleanup_segmentation with min_obj_size/border_buffer_size and returns unchanged
  • check_labeled_binary(float32) now raises TypeError (was AssertionError)
  • cleanup_segmentation on a real label image with one below-threshold and one above-threshold object emits no FutureWarning and removes the small one
  • GPU path (CuPy input) still works via cucim with min_size=
  • Existing tests/segmentation/test_clear_border.py passes

🤖 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:

  • Allow constant (e.g., all-zero) labeled images to pass through segmentation checks without raising, while still validating integer dtypes with a proper TypeError.

Enhancements:

  • Introduce a backend-aware helper for removing small objects that transparently handles skimage's min_size/max_size API change and routes GPU inputs through cucim.
  • Route segmentation cleanup and the public remove_small_objects helper through the new compatibility function to avoid deprecation warnings and keep behavior consistent across backends.

… 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>
@sourcery-ai

sourcery-ai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Updates segmentation utilities so empty/constant labeled images no longer assert, and introduces a backend-aware helper around skimage/cucim remove_small_objects to avoid the deprecated min_size argument while preserving existing semantics for both CPU and GPU paths.

Sequence diagram for _remove_small_objects CPU/GPU dispatch

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Relax labeled image validation to accept constant/empty FOVs and raise a real TypeError for non-integer inputs.
  • Replace the dtype assert in check_labeled_binary with an explicit TypeError when the image is not integer-typed.
  • Remove the assertion that rejected constant images (e.g., all-zero label masks), allowing empty FOVs to pass silently.
  • Retain the single-label UserWarning when exactly two unique values are present in the label image.
cubic/segmentation/segment_utils.py
Introduce a compatibility wrapper for remove_small_objects to handle skimage 0.26+ max_size semantics and cucim's still-supported min_size, and route existing callers through it.
  • Add _remove_small_objects helper that dispatches based on device (CPU vs GPU) and inspects the skimage remove_small_objects signature to decide between max_size and min_size arguments, mapping min_size to max_size=min_size-1 when needed.
  • Update cleanup_segmentation to use _remove_small_objects instead of morphology.remove_small_objects directly for min_obj_size filtering.
  • Update the public remove_small_objects function to call _remove_small_objects after running check_labeled_binary, returning its result directly.
cubic/segmentation/segment_utils.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot 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.

Hey - I've left some high level feedback:

  • 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.
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.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copilot AI left a comment

Copy link
Copy Markdown

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 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 assert to TypeError and allows constant/empty FOV masks.
  • Adds backend-aware _remove_small_objects compatibility 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.

alxndrkalinin and others added 2 commits May 28, 2026 21:44
…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 alxndrkalinin merged commit 0e37ecd into main May 29, 2026
9 checks passed
@alxndrkalinin alxndrkalinin deleted the fix/cleanup-segmentation-empty-fov-and-deprecated-min-size branch May 29, 2026 04:51
alxndrkalinin added a commit that referenced this pull request Jun 1, 2026
Resets the version after v0.7.0a8/a9/a10 tags were deleted before
publish. Covers PRs #42, #43, #44, #45, and #46 on top of v0.7.0a7.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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