fix(detection): make oriented_box_iou_batch exact and gate non-overlapping pairs#2317
Merged
Merged
Conversation
…pping pairs Replace the rasterized mask IoU with exact convex-polygon intersection (cv2.intersectConvexConvex), pruned by a vectorized axis-aligned envelope gate. Rasterizing every box onto a shared canvas was both slow (cost scales with canvas area, not box count) and approximate: pixel quantization skewed the IoU, e.g. two small rectangles whose true IoU is 0.25 scored above 0.35. The result is now exact and invariant under affine transforms, so the canvas-cap workaround and its loose test tolerances are gone. This primitive also backs oriented NMS, NMM and the OBB metrics (precision, recall, F1, mAR), which all become faster and more accurate. Tests now assert exact analytic IoU values, tighten the invariance checks, and add guards for half-overlap, disjoint boxes and self-comparison.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2317 +/- ##
=======================================
Coverage 80% 81%
=======================================
Files 66 66
Lines 8948 8971 +23
=======================================
+ Hits 7196 7222 +26
+ Misses 1752 1749 -3 🚀 New features to boost your workflow:
|
…d IoU Add the two branches codecov flagged: a pair that clears the envelope gate but has no exact polygon overlap (scores 0), and an unsupported overlap metric (raises ValueError).
Borda
previously approved these changes
Jun 14, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates oriented_box_iou_batch to compute oriented-box overlap exactly (via convex polygon intersection) and to skip work for box pairs that cannot overlap (via an axis-aligned envelope gate). This improves both correctness (no rasterization quantization) and performance for downstream oriented NMS/NMM and OBB-based metrics.
Changes:
- Replace rasterized, canvas-based OBB IoU with exact
cv2.intersectConvexConvexintersection, plus a vectorized AABB-envelope prefilter. - Add a self-comparison fast path to compute only the upper triangle when the same array is passed for both arguments.
- Update and extend tests to validate analytic IoU/IoS values, invariance properties, and gating behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/supervision/detection/utils/iou_and_nms.py |
Reimplements oriented_box_iou_batch using exact convex-polygon intersection with an envelope overlap gate and symmetric self-comparison optimization. |
tests/detection/utils/test_iou_and_nms.py |
Tightens invariance tests and adds new analytic and edge-case coverage for exact oriented IoU/IoS behavior. |
Brings PR branch up to date with develop (roboflow#2315 tornado bump, roboflow#2321 coco segmentation fix). No conflicts with iou_and_nms.py changes in this PR. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- Parametrize test_self_comparison_is_symmetric_with_unit_diagonal with N=1 and N=2 (R6: N=1 path exercising triangular-mirror with single (0,0) pair) - Add test_degenerate_boxes_score_zero: collapsed, collinear, zero-area self-comparison → 0.0 (documents divergence from box_iou_batch semantics) - Add test_empty_input_returns_correct_shape: (0,M), (N,0), (0,0) variants exercising early-return path at TestOrientedBoxIouBatch level - Add test_invalid_shape_raises_value_error: 3-D wrong inner dims, 2-D wrong columns, 1-D input — matches exact error messages from implementation - Fix test_is_invariant_to_canvas_transforms: remove stale pixel-IoU / canvas reference; tighten tolerances to rtol=1e-5 / atol=1e-7 (exact arithmetic no longer has quantization noise) --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- Add Raises: section to oriented_box_iou_batch documenting all four ValueError paths (3-D wrong inner dims, 2-D wrong columns, wrong ndim, unsupported overlap_metric) - Add Note: block documenting is_self_comparison identity-based contract (disabled by upstream .copy()), convexity precondition, and NaN/Inf silent-zero behavior - Align Returns: style with box_iou_batch sibling (named entry semantics) - Add Examples: doctest block (doctest: +ELLIPSIS for IoU value) - Strengthen np.clip comment: explicitly mark as load-bearing; explains that cv2 intersection in float32 can exceed float64 area by ~25 ULP - Add one-line comment at NMS caller: is_self_comparison trigger context - Add Args:/Returns: to _polygon_areas and _aabb_envelopes private helpers - Expand _overlapping_envelope_pairs docstring: Note (correctness guarantee — not an approximation), Args: and Returns: blocks --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…emory Replace 4 named NxM float64 intermediates (x_min, y_min, x_max, y_max) with a single fused boolean evaluation using broadcast views. Peak transient memory drops from 5 NxM arrays to 1 boolean array (~17 MB vs ~33 MB at N=M=1000); speed is unchanged. --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…ct IoU A zero-area (collinear) OBB scores IoU 0 under the exact implementation (cf. test_degenerate_boxes_score_zero), so it cannot group and the two detections are no longer merged. Update the expected result to the two unmerged boxes. The previous expectation (merge to one) reflected the old rasterized IoU, which gave the zero-area line a small non-zero pixel IoU.
Borda
approved these changes
Jun 15, 2026
Borda
added a commit
that referenced
this pull request
Jun 16, 2026
- Merge Added [#2312] + Changed [#2312] into single Added entry covering xyxyxyxy_to_xyxy utility and with_nmm OBB merge - Merge Fixed [#2282] + Fixed [#2317] into single entry [#2282, #2317]; #2317 replaced the rasterization approach from #2282 entirely - Fold Fixed [#2252] into Added [#2252]; same function, one entry covers both preserve_audio feature and muxing bug fixes - Fold Fixed [#2289] into Added [#2302, #2289]; same function, one entry covers both is_obb parameter and rotation-loss 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.
oriented_box_iou_batchmeasures overlap by rasterizing every box onto a shared canvas (up to 1024x1024) and counting pixels. Two problems with that:This PR replaces the rasterization with exact convex polygon intersection (
cv2.intersectConvexConvex), gated by a vectorized axis-aligned envelope check. Pairs whose bounding rectangles don't overlap can't overlap as oriented boxes, so they skip the polygon step entirely. When the same set is passed on both sides (the NMS/NMM path) only the upper triangle is computed.The result is exact and invariant under scaling and translation, so the canvas cap and the loose test tolerances are no longer needed.
The same primitive backs oriented NMS, NMM and the OBB metrics (precision, recall, F1, mAR), so those get faster and more accurate too.
Accuracy
Timings
Self comparison, random oriented boxes, same machine. The two exact columns are an ablation to show where the speedup comes from: dropping rasterization does most of the work, and the envelope gate adds the rest on sparse scenes (the two exact columns produce identical values).
End to end, MeanAverageRecall over 8 images with 50 OBB each drops from 2.8s to 0.007s with an identical score.
How the timings were measured
The
rasterizedcolumn issv.oriented_box_iou_batchondevelop;exact + gateis the same call on this branch. Both use these boxes:The
exact, no gatecolumn toggles the envelope pre-filter off (exact intersection over every pair), isolating the gate's contribution:Rasterization cost is tied to the canvas, so it barely moves with box count; the exact path is tied to the number of pairs, and the gate keeps that to the pairs that can actually overlap.
Tests