Skip to content

fix(annotators): clip CropAnnotator boxes to the scene before cropping - #2391

Merged
Borda merged 1 commit into
roboflow:developfrom
abhijithneilabraham:abhijith_cropannotation_fix
Jul 3, 2026
Merged

fix(annotators): clip CropAnnotator boxes to the scene before cropping#2391
Borda merged 1 commit into
roboflow:developfrom
abhijithneilabraham:abhijith_cropannotation_fix

Conversation

@abhijithneilabraham

@abhijithneilabraham abhijithneilabraham commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
Before submitting

Description

CropAnnotator.annotate passed raw detection coordinates into crop_image, which
slices with plain numpy indexing. A negative x_min or y_min wraps around and
produces an empty crop, and scale_image then feeds the zero-size image to
cv2.resize, which raises cv2.error. Boxes fully outside the frame and
zero-width or zero-area boxes crash the same way, so one degenerate detection
aborts annotation of the whole frame.

This PR clips boxes to the scene with the existing clip_boxes helper,
mirroring BlurAnnotator and PixelateAnnotator, and skips boxes that are empty
after clipping. Skipped boxes keep their detection index, so color lookup
stays aligned with the original detections.

For every input the old code handled without crashing, the output is
pixel-identical (verified byte-for-byte for in-bounds and
right/bottom-overshoot boxes). The only behavior change besides removing the
crash: a fully negative box such as [-50, -50, -10, -10] previously wrapped
around and silently pasted a crop of an unrelated image region; it is now
skipped.

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)

Motivation and Context

Boxes at or beyond the frame border arise from supported workflows:
sv.pad_boxes produces negative coordinates for boxes near the frame edge, the
from_vlm connectors pass model coordinates through unclamped, and boxes land
fully outside the frame when detections computed at one resolution are
annotated onto another. The sibling annotators that slice the scene the same
way (BlurAnnotator, PixelateAnnotator) already clip; CropAnnotator was the
only one that did not.

Closes #2390

Changes Made

  • Clip boxes to the scene in CropAnnotator.annotate using the existing
    clip_boxes helper before cropping
  • Skip boxes whose width or height is zero after clipping instead of feeding
    empty crops to cv2.resize
  • Move crop and scale into the per-detection loop so a degenerate box no
    longer prevents valid boxes from being drawn
  • Add regression tests: five border-crossing boxes annotate without raising,
    four degenerate boxes are skipped leaving the scene unchanged, and a mixed
    valid/degenerate case still draws the valid box

Testing

  • I have tested this code locally
  • I have added unit tests that prove my fix is effective or that my
    feature works
  • All new and existing tests pass

The new tests fail on current develop with cv2.error (7 failing cases) and
pass with the fix (13/13 in TestCropAnnotator). Full suite: 2459 passed, 1
skipped, including under pytest --cov=supervision.

Reproduce the original crash on develop:

  import numpy as np  
  import supervision as sv

  image = np.zeros((100, 100, 3), dtype=np.uint8)
  detections = sv.Detections(
      xyxy=np.array([[-5, 20, 40, 60]], dtype=np.float32),
      class_id=np.array([0]),   
  )

  sv.BlurAnnotator().annotate(image.copy(), detections)   # works
  sv.CropAnnotator().annotate(image.copy(), detections)   # cv2.error before 
  this PR

Google Colab (optional)

Colab link: N/A

Screenshots/Videos (optional)

N/A

Additional Notes

No public API changes and no new dependencies. crop_image itself still returns
a silently empty array for out-of-bounds input; clamping it was left out of
scope to keep this PR minimal, since callers other than CropAnnotator already
clip.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84%. Comparing base (f196e15) to head (443fec3).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files
@@           Coverage Diff           @@
##           develop   #2391   +/-   ##
=======================================
  Coverage       84%     84%           
=======================================
  Files           70      70           
  Lines        10004   10009    +5     
=======================================
+ Hits          8436    8442    +6     
+ Misses        1568    1567    -1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@abhijithneilabraham abhijithneilabraham changed the title fix: cropannotation negative crashes fix(annotators): clip CropAnnotator boxes to the scene before cropping Jul 3, 2026
@Borda
Borda requested a review from Copilot July 3, 2026 07:45

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.

Pull request overview

This PR fixes CropAnnotator.annotate crashing when detections contain boxes that touch/cross the scene border (negative coords, fully out-of-frame, or degenerate/zero-area boxes). It aligns CropAnnotator with sibling annotators that already clamp boxes before NumPy slicing, preventing cv2.resize from receiving empty crops.

Changes:

  • Clip detections.xyxy to the scene bounds via existing clip_boxes(...) before cropping.
  • Skip detections that become empty after clipping (zero width/height) to avoid cv2.error.
  • Add regression tests covering border-crossing, fully-outside/degenerate, and mixed valid+degenerate cases.

Assessment (n/5):

  • Code quality: 5/5
  • Testing: 5/5
  • Docs: 5/5 (no public API change; existing docstrings remain applicable)

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/supervision/annotators/core.py Clamp crop regions to the scene and skip degenerate boxes so annotation can’t crash on out-of-bounds slices.
tests/annotators/test_core.py Adds coverage to ensure border/degenerate boxes don’t raise and are skipped appropriately.

@abhijithneilabraham

abhijithneilabraham commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@Borda is #2393 an alternate fix for this PR?

@Borda Borda added the bug Something isn't working label Jul 3, 2026
@Borda
Borda merged commit 0d4c3a4 into roboflow:develop Jul 3, 2026
22 checks passed
Borda added a commit that referenced this pull request Jul 3, 2026
- Keep .astype(np.int32) over .astype(int) — annotation declares npt.NDArray[np.int32]
- Keep crop_x1/crop_x2 naming over x_min/x_max — cleaner degenerate-box guard
- Merge all TestCropAnnotator tests from both branches — deprecation warning tests
  (from fix/annot) complement parametrized OOB coverage (from PR #2391)
- Preserve TestIconAnnotator class introduced in fix/annot

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Borda added a commit that referenced this pull request Jul 3, 2026
Resolve duplicate-change conflicts: several commits from this branch were
cherry-picked into separate PRs (#2391/#2393/#2394/#2396/#2397/#2398/#2399)
and squash-merged to develop, so merging develop back produced same-change
conflicts across 19 files.

Resolution:
- Keep this branch's later refactors: public `check_no_basename_collisions`
  (standardized collision-guard layer, renamed all `_check_*` call sites in
  dataset/core, yolo, labelme, tests), `save_pascal_voc_annotations` helper,
  value-based from_lmm->from_vlm mapping (drop dead lmm_to_vlm dict),
  CropAnnotator source-scene snapshot for overlapping boxes.
- Take develop's hardening/refinements: from_vlm empty-list guard, explicit
  float() mAP casts, np.float32 dtypes, MJPG/avi VideoSink tests, shared
  make_panoptic_png test helper, expanded empty-input adapter tests.
- Combine where both improve: np.int32 crop dtype + snapshot; keep both
  sides' added tests.

Full suite: 2924 passed, 1 xfailed. pre-commit (ruff, mypy, codespell) clean.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
@Borda Borda mentioned this pull request Jul 29, 2026
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.

[Bug]: CropAnnotator crashes with cv2.error when a box touches or crosses the image border

3 participants