Skip to content

feat: expose sv.match_detections as a public matching primitive - #2557

Merged
Borda merged 8 commits into
roboflow:developfrom
jabrailkhalil:feat/match-detections
Sep 10, 2026
Merged

Borda merged 8 commits into
roboflow:developfrom
jabrailkhalil:feat/match-detections

Conversation

@jabrailkhalil

Copy link
Copy Markdown
Contributor

Summary

Exposes the greedy matcher that the metrics modules use internally as a public, stateless primitive: sv.match_detections.

matched_pairs, unmatched_a, unmatched_b = sv.match_detections(
    detections_a,
    detections_b,
    iou_threshold=0.5,
    class_agnostic=False,
)
  • matched_pairs has shape (M, 2): column 0 indexes detections_a, column 1 indexes detections_b; unmatched_a / unmatched_b hold the remaining indices of each side. The result composes with Detections slicing (detections_b[matched_pairs[:, 1]], etc.).
  • Assignment is one-to-one, greedy, highest-IoU-first — identical to _greedy_match from fix(metrics): replace np.unique matching with greedy algorithm #2380. Ties keep the stable np.argsort(-iou, kind="stable") order.
  • By default a pair additionally requires equal class_id; class_agnostic=True matches on IoU only. If either side has no class_id, matching falls back to IoU only.
  • Pairs below iou_threshold are never matched; confidence is ignored.
  • Empty inputs return an empty (0, 2) pair array and the other side's indices as unmatched.

No metric behavior changes: the implementation only wraps box_iou_batch + _greedy_match.

Tests

Added TestMatchDetections in tests/metrics/utils/test_matching.py (8 cases): identical pairing, unmatched-index reporting, class mismatch blocking by default, class_agnostic=True, IoU threshold filtering, one-to-one assignment under contested predictions, empty inputs, and missing class_id fallback. The docstring example also runs under --doctest-modules.

Documentation

  • New page docs/metrics/match_detections.md with usage and API reference; added to the Metrics nav in mkdocs.yml.
  • Changelog entry under Unreleased.

Verification

pytest tests/metrics/utils/test_matching.py tests/metrics/test_lazy_imports.py   # 17 passed
pytest --doctest-modules src/supervision/metrics/utils/matching.py                # 3 passed
ruff check src/supervision/metrics/utils/matching.py src/supervision/__init__.py tests/metrics/utils/test_matching.py   # clean
ruff format --check ...                                                            # clean
python -c "import supervision as sv; ..."                                          # sv.match_detections works

Closes #2476.

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@CLAassistant

CLAassistant commented Sep 9, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@Borda
Borda changed the base branch from main to develop September 9, 2026 20:48
@Borda
Borda requested a balanced review from Copilot September 9, 2026 20:49
@Borda Borda changed the title feat: expose sv.match_detections as a public matching primitive feat: expose sv.match_detections as a public matching primitive Sep 9, 2026

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.

🟡 Changes recommended

The public API currently accepts invalid IoU thresholds, which can pair unrelated detections or fail silently.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Exposes the metrics’ greedy IoU matcher as the public sv.match_detections API.

Changes:

  • Adds one-to-one matching with class-aware and class-agnostic modes.
  • Adds comprehensive tests and public export.
  • Adds API documentation, navigation, and changelog entry.

Assessment: 🟠 Request Changes
Quality: Code 4/5 · Testing 4/5 · Documentation 5/5 · Risk 2/5
Invalid IoU thresholds require validation and a regression test.

File summaries
File Description
src/supervision/metrics/utils/matching.py Implements the public matcher.
src/supervision/__init__.py Exports match_detections.
tests/metrics/utils/test_matching.py Tests matching behavior and edge cases.
docs/metrics/match_detections.md Adds API usage documentation.
mkdocs.yml Adds the documentation page to navigation.
docs/changelog.md Records the new public API.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread supervision/detection/utils/match_detections.py
Add a public API that matches two sets of detections into one-to-one
pairs, reusing the same greedy highest-IoU-first matcher the metrics
modules use internally.

match_detections(detections_a, detections_b, iou_threshold=0.5,
class_agnostic=False) returns index arrays: matched_pairs of shape
(M, 2) plus the unmatched indices of each side, so the result composes
with Detections slicing. By default a pair also requires equal
class_id; class_agnostic=True restricts matching to IoU. confidence is
ignored. Empty inputs return an empty (0, 2) pair array with every
index of the other side unmatched.

Export it as sv.match_detections, add unit tests, a docs page under
metrics, and a changelog entry.

Closes roboflow#2476.

Signed-off-by: jabrailkhalil <[email protected]>
@Borda

Borda commented Sep 9, 2026

Copy link
Copy Markdown
Member

@jabrailkhalil pls avoid force push, otherwise you destroy our review process and cannot move forward...

@codecov

codecov Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89%. Comparing base (6337efe) to head (0d54763).

Additional details and impacted files
@@           Coverage Diff           @@
##           develop   #2557   +/-   ##
=======================================
  Coverage       89%     89%           
=======================================
  Files           86      86           
  Lines        11877   11908   +31     
=======================================
+ Hits         10619   10650   +31     
  Misses        1258    1258           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Borda Borda added the enhancement New feature or request label Sep 10, 2026
Borda and others added 5 commits September 10, 2026 09:59
Changes:
- Validate public match_detections IoU thresholds before empty-input handling using the shared range validator.
- Add invalid-threshold regression coverage and the required maintainer module documentation.

Impact:
- Disjoint detections can no longer be falsely paired by a negative threshold; invalid thresholds consistently raise ValueError.
- Maintainers have an explicit module boundary and failure contract for the public matcher.

Verification:
- Focused matcher tests and doctests: 21 passed.
- Ruff check, Ruff format check, and git diff --check: passed.

Residual limits:
- Independent QA/challenger evidence for PR roboflow#2557 remains unresolved; this commit fixes only local source findings.

---

Co-authored-by: Codex <[email protected]>
Changes:
- Move the public detection matcher and its tests from metrics utilities to detection utilities, updating metric consumers and documentation references.
- Require class IDs for non-empty class-aware matching and document explicit geometry-only opt-in plus greedy non-optimal assignment behavior.

Impact:
- sv.match_detections keeps its top-level API while its module ownership now matches the Detections domain.
- Callers cannot silently weaken class-aware matching when class metadata is absent.

Verification:
- Focused matcher, precision, and recall tests plus matcher doctests: 85 passed.
- Ruff check, Ruff format check, and git diff --check: passed.

Residual limits:
- Codemap query was unavailable because its launcher requires Python 3.11+ while this workspace uses Python 3.10.
- Independent QA/challenger evidence for PR roboflow#2557 remains unresolved.

---

Co-authored-by: Codex <[email protected]>
Changes:
- Validate class IDs before the empty-input fast path for class-aware detection matching.
- Add regression coverage for empty class-aware inputs and preserve explicit geometry-only matching.

Impact:
- class_agnostic=False now consistently rejects missing class metadata, preventing any implicit geometry-only fallback.

Verification:
- Focused matcher, precision, and recall tests plus matcher doctests: 86 passed.
- Ruff check, Ruff format check, and git diff --check: passed.

Residual limits:
- Independent QA/challenger evidence for PR roboflow#2557 remains unresolved.

---

Co-authored-by: Codex <[email protected]>
Borda
Borda previously approved these changes Sep 10, 2026
- Ensure `detections_a.class_id` and `detections_b.class_id` are not `None` before comparison to prevent silent failures in class-aware detection matching.
- Maintain consistent behavior for `class_agnostic=False` by asserting required metadata.
@Borda
Borda merged commit b5c4a15 into roboflow:develop Sep 10, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

match_detections(): expose the unified greedy matcher as a public cross-Detections primitive

4 participants