Skip to content

fix(annotators): exclude empty compact masks from the mask blend region - #2741

Merged
Borda merged 4 commits into
roboflow:developfrom
jorge-asenjo:fix/skip-empty-compact-mask-roi
Oct 16, 2026
Merged

Borda merged 4 commits into
roboflow:developfrom
jorge-asenjo:fix/skip-empty-compact-mask-roi

Conversation

@jorge-asenjo

@jorge-asenjo jorge-asenjo commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Description

MaskAnnotator only copies and blends the region covered by masks (#2368). An all-False mask still widened that region to its box:

  • CompactMask: the region is the union of crop boxes, and an empty crop still contributes its box.
  • Dense masks: MaskAnnotator passes xyxy, and the box fast path takes the union of every box, including boxes of all-False masks.
Masks (80×60 scene) Blended region on develop (dense / compact) With this PR (dense / compact)
One all-False full-frame mask none / full frame none / none
That mask + a 5×5 mask full frame / full frame 5×5 / 5×5

Painted pixels are the same in both versions.

This PR drops the boxes of masks without foreground before taking the union: CompactMask.area > 0 (counted from the RLE, no decoding) for compact masks, and masks.any(axis=(1, 2)) for dense masks. When no compact crop remains, the helper returns None, so the annotator's existing early return applies, as it already did for dense masks.

from unittest.mock import patch
import numpy as np
import supervision as sv
from supervision import _cv2

masks = np.zeros((2, 60, 80), dtype=bool)
masks[1, 40:45, 60:65] = True
boxes = np.array([[0, 0, 79, 59], [60, 40, 64, 44]], dtype=np.float32)
annotator = sv.MaskAnnotator(color=sv.Color.RED, color_lookup=sv.ColorLookup.INDEX)
for mask in (masks, sv.CompactMask.from_dense(masks, boxes, image_shape=(60, 80))):
    detections = sv.Detections(xyxy=boxes, mask=mask)
    with patch.object(_cv2, "addWeighted", wraps=_cv2.addWeighted) as blend:
        annotator.annotate(np.zeros((60, 80, 3), dtype=np.uint8), detections)
    print(type(mask).__name__, [call.args[0].shape for call in blend.call_args_list])
# develop: ndarray [(60, 80, 3)], CompactMask [(60, 80, 3)]
# this PR: ndarray [(5, 5, 3)],   CompactMask [(5, 5, 3)]

Motivation and Context

Completes the empty-mask handling from #2368: it covered collections with no foreground at all, but not empty masks next to non-empty ones. No issue was opened for this case.

Changes Made

  • _compact_masks_to_roi: union only crops that have foreground.
  • _masks_to_roi: in the dense box fast path, drop boxes of all-False masks before the union. Docstrings updated for both helpers.
  • Tests: an all-False compact collection has no ROI; an empty mask in either position does not widen the compact or the dense ROI (compact crops are not decoded); the annotator tests for the skipped blend and for the blended region size, painted pixels and in-place return are parametrized over dense and compact masks.
  • Changelog entry under Unreleased.

Out of scope: vectorizing CompactMask.area (see notes).

Testing

Windows x64, Python 3.11.9, locked environment (uv sync --frozen), develop at 6ecb279.

  • New and parametrized tests on develop: 8 failed, 1 passed (the pass is the dense all-False case, which already worked), with both the NumPy fallback and OpenCV 4.12.0. With this PR: 9 passed.

  • pytest tests/detection/utils/test_masks.py tests/annotators/test_core.py src/supervision/detection/utils/masks.py: 278 passed on both backends.

  • Full suite, pytest --cov=supervision: 5,702 passed, 105 skipped, 94% coverage.

  • pre-commit run on the changed files: all hooks pass.

  • Added/updated tests, and the full suite passes locally

  • Updated docs (docstrings / mkdocs entry) for new or changed public API

  • Added a changelog entry in docs/changelog.md under Unreleased (skip for lint/type/format-only or pure doc changes)

Additional Notes

Cost, measured at 1280×720:

  • Compact: CompactMask.area loops over masks in Python, so the ROI helper goes from ~10 µs to roughly 3 µs per mask (≈0.3 ms at 100 masks, ≈1.5 ms at 500, OpenCV). Without empty masks, annotate() timings stayed within run-to-run noise on my machine (500 masks: 28–31 ms on both branches).
  • Dense: the extra any reduction costs ~0.1 ms at 10 masks and ~1.9 ms at 100, against ~0.5 / 7.6 ms for the existing dense helper and ~4 / 49 ms for the area sum that _paint_masks_by_area already computes.
  • With five small compact masks plus one empty full-frame crop, annotate() went from ~6.5 ms to ~2.4 ms with OpenCV and from ~67 ms to ~3.5 ms with the NumPy fallback.

Happy to share the benchmark script.

If the per-mask cost matters, CompactMask.area can be vectorized (concatenate the RLE arrays and sum odd runs with np.bincount). A local prototype matches the current results and is ~8–12× faster at 100–500 masks; it would also speed up the area sort in _paint_masks_by_area. I left it out to keep this fix minimal and can open it separately.

The dense-path case was flagged by the automated Codex review on this PR.

Prepared with Codex and reviewed with Claude Code; the test runs and timings above come from those local sessions.

🤖 Generated with Claude Code

All-False CompactMask crops contributed their full bounding boxes to the
ROI that MaskAnnotator copies and blends. Filter crop bounds by RLE
foreground count so empty crops no longer widen the region, and return
None when no foreground remains so the existing early return skips the
blend entirely.

Co-authored-by: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T12:40:12.785305Z 2a2e4ae PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@CLAassistant

CLAassistant commented Oct 16, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a2e4aef4f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/supervision/detection/utils/masks.py
MaskAnnotator passes detection boxes to _masks_to_roi, whose dense fast
path took the union of every box, including boxes of all-False masks.
Drop those boxes before the union so dense and compact masks blend the
same region.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Borda Borda added the bug Something isn't working label Oct 16, 2026
@Borda
Borda requested a balanced review from Copilot October 16, 2026 14:35

Copilot AI 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.

🟢 Approval recommended

The focused fix includes regression coverage for both mask formats, with no unresolved findings.

0 open findings

What changed in this PR

Completes the mask-blending optimization from #2368 by excluding empty masks from the blend region.

Changes:

  • Filters empty dense and compact masks before computing region bounds.
  • Adds regression coverage for skipped blending, region size, and preserved rendering.
  • Updates helper docstrings and the changelog.

🟢 Approve — no actionable issues identified. Behavior, regression coverage, performance tradeoffs, and documentation are addressed; tests were not independently run.

Quality: code 5/5, tests 5/5, documentation 5/5. Overall risk: 1/5 (focused change with targeted coverage).

File Description
tests/​detection/​utils/​test_masks.py Tests empty-mask exclusion from region bounds.
tests/​annotators/​test_core.py Tests blend behavior for dense and compact masks.
src/​supervision/​detection/​utils/​masks.py Excludes empty masks from bounding-box unions.
docs/​changelog.md Documents the blending optimization.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Changes:
- Merge develop at 74c9574 into the mask ROI PR branch and preserve the target release updates plus the PR changelog entry.

Impact:
- The PR branch now includes the latest target source, docs, and version metadata while retaining its mask ROI fix.

Verification:
- Reviewed all ten staged paths and resolved the changelog conflict; git diff --cached --check passed.
- All pre-commit hooks passed.
- Full suite: 5,702 passed, 105 skipped, 94% coverage.

Residual limits:
- The admitted review report has no independent specialist evidence; no independent review coverage is claimed.

---

Co-authored-by: Codex <codex@openai.com>
@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94%. Comparing base (74c9574) to head (23e4558).

Additional details and impacted files
@@           Coverage Diff           @@
##           develop   #2741   +/-   ##
=======================================
- Coverage       94%     94%   -0%     
=======================================
  Files           83      83           
  Lines        13489   13491    +2     
=======================================
  Hits         12675   12675           
- Misses         814     816    +2     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Borda
Borda merged commit 3d06d6e into roboflow:develop Oct 16, 2026
36 checks passed
jorge-asenjo added a commit to jorge-asenjo/supervision that referenced this pull request Oct 17, 2026
Merge develop at ffd0ac0, which released 0.31.0 and merged roboflow#2741, roboflow#2740
and roboflow#2743. Only docs/changelog.md conflicted: the CompactMask.area entry
now opens the new Unreleased section and the rest matches develop.

roboflow#2741 now filters MaskAnnotator ROI crops with CompactMask.area > 0, so
that path uses the vectorized area; its tests pass unchanged.

Verification: full suite 5,735 passed, 123 skipped, 94% coverage (NumPy
fallback); compact mask, mask utility and annotator tests 601 passed with
both the fallback and OpenCV 5.0.0; pre-commit passed on the PR files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Borda added a commit to Lingikaushikreddy/supervision that referenced this pull request Oct 17, 2026
Changes:
- Merge the fetched develop target into the Pascal VOC rounding branch, including the Unreleased notes for PRs roboflow#2740, roboflow#2741, and roboflow#2743.
- Integrate OpenCV-compatible ellipse rasterization and its regression coverage.
- Integrate mask ROI handling that excludes all-False masks and its tests.
- Integrate overflow-safe mean-average-precision box extents and dtype regression tests.

Impact:
- Keeps the PR branch current so the roboflow#2748 changelog note can be added beside the target's Unreleased entries.
- Brings the target's drawing, mask annotation, and metric fixes into the local PR branch.

Verification:
- The three-tree merge completed automatically without conflicts.
- The staged merge diff check passed; all eight staged paths were reviewed.

Residual limits:
- None known.

---

Co-authored-by: Codex <codex@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants