Optimize mask annotation ROI blending - #2368
Merged
Merged
Conversation
Blend MaskAnnotator overlays only within the touched mask ROI while preserving dense and CompactMask rendering semantics. Add ROI coverage tests for dense masks, CompactMask masks, and all-false masks, and clarify the compact-mask benchmark annotation speedup output. Co-authored-by: Codex <codex@openai.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #2368 +/- ##
=======================================
Coverage 84% 84%
=======================================
Files 70 70
Lines 9834 9887 +53
=======================================
+ Hits 8219 8270 +51
- Misses 1615 1617 +2 🚀 New features to boost your workflow:
|
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request optimizes MaskAnnotator.annotate by limiting blending work (cv2.addWeighted) to the minimal region-of-interest (ROI) that actually contains mask pixels, reducing unnecessary full-frame blending for sparse masks. It also extends the shared mask-painting utility to support painting into a sub-canvas with an absolute origin, and adds tests/benchmark wording to validate and explain the new behavior.
Changes:
- Compute a minimal ROI for dense and
CompactMaskmasks and blend only that ROI inMaskAnnotator.annotate. - Extend
_paint_masks_by_area(...)with acanvas_originparameter so masks can be painted correctly into an ROI-sized canvas. - Add tests asserting blending happens only on the ROI (and is skipped for all-false masks), and clarify benchmark reporting text/labels.
Review scores (n/5):
- Code quality: 5/5
- Testing: 5/5
- Documentation/clarity: 5/5
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/supervision/annotators/core.py |
Adds ROI computation helpers, supports sub-canvas mask painting via canvas_origin, and blends only the ROI in MaskAnnotator. |
tests/annotators/test_core.py |
Adds regression tests verifying ROI-only blending for dense/compact masks and skipping blending for all-false masks. |
examples/compact_mask/benchmark.py |
Updates benchmark docstring/table labels to better attribute annotation speedups to ROI-only blending. |
- fix: use scene_roi.copy() in cv2.addWeighted to avoid non-contiguous stride rounding divergence (±1 uint8 mismatch on column-cropped views) - fix: validate color lookup config before all-false early return via resolve_color pre-check; expose misconfigured annotators at call time - perf: pass detections.xyxy to _masks_to_roi for dense path; converts O(N·H·W) pixel scan to O(N) box union; uses floor(x2)+1 inclusive→ exclusive to match CompactMask.bbox_xyxy + 1 convention - refactor: move _mask_to_roi, _compact_masks_to_roi, _masks_to_roi from annotators/core.py to detection/utils/masks.py (pure geometry helpers) - perf: replace per-detection RLE decode loop in _compact_masks_to_roi with vectorized bbox_xyxy read (no decode required) - docs: add Args/Returns/Notes to all three ROI helpers; document image_shape (height, width) convention and exclusive-bounds semantics; clarify _paint_masks_by_area Returns uses canvas dims not image dims --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- add TestMaskROIHelpers class: 11 parametrized unit tests covering _mask_to_roi (all-false, single-pixel, boundary, full-image, region), _compact_masks_to_roi (empty, single, union coordinates), and _masks_to_roi (empty array, 2D input, dense+xyxy box-union path) - add test_annotate_pixels_outside_roi_unchanged: asserts pixels strictly outside ROI bounds are identical to original scene after annotate - add test_annotate_roi_parity_with_full_frame_blend: verifies masked pixels match manual reference blend within ±1 uint8 rounding tolerance - add TestPaintMasksByArea::test_canvas_origin_nonzero_paints_at_correct_position - update test_annotate_blends_only_dense_mask_roi: switch to two-detection setup with tight inclusive xyxy so blended shape assertion remains valid - update test_annotate_skips_all_false_mask_blend: remove blended_shapes assertion (xyxy now drives a non-None ROI; blend is a no-op since no pixels are painted, scene remains pixel-exact) --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Borda
commented
Jul 1, 2026
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
…t_masks.py Mirrors source location of _mask_to_roi, _compact_masks_to_roi, _masks_to_roi (detection/utils/masks.py). Removes dead try/except import fallback from test_core.py. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…into perf-CMask/2
…alse Previously the xyxy fast-path returned a box-union ROI even when every mask pixel was False (all-empty masks). This caused MaskAnnotator to call cv2.addWeighted on an ROI unnecessarily. Merge the empty-array guard with an any() check so both paths return None early. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Fix test_annotate_skips_all_false_mask_blend to assert cv2.addWeighted is never called (not just that pixels are unchanged) — pixel-equality alone passes even when addWeighted is invoked on identical arrays. Add two cases to TestMaskROIHelpers covering the xyxy fast-path: loose-box (xyxy larger than true pixel region returns box union) and all-false+xyxy (should return None after the fast-path guard fix). --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
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.
This pull request introduces a significant optimization to the
MaskAnnotatorclass, ensuring that mask blending is limited to only the region of interest (ROI) touched by the mask(s), rather than always blending the full image. This change improves annotation speed, especially for large images with sparse masks, and reduces unnecessary computation. The update includes supporting utility functions, test coverage for the new behavior, and improved documentation and reporting in the benchmark script.Mask annotation ROI optimization:
MaskAnnotator.annotateto blend only the minimal bounding rectangle (ROI) covering all true mask pixels, both for dense and compact masks, instead of the entire image. This is achieved by computing the ROI with new helper functions and passing the ROI to the blending operation._paint_masks_by_areafunction to support painting into a subregion of the canvas with a newcanvas_originparameter, aligning mask coordinates with the ROI. [1] [2] [3] [4]Utility functions for ROI calculation:
_mask_to_roi,_compact_masks_to_roi, and_masks_to_roifunctions to compute the minimal bounding box for true mask pixels in dense and compact masks.Testing:
tests/annotators/test_core.pyto verify that blending occurs only within the mask ROI for both dense and compact masks, and that no blending occurs when masks are all-false.Documentation and reporting: