Skip to content

fix(workflows): route detections_consensus IoU through detection geometry#2420

Open
kounelisagis wants to merge 9 commits into
roboflow:mainfrom
kounelisagis:fix/consensus-iou-mask-obb-aware
Open

fix(workflows): route detections_consensus IoU through detection geometry#2420
kounelisagis wants to merge 9 commits into
roboflow:mainfrom
kounelisagis:fix/consensus-iou-mask-obb-aware

Conversation

@kounelisagis

Copy link
Copy Markdown
Contributor

What does this PR do?

DetectionsConsensusBlockV1.calculate_iou always uses sv.box_iou_batch on xyxy, regardless of what geometry each detection actually carries. For instance-segmentation or OBB workflows that's the bounding box of the actual shape - which can overlap heavily even when the masks / oriented bodies don't. Two detections with identical AABBs but disjoint masks (or crossed OBBs in an aerial image) end up matched as the same object, and consensus silently merges them.

The fix dispatches on the geometry the detections actually carry:

  • mask_iou_batch when both have a mask
  • oriented_box_iou_batch when both have data["xyxyxyxy"]
  • box_iou_batch otherwise (unchanged)

Type of Change

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

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Test details:

Two new unit tests in tests/workflows/unit_tests/core_steps/fusion/test_detections_consensus.py:

  • test_calculate_iou_uses_mask_iou_when_both_have_masks - identical AABB, disjoint masks -> IoU 0.0 (would be 1.0 pre-fix).
  • test_calculate_iou_uses_obb_iou_when_both_have_oriented_boxes - crossed OBBs at +-45°, near-identical AABBs -> IoU < 0.2 (would be > 0.95 pre-fix).

Both fail on main. Existing calculate_iou tests are unchanged.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable)

Additional Context

The block already accepts a detections_merge_mask_aggregation parameter and is wired up to instance-segmentation models, so mask support was clearly intended - but the matching step itself was still bbox-only, so the aggregation never actually saw masks that the user expected to be merged.

…onsensus v2 block

Per review: changes that alter results of an existing block for clients
already using it ship as a new block version. v1 is reverted to its
original axis-aligned calculate_iou; the geometry-aware dispatch (mask
IoU when both sides carry masks, oriented-box IoU when both carry
data['xyxyxyxy'], axis-aligned otherwise) now lives in
detections_consensus@v2, registered alongside v1 so existing @v1
workflows keep their behaviour.

Tests for the new dispatch move to test_detections_consensus_v2.py,
together with v2 manifest parsing coverage; v1 tests are untouched.
Comment thread inference/core/workflows/core_steps/fusion/detections_consensus/v2.py Outdated
Three changes to calculate_iou, results unchanged (verified bit-identical
against the previous implementation across dense scattered scenarios):

1. Box IoU acts as a gate: a mask lives inside its bounding box and
   oriented-box corners live inside their xyxy, so disjoint boxes imply
   zero mask / oriented-box IoU. Most cross-source pairs in a consensus
   run do not overlap, so the geometry comparison only runs for actual
   match candidates. The box-only fallback reuses the gate value.

2. Mask IoU is computed on bounding-box windows instead of full frames.
   The intersection can only live inside the boxes' overlap window and
   each mask's area inside its own box window, so the cost is
   proportional to box size, not image size: a 1080p or 4K mask pair
   costs the same ~0.15 ms / 0.09 MB transient (down from 1.5-5.5 ms /
   2-8 MB full-frame).

3. Oriented-box IoU is computed analytically with shapely (already a
   dependency, used by overlap_analysis) instead of rasterization. This
   is exact, allocation-free, and independent of coordinate magnitude
   (~0.17 ms for any pair, down from 8 ms / 23 MB for far-corner boxes).
   It also sidesteps a rasterization bug in currently-released
   supervision 0.28.0 where overlapping boxes in the right portion of a
   landscape frame return IoU 0 (fixed upstream in supervision#2282 but
   not yet released).

End-to-end: a 2-sources x 20-detections scattered 1080p scenario costs
6.3 ms for all 400 pair comparisons, vs 4.9 ms for v1's box-only path
and 509 ms for the naive full-frame implementation.

@dkosowski87 dkosowski87 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.

Two fixes required, plus tests for that fixes area also needed.

Other than that, we are missing a workflow integration test, were we run a workflow definition including the newly added block. Add an execution test for @v2, ideally including mask/OBB matching and default union mask output.

Comment thread inference/core/workflows/core_steps/fusion/detections_consensus/v2.py Outdated
Comment thread inference/core/workflows/core_steps/fusion/detections_consensus/v2.py Outdated
get_union_mask summed masks, so overlapping pixels became 2 instead of a
boolean union; use np.any. Placeholder masks for detections that lack one
defaulted to float and upcast the merged mask; allocate them as bool.
@kounelisagis kounelisagis requested a review from dkosowski87 July 3, 2026 13:51
@dkosowski87

Copy link
Copy Markdown
Contributor

Other than that, we are missing a workflow integration test, were we run a workflow definition including the newly added block. Add an execution test for @v2, ideally including mask/OBB matching and default union mask output.

@kounelisagis please add the test mentioned above ☝️

Runs a workflow with the detections_consensus@v2 block over injected
predictions: mask matching with the default union output, plus oriented
box matching including the crossed-envelope case that must not vote
together.
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.

3 participants