Compare commits

..

203 Commits

Author SHA1 Message Date
Swapnil Gautam 7d325d23e3
docs: convert fenced examples to doctests in dataset/formats/createml.py (#2475)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:12:40 +02:00
Franklin Cappadora fb22686a5e
ITZ-90 replaces ga tag with unified instance tag (#2028)
Co-authored-by: Piotr Skalski <piotr.skalski92@gmail.com>
Co-authored-by: jirka <6035284+borda@users.noreply.github.com>
2026-08-05 19:54:21 +02:00
Daniiiil1 6b143e4e41
test(metrics): verify live sklearn parity (#2470)
* add sklearn parity goldens
* compute sklearn parity live
* separate empty input semantics
2026-08-05 19:24:44 +02:00
Swapnil Gautam bc20dd19fb
docs: convert fenced examples to doctests in dataset/formats/coco.py (#2474)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 14:51:58 +02:00
dependabot[bot] e82349fabf
⬆️ Bump cryptography from 48.0.1 to 50.0.0 in the uv group across 1 directory (#2473)
Bumps the uv group with 1 update in the / directory: [cryptography](https://github.com/pyca/cryptography).


Updates `cryptography` from 48.0.1 to 50.0.0
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/48.0.1...50.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 50.0.0
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 20:32:01 +02:00
dependabot[bot] 01df50b440
⬆️ Bump pymdown-extensions from 10.21.3 to 11.0 in the uv group across 1 directory (#2472)
Bumps the uv group with 1 update in the / directory: [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions).


Updates `pymdown-extensions` from 10.21.3 to 11.0
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.21.3...11.0)

---
updated-dependencies:
- dependency-name: pymdown-extensions
  dependency-version: '11.0'
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-04 19:45:30 +02:00
jirka a95d85ce07 rolling towards `0.31.0.dev` 2026-08-04 19:43:57 +02:00
Jirka Borovec 813c4295bf
releasing `0.30.0` (#2462) 2026-08-04 16:22:04 +02:00
Arthi Arumugam 7fc91180fe
fix(metrics): track prediction-only classes in Recall (#2468)
#2331 made Precision and F1Score include classes that appear only in
predictions, and added regression tests to both. Recall was not touched, so
the line #2331 replaced is still there and the three metrics disagree about
which classes exist for identical input:

    precision.matched_classes -> [0 1]   precision_per_class (2, 10)
    recall.matched_classes    -> [0]     recall_per_class    (1, 10)
    f1.matched_classes        -> [0 1]

These read as parallel outputs, so zipping them silently truncates rather
than raising.

Recall for a class with no ground-truth instances is 0.0 rather than
undefined, which is what sklearn reports (it infers labels from the union of
y_true and y_pred) and what #2331 cited as its own standard. MICRO is
unchanged because an absent class contributes no false negatives, and
WEIGHTED is unchanged because its ground-truth support is zero. MACRO does
change, and the changelog says so.

Also of note: recall.py already carried #2331's WEIGHTED zero-support guard,
whose comment refers to 'only false-positive classes'. That state could not
arise in recall.py, because unique_classes came from ground truth alone. The
guard was propagated; the union that gives it meaning was not.

Addresses the review on #2468. Building the class union inside
_compute_recall_for_classes only covers samples that reach it, and samples with
predictions but no targets are skipped earlier in _compute. So matched_classes
could still disagree with Precision and F1Score for list inputs containing a
background image, which is the exact invariant the new test asserts.

Before, for one normal sample plus one background image predicting class 2:

    precision.matched_classes -> [0 2]
    recall.matched_classes    -> [0]

Recall now handles len(targets) == 0 and len(predictions) > 0 the way Precision
does. No recall value changes, since a background image produces no false
negatives; only the tracked class set does.

* test: cover Recall bg-image size-bucket, dup & non-contiguous ids
* docs: strengthen Recall changelog migration note
* docs+perf: Recall doctest example; dedupe-then-union micro-opt

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-08-03 20:57:05 +02:00
pre-commit-ci[bot] f7f53d0a60
chore(pre_commit): ⬆ pre_commit autoupdate (#2471)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.16.0 → v0.16.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.16.0...v0.16.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-03 19:50:44 +02:00
Jirka Borovec f2efc328f7
fix(dataset): 3D empty mask for VOC background (#2469)
- `detections_from_xml_obj` now builds `np.empty((0, H, W))` for a background image under `force_masks=True` instead of letting `np.array([])` collapse to shape `(0,)`, which failed `Detections` mask validation
- document the forced `class_id` `dtype=int` with an inline comment and state the integer-dtype guarantee in the `detections_from_xml_obj` docstring Returns section
- add background-image coverage: force_masks empty 3D mask, all-background dataset, background-first ordering, and save-then-load round-trip
- add changelog entry for the `force_masks=True` background-image mask fix

---

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-08-03 18:30:24 +02:00
shao 475d551908
refactor: vectorize `get_labels_text()` in annotators/utils (#2465)
* refactor: vectorize `get_labels_text()` in annotators/utils.py
* test: add tests for all get_labels_text branches
* style: flatten elif/else to guard clauses in get_labels_text()
* refactor: guard get_labels_text() against array/detections length mismatch
* test: add missing get_labels_text() coverage

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-08-03 17:43:14 +02:00
Shadow_Lu 1a2b9b24db
fix(dataset): keep `class_id` integral for VOC background images (#2463)
detections_from_xml_obj built class_id with np.array(...) over a list of
indices. For an annotation file with no object elements that list is empty,
so NumPy inferred float64 and DetectionDataset validation rejected the
resulting Detections, making any Pascal VOC dataset that contains an
unannotated image impossible to load.
2026-08-03 12:17:01 +02:00
dependabot[bot] 3c19d176b3
⬆️ Bump the github-actions group with 2 updates (#2466)
Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `actions/checkout` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v7...v7.0.1)

Updates `astral-sh/setup-uv` from 8.3.0 to 9.0.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v8.3.0...c771a70e6277c0a99b617c7a806ffedaca235ff9)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: astral-sh/setup-uv
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 10:42:38 +02:00
Jirka Borovec 794971ba0f
feat(cv2): remove OpenCV dependency (#2443)
* feat(cv2): remove OpenCV dependency
* ci(tests): cover ambient cv2 wheels
* fix(cv2): restore fallback CI
* ci(tests): simplify pytest job name formatting
* ci(tests): refactor cv2 backend check for readability and set Python shell explicitly
* test(cv2): stabilize OpenCV parity checks
* ci(tests): fix Python 3.13 matrix typo in CI workflow
* scope OpenCV-absence check to declared deps/extras
* normalize whitespace in release-doc phrase assertions
* fix(ci): strip manifest line before comment check
* emit `UserWarning` when OpenCV is missing to alert users of fallback behavior

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-31 22:19:33 +02:00
Erik 541b0226cd
feat: add image URL loader (#2372)
- Added image loading from HTTP and HTTPS URLs with descriptive URL validation errors
- Added optional caching for image URL loads using the shared Supervision cache
- Improved URL downloads with atomic file replacement and shared download behavior across image loading and asset downloads
- Updated image decoding compatibility with Pillow fallbacks when OpenCV decoding or encoding is unavailable

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-28 21:24:55 +02:00
shao 5212af7b70
docs: improve doctests in draw/utils.py (#2425)
* docs: wrap obb_polygon_area doctest in pycon fence
* Update scene assignment in drawing functions

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-07-28 10:31:16 +02:00
Andrew Barnes e138f6c544
Fix sink state when instances are reopened (#2459)
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-07-28 10:11:06 +02:00
pre-commit-ci[bot] 5f0654e5cc
chore(pre_commit): ⬆ pre_commit autoupdate (#2460)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/rbubley/mirrors-prettier: v3.9.5 → v3.9.6](https://github.com/rbubley/mirrors-prettier/compare/v3.9.5...v3.9.6)
- [github.com/tox-dev/pyproject-fmt: v2.25.3 → v2.26.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.25.3...v2.26.0)
- [github.com/astral-sh/ruff-pre-commit: v0.15.22 → v0.16.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.22...v0.16.0)

* test(detection): fix type hint for `expected_results` in VLM tests

* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
2026-07-28 10:01:41 +02:00
dependabot[bot] caff4d1ec2
⬆️ Bump the github-actions group with 2 updates (#2458)
Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish).


Updates `actions/checkout` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v7...v7.0.1)

Updates `pypa/gh-action-pypi-publish` from 1.14.0 to 1.14.1
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](cef221092e...ba38be9e46)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: pypa/gh-action-pypi-publish
  dependency-version: 1.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 09:20:23 +02:00
dependabot[bot] ceddf331c6
⬆️ Update pydeprecate requirement from <0.11,>=0.9 to >=0.9,<0.12 (#2457)
Updates the requirements on [pydeprecate](https://github.com/Borda/pyDeprecate) to permit the latest version.
- [Release notes](https://github.com/Borda/pyDeprecate/releases)
- [Changelog](https://github.com/Borda/pyDeprecate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Borda/pyDeprecate/compare/v0.9.0...v0.11.0)

---
updated-dependencies:
- dependency-name: pydeprecate
  dependency-version: 0.11.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 09:20:12 +02:00
dependabot[bot] 9c06bfeddb
⬆️ Bump jupyterlab from 4.5.9 to 4.5.10 in the uv group across 1 directory (#2456)
Bumps the uv group with 1 update in the / directory: [jupyterlab](https://github.com/jupyterlab/jupyterlab).


Updates `jupyterlab` from 4.5.9 to 4.5.10
- [Release notes](https://github.com/jupyterlab/jupyterlab/releases)
- [Changelog](https://github.com/jupyterlab/jupyterlab/blob/main/RELEASE.md)
- [Commits](https://github.com/jupyterlab/jupyterlab/compare/@jupyterlab/lsp@4.5.9...@jupyterlab/lsp@4.5.10)

---
updated-dependencies:
- dependency-name: jupyterlab
  dependency-version: 4.5.10
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 09:20:06 +02:00
dependabot[bot] fe77de5b9f
⬆️ Bump gitpython from 3.1.44 to 3.1.54 in the uv group across 1 directory (#2454)
Bumps the uv group with 1 update in the / directory: [gitpython](https://github.com/gitpython-developers/GitPython).


Updates `gitpython` from 3.1.44 to 3.1.54
- [Release notes](https://github.com/gitpython-developers/GitPython/releases)
- [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES)
- [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.44...3.1.54)

---
updated-dependencies:
- dependency-name: gitpython
  dependency-version: 3.1.54
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-26 22:31:58 +02:00
Jirka Borovec bcbe8de1e4
chore(pre-commit): remove unnecessary exclude rule for changelog... (#2452)
* chore(pre-commit): remove unnecessary exclude rule for changelog and deprecated docs
* docs(changelog): reformat code blocks for consistency and clarity
* docs(changelog): reformat and align code blocks for consistent indentation and readability
* chore(pre-commit): update mdformat hooks to include gfm and frontmatter extensions
* chore(pre-commit): split mdformat hook into gfm and mkdocs variants
* docs(changelog): fix nested code fences breaking mdformat-mkdocs
* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-21 19:33:35 +02:00
Vikas saini 156ee33740
docs: convert fenced examples to doctests in draw/utils.py (#2451) 2026-07-21 17:40:13 +02:00
dependabot[bot] 7d915a7357
⬆️ Bump pillow from 12.2.0 to 12.3.0 in the uv group across 1 directory (#2450)
Bumps the uv group with 1 update in the / directory: [pillow](https://github.com/python-pillow/Pillow).


Updates `pillow` from 12.2.0 to 12.3.0
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/12.2.0...12.3.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 12.3.0
  dependency-type: direct:production
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 16:21:26 +02:00
Piotr Skalski 9837c17878
feat(vlm): add Gemini 3.5 Flash parsing support (#2449)
Add VLM.GOOGLE_GEMINI_3_5 enum and from_google_gemini_3_5 connector reusing the 2.5 parser, wire it into Detections.from_vlm, and salvage valid entries from partially malformed Gemini JSON arrays. Includes tests and changelog.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-07-21 16:20:05 +02:00
Mahbod b20d6eac46
feat(utils): add prefetch to `get_video_frames_generator` (#2273)
* feat(utils): add prefetch to get_video_frames_generator
* fix(utils): harden _prefetched_frames_generator threading safety
* test(utils): add prefetch combination and minimum-queue tests
* docs(utils): improve prefetch documentation, validation, and test docstrings
* fix(utils): harden prefetch reader-thread exception handling + docs
* docs(changelog): sync front-matter date_modified
* test(utils): harden and extend prefetch test coverage
* test(utils): cover buffered-frames-before-error and zero-frame prefetch cases

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-21 14:12:08 +02:00
Matt Van Horn 25e879ec05
refactor: centralize geometry-aware dispatch for detection_area and detection_iou (#2374)
* refactor: centralize geometry-aware dispatch for detection_area and detection_iou
* fix(detection): centralize geometry-aware merge IoU
* docs(detection): add code examples to geometry calculation docstrings
* code: reuse count_mask_pixels; relocate geometry dispatch out of utils/
* docs: fix changelog framing and filter_detections.md wording
* test: add edge-case coverage for geometry dispatch and merge chains
* fix(tests): repair syntax-broken docstrings in geometry dispatch/merge tests
* refactor(detection): make geometry dispatch module private

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-21 12:37:20 +02:00
HALLOUARD 89d49c2e93
feat: Add soft Non-Max suppression (#1624)
* feat: Add soft Non-Max suppression
* feat(nms): add vectorized Gaussian Soft-NMS box/mask primitives
* feat(core): add Detections.with_soft_nms
* feat: export soft-NMS functions from top-level supervision API
* docs: add soft-NMS entries to IoU/NMS utils page
* test: add Soft-NMS coverage for box/mask primitives and Detections API

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-21 10:25:14 +02:00
pre-commit-ci[bot] 140c05f273
chore(pre_commit): ⬆ pre_commit autoupdate (#2448)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/JoC0de/pre-commit-prettier: 0737985b8f21a8d83195f6ced1045e7637fc82fb → v3.9.5](0737985b8f...v3.9.5)
- [github.com/tox-dev/pyproject-fmt: v2.25.2 → v2.25.3](https://github.com/tox-dev/pyproject-fmt/compare/v2.25.2...v2.25.3)
- [github.com/astral-sh/ruff-pre-commit: v0.15.21 → v0.15.22](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.21...v0.15.22)
- [github.com/codespell-project/codespell: v2.4.2 → v2.4.3](https://github.com/codespell-project/codespell/compare/v2.4.2...v2.4.3)

* Apply suggestions from code review

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>

* Update .pre-commit-config.yaml

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-07-20 22:00:13 +02:00
Jirka Borovec 206adc083c
ci: add doctest fence hook (#2447)
* ci: add doctest fence hook
* docs: fence source doctests

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-20 13:35:46 +02:00
Jirka Borovec 5fccf8a966
chore: normalize doctest code fences to pycon in src docs (#2446) 2026-07-20 11:22:27 +02:00
dependabot[bot] 8be58fa0aa
⬆️ Bump astral-sh/setup-uv from 8.3.0 to 8.3.2 in the github-actions group (#2445)
Bumps the github-actions group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 8.3.0 to 8.3.2
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](d31148d669...11f9893b08)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 10:58:30 +02:00
dependabot[bot] f1f8213c01
⬆️ Update opencv-python requirement from <5,>=4.5.5.64 to >=4.5.5.64,<6 (#2444)
Updates the requirements on [opencv-python](https://github.com/opencv/opencv-python) to permit the latest version.
- [Release notes](https://github.com/opencv/opencv-python/releases)
- [Commits](https://github.com/opencv/opencv-python/commits)

---
updated-dependencies:
- dependency-name: opencv-python
  dependency-version: 5.0.0.93
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 10:58:09 +02:00
Mahbod 937ed4af37
feat(detection): add require_all_anchors to PolygonZone (#2272)
Currently a detection counts as 'in the zone' only when every anchor in
triggering_anchors is inside. For boxes that straddle the zone boundary
this means a detection with many anchors (e.g. the four corners) is often
under-counted unless the user shrinks triggering_anchors to a single point.

Add require_all_anchors: bool = True so callers can opt into 'any anchor
inside is enough'. Default preserves current behaviour.

* test: strengthen PolygonZone require_all_anchors coverage
* docs: clarify require_all_anchors anchor-based semantics

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 18:56:42 +02:00
Lee Clement 60d748e57d
fix: read COCO export image sizes from headers instead of decoding pixels (#2442)
save_coco_annotations iterated the dataset, cv2-decoding every image only
to read its shape — even for labels-only exports. Sizes now come from the
in-memory array when present, else a lazy PIL header read, the same
optimization from_yolo uses (#1636).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-07-17 18:30:24 +02:00
Jirka Borovec f7b63f149a
perf(cv2): simplify fallback operations (#2441)
- Remove unused compatibility operations and use focused Pillow and NumPy paths to reduce maintained fallback code.
- Preserve numerical decisions and hot-path performance with exact regression coverage and bounded algorithms.
- Preserve INTER_LINEAR uint8 reductions within one LSB while retaining the resize performance budget and numeric RGBA handling.
- Restore repeated-endpoint contour anchors and bound cross-platform chamfer coefficient drift in regression tests.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-17 17:31:57 +02:00
Shehzad Waseem 9aaf7fdbd6
fix(metrics): avoid division by zero RuntimeWarning in F1Score using np.divide (#2437)
* fix(metrics): avoid division by zero RuntimeWarning in F1Score using np.divide
* test+changelog: add F1Score zero-denom regression test; add changelog entry
* follow-up cleanup: mirror F1Score np.divide fix in mean_average_recall

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 17:11:45 +02:00
jirka a4b9c4e097 fix(cv2): pad getTextSize height/baseline from actual stroke_width
_get_text_size approximated thickness-to-stroke padding with
thickness // 2 formulas that diverge from the thickness - 1
stroke_width _put_text actually renders with. Past thickness 2 the
padding grows too slowly, so heavy-stroke descender pixels can fall
outside the reported box, breaking the documented enclosure
guarantee. Both functions now derive stroke_width from one shared
helper.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-17 10:29:55 +02:00
Jirka Borovec 68e63f9e39
refactor(cv2): replace Hershey text with Pillow (#2440)
Text fallback now renders through Pillow with the DejaVu Sans face
resolved via matplotlib font_manager, replacing the Hershey stroke-font
reader; getTextSize metrics derive from the same font and differ from
OpenCV within the documented visual-divergence tier.

Remove the packaged Hershey glyph data (hershey_fonts.json, provenance,
license) and its _cv2/data package-data entry.

Delete unused fallbacks: _geometry _fill_poly and _point_in_polygon
(live fillPoly is the Pillow one in _drawing) and _common _unavailable.

Replace test_hershey with Pillow-oriented test_text, drop test_common,
and point test_contours/test_geometry at _drawing._fill_poly. Document
the fallback text-backend change in the changelog.

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-17 10:22:32 +02:00
jirka bc7b9fc69e docs(changelog): document cv2 fallback fixes from resolve pass
[resolve] PR #2439 — changelog entries for items 2,3 and the
copyMakeBorder scalar-channel bugfix, per AGENTS.md changelog policy.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-17 09:08:16 +02:00
Jirka Borovec 1efa5b8eaa
feat(cv2): complete fallback integration (#2439)
* feat(cv2): complete fallback integration
* fallback-fixes: reject invalid addWeighted dtype; O(N) approxPolyDP anchor seeding
* tests: copyMakeBorder sequence parity; drop non-empty facade-import assert; fix Windows path separator in boundary check
* fix(cv2): copyMakeBorder scalar value only fills channel 0 on multichannel images

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-17 07:50:45 +02:00
Jirka Borovec 20b7c085b7
feat(video): require PyAV during cv2 transition (#2438)
- Add the PyAV-backed file-video and audio fallback to the compatibility layer.
- Declare PyAV alongside OpenCV until the final dependency-removal integration.
- _VideoWriter now rejects is_color=False (NotImplementedError) instead of
  silently dropping it, since the PyAV fallback only encodes 3-channel frames.
- _mux_audio cleanup (container closes, temp-file removal) is now best-effort
  so a failing close/remove in finally can no longer mask the primary result
  or the original exception.
- The subprocess used to validate the cv2-free fallback had no timeout;
  a hang (import deadlock, codec probe stall) could block the whole CI
  run. Added a 60s timeout so a hang fails fast with a clear traceback
  instead of an opaque suite-wide stall.
- process_video(preserve_audio=True) docstring still described the old
  ffmpeg-based muxing; audio remuxing was reimplemented with PyAV and no
  longer requires an external ffmpeg executable.
- get_video_frames_generator's documented webcam fallback
  (`_cv2.VideoCapture(0)`) silently fails under the PyAV backend: the
  BackendUnavailableError raised for integer sources was swallowed with no
  logging, so isOpened() just returns False with zero diagnostic signal.
  Doc note now states the limitation explicitly and the capture logs a
  warning instead of failing silently.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-16 20:13:15 +02:00
Jirka Borovec c3496134bc
feat(cv2): add Hershey text fallback (#2435)
* feat(cv2): add Hershey text fallback
* fix(cv2): sync Hershey provenance hash

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-16 17:10:49 +02:00
Jirka Borovec 39eb6571ce
feat(cv2): add drawing fallbacks (#2433)
* feat(cv2): add drawing fallbacks
* code: reject non-default hierarchy in _draw_contours fallback
* tests: keep cv2 optional so cv2-less fallback tests still run

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-16 12:53:01 +02:00
Jirka Borovec 7096ee911c
feat(cv2): add geometry fallbacks (#2432)
- Added geometry fallbacks for OpenCV-dependent operations, including connected-component processing.
- Fixed contour hierarchy detection for concave shapes by reliably selecting an interior point.
- Improved contour hierarchy remapping performance for large contour sets.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-16 00:54:57 +02:00
Jirka Borovec 3d669f1ab4
feat(cv2): add image fallback backend (#2431)
- Organize facade constants and implementations into thematic private modules.
- Add NumPy/Pillow/SciPy fallbacks with OpenCV parity coverage.
- Mirror package modules in cv2 tests and verify blocked imports.
- Split compound operation tests into isolated cases.
- Parameterize color parity and fallback bindings for targeted failures.
- Inline color conversion cases at their only use site.
- Inline fallback binding cases while preserving reusable manifests.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-15 21:55:08 +02:00
Jirka Borovec 8ecd9a6680
refactor(cv2): add optional backend facade (#2430)
* refactor(cv2): add optional backend facade
* test: guard real cv2 oracle import for cv2-less environments
* test: preserve existing PYTHONPATH in subprocess import tests
* lint: auto-fix violations after resolve cycle
* fix(typing): remove obsolete suppressions
* test(cv2): parametrize constant alignment tests and refactor fallback validation
* test(cv2): simplify constant grouping and optimize REQUIRED_SYMBOLS validation

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-15 20:53:24 +02:00
Jirka Borovec 16814acff3
feat(utils): add `TkImageWindow` to unblock switch to `opencv-python-headless` (#2320)
- Added `sv.ImageWindow`, a Tkinter/Pillow-based desktop image viewer with BGR, grayscale, and BGRA support, keyboard polling, left-click callbacks, context-manager usage, window-state checks, and clean close handling
- Added responsive image resizing with optional aspect-ratio preservation and correctly mapped mouse coordinates after scaling or letterboxing
- Updated compatible runnable examples to use `sv.ImageWindow`, while retaining OpenCV display APIs for worker-thread streaming examples that are incompatible with Tkinter
- Improved `sv.cv2_to_pillow` to support grayscale and BGRA images
- Updated webcam guidance to clarify capture ownership and explicit `VideoCapture` cleanup
- Fixed image-window event handling to prevent stale keypresses, ghost windows, close-time races, and blocked waits after the window closes

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-15 15:06:55 +02:00
Abhijith Neil Abraham 94cfb7f290
fix(metrics): ignore out-of-bucket detections in size-bucketed sco… (#2428)
* fix: ignore out-of-bucket   detections in size-bucketed scoring
* fix: honor area metadata in buckets

- Prefer stored COCO area metadata before geometry, mask, or OBB fallbacks.
- Add explicit-area, mask, and OBB regression coverage.
- Align COCO, mAP, and changelog area semantics.

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-07-15 12:48:19 +02:00
pre-commit-ci[bot] 3e80c0ce3c
chore(pre_commit): ⬆ pre_commit autoupdate (#2426)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/JoC0de/pre-commit-prettier: fc2da0552b28c24c836d045bfb6c3057b3d11e62 → v3.9.5](fc2da0552b...v3.9.5)
- [github.com/tox-dev/pyproject-fmt: v2.25.1 → v2.25.2](https://github.com/tox-dev/pyproject-fmt/compare/v2.25.1...v2.25.2)
- [github.com/astral-sh/ruff-pre-commit: v0.15.20 → v0.15.21](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.20...v0.15.21)
- [github.com/pre-commit/mirrors-mypy: v2.1.0 → v2.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v2.1.0...v2.3.0)

* Apply suggestions from code review

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-07-14 12:46:15 +02:00
Nick Herrig d5cadf526a
Add cookbook for blurring faces with hosted api (#923)
* Add cookbook for blurring faces with hosted api
* fix: correct Colab URL and spelling errors
* fix: strip outputs and remove GPU section
* fix: improve code quality and notebook structure
* fix: align HTML card and prose style

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-11 01:41:49 +02:00
dependabot[bot] 6306b7ca71
⬆️ Bump mistune from 3.2.1 to 3.3.0 in the uv group across 1 directory (#2423)
Bumps the uv group with 1 update in the / directory: [mistune](https://github.com/lepture/mistune).


Updates `mistune` from 3.2.1 to 3.3.0
- [Release notes](https://github.com/lepture/mistune/releases)
- [Changelog](https://github.com/lepture/mistune/blob/main/docs/changes.rst)
- [Commits](https://github.com/lepture/mistune/compare/v3.2.1...v3.3.0)

---
updated-dependencies:
- dependency-name: mistune
  dependency-version: 3.3.0
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 00:57:39 +02:00
Teïlo M 14c3c86e22
Fix hex parser accepting multiple leading prefixes (#2421)
`hex_to_rgba` previously stripped every leading `#`, so invalid inputs such as `##000000` were accepted despite `is_valid_hex` rejecting them.

Remove only one optional prefix and add regression coverage for the minimized failing input.

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-07-11 00:55:51 +02:00
dependabot[bot] 39c015c0ff
⬆️ Bump soupsieve from 2.7 to 2.8.4 in the uv group across 1 directory (#2422)
Bumps the uv group with 1 update in the / directory: [soupsieve](https://github.com/facelessuser/soupsieve).


Updates `soupsieve` from 2.7 to 2.8.4
- [Release notes](https://github.com/facelessuser/soupsieve/releases)
- [Commits](https://github.com/facelessuser/soupsieve/compare/2.7...2.8.4)

---
updated-dependencies:
- dependency-name: soupsieve
  dependency-version: 2.8.4
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 00:50:36 +02:00
OrbisAI Security 9897b0790f
fix: this dependabot configuration does not set a co... in... (#2419)
Automated security fix generated by OrbisAI Security
2026-07-09 22:05:09 +02:00
Abhijith Neil Abraham 287868e171
feature: add KeyPoints.merge() method (#2412)
* feat: add KeyPoints.merge() method
* docs: address review comments on merge docstring and changelog date
* chore: retrigger CI after transient links-check failure
* ci(links-check): accept transient 5xx responses to stop flaky failures
* test(keypoints): add validation for consistent coordinate depth across skeletons
* docs(keypoints): document coordinate-depth ValueError in merge() Raises
* test(keypoints): add docstring, zero-keypoint, and merge+with_nms coverage

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-09 21:37:26 +02:00
Jirka Borovec 5344cb99dd
fix: close out remaining review findings (#2418)
Re-verify remaining supervision review backlog against develop HEAD; most items were already resolved by an intervening commit, only genuinely-open gaps got new fixes.
Fix float32 precision loss in box_iou_batch for large coordinates (GeoTIFF-scale) by accumulating in float64.
Raise ValueError instead of a strippable assert in EvaluationDataset.load_predictions for unknown image ids.
Add HeatMapAnnotator.reset() to clear accumulated heat for annotator reuse.
Add missing coverage: labelme export basename collisions, _greedy_match matcher, metrics.core ABC/enum contracts, metrics.utils.utils pandas guard; remove a global RNG-seed pollution site in a metrics test.
Document the last two undocumented public exports (calculate_masks_centroids, is_compressed_rle) and add usage examples to 17 previously-example-less public functions/classes (NMS/NMM helpers, draw utils, PolygonZoneAnnotator, mask/polygon converters).

* tests: load_predictions ValueError branch + empty-dataset coverage
* fix: box_iou_batch int-dtype overflow, narrow float32 precision claim
* feat: add reset() to TraceAnnotator/DetectionsSmoother, fix docstrings
* docs: fix temp file leak in coco.py docstring, rename misnamed test

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-09 17:53:52 +02:00
Jirka Borovec 8dedc3474d
chore: update pre-release workflow to support additional tag formats (#2420) 2026-07-09 15:19:43 +02:00
dependabot[bot] 1e4489f61b
⬆️ Bump the uv group across 1 directory with 6 updates (#2417)
Bumps the uv group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [pillow](https://github.com/python-pillow/Pillow) | `11.3.0` | `12.2.0` |
| [requests](https://github.com/psf/requests) | `2.32.5` | `2.33.0` |
| [pytest](https://github.com/pytest-dev/pytest) | `8.4.2` | `9.0.3` |
| [bleach](https://github.com/mozilla/bleach) | `6.2.0` | `6.4.0` |
| [jupyter-server](https://github.com/jupyter-server/jupyter_server) | `2.18.0` | `2.20.0` |
| [urllib3](https://github.com/urllib3/urllib3) | `2.6.3` | `2.7.0` |



Updates `pillow` from 11.3.0 to 12.2.0
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/11.3.0...12.2.0)

Updates `requests` from 2.32.5 to 2.33.0
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0)

Updates `pytest` from 8.4.2 to 9.0.3
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.4.2...9.0.3)

Updates `bleach` from 6.2.0 to 6.4.0
- [Changelog](https://github.com/mozilla/bleach/blob/main/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v6.2.0...v6.4.0)

Updates `jupyter-server` from 2.18.0 to 2.20.0
- [Release notes](https://github.com/jupyter-server/jupyter_server/releases)
- [Changelog](https://github.com/jupyter-server/jupyter_server/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter-server/jupyter_server/compare/v2.18.0...v2.20.0)

Updates `urllib3` from 2.6.3 to 2.7.0
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 12.2.0
  dependency-type: direct:production
  dependency-group: uv
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
  dependency-group: uv
- dependency-name: pytest
  dependency-version: 9.0.3
  dependency-type: direct:development
  dependency-group: uv
- dependency-name: bleach
  dependency-version: 6.4.0
  dependency-type: indirect
  dependency-group: uv
- dependency-name: jupyter-server
  dependency-version: 2.20.0
  dependency-type: indirect
  dependency-group: uv
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 00:51:33 +02:00
Jirka Borovec 75023c5f2f
fix: remaining review findings in dataset, docs, and tests (#2416)
- Added `sv.mask_to_roi` as an explicit migration path for exclusive mask bounds
- Fixed COCO, CreateML, and Pascal VOC export validation to reject ambiguous or colliding dataset paths before writing
- Fixed in-memory `DetectionDataset` split and merge behavior
- Fixed `supervision` imports to avoid loading ByteTrack until it is used
- Fixed detection conversion helpers to support coordinate-convention migration while preserving legacy inclusive defaults
- Fixed Azure tag mapping, anchor rounding, and line-zone smoothing to avoid incorrect or ghost detections
- Fixed video processing shutdown handling for timeout and full-queue cases
- Improved downloader, validator, documentation, and regression coverage for the shipped dataset, detection, annotator, image, and video behavior

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-08 23:00:32 +02:00
Jirka Borovec 23a2227ae7
fix(docs): resolve review deprecation follow-ups (#2415)
- Extend active deprecation removals to 0.31.0 and align deprecated API docs, changelog, and warnings.
- Add missing reference docs for VLM, conversion helpers, geometry, metrics extras, and tracker deprecation notices.
- Raise when ImageSink cannot write an image and cover the failure path with a regression test.
- Correct conversion and deprecated docs to match exported names and restore KeyPoints.confidence.
- Add regression coverage for SUPERVISION_DEPRECATION_WARNING precedence and document ImageSink.save_image() failure behavior.

* test: add validation and behavior tests for Color, Position, and polygon approximation adjustments

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-08 14:30:32 +02:00
Jirka Borovec 072f78471c
fix: resolve medium dataset findings (#2408)
- Reinstated NumPy-safe `Classifications` equality and ordered class-list comparisons in dataset equality.
- Restored greedy matching plus size-bucket scoring for Precision, Recall, F1, and MeanAverageRecall, with regression coverage for the medium-object boundary case.
- Filter size-bucket precision, recall, and F1 against target boxes so predictions no longer claim the bucket.
- Preserve confidence order for bucketed mAR@K scoring and return zero when a bucket has no support.
- Add regression coverage for bucket matching, empty-support mAR, top-K limits, and missing-mask errors.

---------

Co-authored-by: Codex <codex@openai.com>
2026-07-08 08:56:34 +02:00
Jirka Borovec 74db9e29ff
fix(utils): normalize timm confidences and verify assets (#2414)
- Convert timm classification logits with softmax so confidence values match the normalized scale used by other classification adapters.
- Verify asset MD5 hashes after fresh downloads and retry once when a payload is corrupted.
- Add focused regressions for timm confidence scaling and asset download integrity paths.
- Convert from_timm outputs to probabilities before applying thresholds and document that existing thresholds may need retuning.
- Add downloader regression coverage for repeated MD5 mismatches so exhausted retries now raise ValueError.

---------

Co-authored-by: Codex <codex@openai.com>
2026-07-07 21:59:23 +02:00
Jirka Borovec dde422703c
fix(tracker): harden ByteTrack edge cases (#2413)
- Keep ByteTrack confidence-threshold boundary detections eligible and avoid impossible activation thresholds above score 1.0.
- Stop mutating caller-owned detections and assignment cost matrices while preserving matched tracker output.
- Filter invalid tensor boxes before Kalman updates and respect minimum consecutive frames on first-frame tensor updates.
- Avoids per-call np.arange allocation by cloning detections with slice(None) while preserving non-mutation behavior.
- Adds regressions for delayed activation on the second consecutive tensor frame and broader invalid-tensor rejection cases.

---------

Co-authored-by: Codex <codex@openai.com>
2026-07-07 21:05:03 +02:00
Jirka Borovec 814a226eba
fix(metrics): harden scoring edge cases (#2411)
- Use COCO 101-point AP averaging in the legacy mAP path so perfect and imperfect curves score consistently.
- Validate confusion-matrix class ids before indexing and preserve target ignore flags in the COCO-style evaluator.
- Keep mAR per-class recall for each max-detection cutoff and cover the scoring fixes with focused regressions.
- Return empty mAR scores with the same max-detection axis as non-empty results.
- Add an empty-input regression covering recall score and per-class result shapes.
- Update the public mAR docstring to describe per-image detection limits.

---------

Co-authored-by: Codex <codex@openai.com>
2026-07-07 18:46:05 +02:00
Jirka Borovec 6a69197177
fix(dataset): harden dataset IO edge cases (#2410)
- Avoid mutating caller-owned Detections during dataset construction and reject invalid class ids with clear ValueErrors.
- Make COCO loading/export tolerant of missing optional metadata, add from_coco(use_iscrowd), and export mask pixel area when needed.
- Let folder-structure and YOLO loading skip common clutter and accept PIL-readable image modes with regression coverage.
- Preserve from_coco positional show_progress compatibility while keeping use_iscrowd keyword-only.
- Filter class-folder loading to image files and export missing COCO mask area from decoded masks.
- Add regression coverage, changelog updates, and types-tqdm for mypy.

---------

Co-authored-by: Codex <codex@openai.com>
2026-07-07 16:29:43 +02:00
Abhijith Neil Abraham 5b4c8b6d0d
fix(key_points): handle empty and numpy index input, keep degenerate skeletons (#2402)
* handle empty and numpy index input, keep degenerate skeletons

- Filter non-finite keypoint coordinates when converting to detections while preserving finite zero-area skeletons.
- Treat zero-length KeyPoints selections as empty and add regression coverage for metadata alignment and selected-index equivalence.

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-07-07 00:02:38 +02:00
Jirka Borovec c3413a8f10
fix(annotators): resolve annotator medium findings (#2407)
- Added deterministic color lookup with flexible palette resolution and clear errors for empty palettes
- Improved annotator and utility handling for warning formatting, plotting imports, and icon caching
- Added validation for keypoint edges, MediaPipe inputs, and VideoSink state

---------

Co-authored-by: Codex <codex@openai.com>
2026-07-06 22:43:35 +02:00
pre-commit-ci[bot] beede8a638
chore(pre_commit): ⬆ pre_commit autoupdate (#2406)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/JoC0de/pre-commit-prettier: v3.8.4 → v3.9.4](https://github.com/JoC0de/pre-commit-prettier/compare/v3.8.4...v3.9.4)

* chore(pre_commit): use SHA instead of version tag for pre-commit-prettier

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-06 21:17:27 +02:00
Jirka Borovec e13090f84b
Fix detection medium review findings (#2400)
- Fixed detection medium findings across adapters, mask non-max merge, sinks, segmentation parsing, LineZone history, and mask ROI handling
- Fixed mask non-max merge deprecation warnings to honor the standard warning opt-out and include version context
- Fixed mask non-max merge validation for invalid IoU thresholds
- Fixed CompactMask non-max merge grouping to update merged mask candidates correctly
- Fixed selected and compacted detections to copy arrays and metadata, preventing mutations from leaking back to source detections
- Fixed LineZone crossing history eviction to tolerate short tracking gaps and evict stale state per tracker/class key
- Fixed semantic segmentation handling to preserve class ID 0
- Improved mask ROI conversion performance by avoiding unnecessary full-frame copies and repeated scans
- Updated JSONSink changelog/docs to document native bool/int/float output while leaving CSVSink unchanged
- Updated detection docstrings for mask parsing, selection copy semantics, validation errors, and argument readability guidance

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-06 19:02:38 +02:00
dependabot[bot] 8fd47a9ec6
⬆️ Bump astral-sh/setup-uv from 8.2.0 to 8.3.0 in the github-actions group (#2405)
Bumps the github-actions group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 8.2.0 to 8.3.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](fac544c07d...d31148d669)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 10:35:25 +02:00
dependabot[bot] ced30b48f7
⬆️ Update pydeprecate requirement from <0.10,>=0.9 to >=0.9,<0.11 (#2404)
Updates the requirements on [pydeprecate](https://github.com/Borda/pyDeprecate) to permit the latest version.
- [Release notes](https://github.com/Borda/pyDeprecate/releases)
- [Changelog](https://github.com/Borda/pyDeprecate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Borda/pyDeprecate/compare/v0.9.0...v0.10.1)

---
updated-dependencies:
- dependency-name: pydeprecate
  dependency-version: 0.10.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 10:34:57 +02:00
Jirka Borovec bd0f44fcfd
fix: resolve remaining High findings from deep codebase review (#2389)
- Fixed crop annotation so overlapping detections sample from the original scene
- Fixed dataset exports to reject basename collisions, including case-insensitive collisions
- Fixed LMM connector mapping to support mirror enum aliases without a hand-maintained dispatch table
- Updated benchmark documentation to install the released inference package with metrics support

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-03 22:58:07 +02:00
Jirka Borovec 78aec073c4
test: cover public API gaps and dataset split (#2399)
* test: cover public API gaps and dataset split
* test(sinks): switch VideoSink to AVI/MJPG and add ImageSink clearing test
* test(detection): add box_non_max_merge 6-column class-separation tests
* test(dataset): drop deprecated dict API and strengthen class-id assertion
* test(public_api): strengthen importability check with getattr

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-03 20:57:42 +02:00
Jirka Borovec eea04b3656
fix(detection): harden model connectors and mask extraction (#2398)
- `from_tensorflow` scaled boxes in place on the array returned by `.numpy()`, which can share memory with the source tensor — corrupting caller data and double-scaling on a repeat call; copy before scaling
- `from_lmm` raised a bare `KeyError` for `MOONDREAM` and `QWEN_3_VL`, which the enum and docstring advertise; map both to their `VLM` members
- `from_deepseek_vl_2` returned a `(0,)`-shaped `xyxy` on empty output, so a zero-detection response crashed the `Detections` constructor; return `(0, 4)` like the other parsers
- `extract_ultralytics_masks` binarized bilinear-resized masks with `> 0`, dilating every mask at object boundaries; threshold at 0.5 to match Ultralytics
- add connector coverage: fake-result shims and round-trip tests (N>1, N=1, empty) for the nine previously untested `from_*` connectors and the `detection/tools/transformers.py` processors; one empty-`segments_info` panoptic case is xfail-marked pending a separate fix

* test(ci-fix): drop deprecated Pillow mode arg from panoptic helpers
* test(coverage): add from_qwen_3_vl end-to-end parametrized tests
* test(quality): harden test isolation, xfail strictness, and kwarg forwarding
* fix(detection): fix class_name empty dtype; annotate mask threshold asymmetry
* chore: ruff-format cleanup (blank lines)

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-03 20:09:57 +02:00
Abhijith Neil Abraham afcf13a6f5
fix(annotators): clip BackgroundOverlayAnnotator boxes to the scene… (#2396)
* fix(annotators): clip BackgroundOverlayAnnotator boxes to the scene before   restoring detection regions
* fix(annotators): use explicit np.int32 cast in BackgroundOverlayAnnotator
* test(annotators): strengthen BackgroundOverlayAnnotator test coverage
* docs(changelog): add Unreleased entry for BackgroundOverlayAnnotator fix

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-03 18:36:25 +02:00
Jirka Borovec 0e1056df19
fix(metrics): count false positives on empty-GT images (#2397)
- Fixed mAP calculation to count predictions on background-only images as false positives
- Fixed all-background mAP inputs to return 0.0 instead of NaN when no ground-truth classes exist
- Updated `from_tensors` documentation to define empty-target background images and their false-positive behavior

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-03 16:17:08 +02:00
Jirka Borovec f173905c8b
fix(dataset): stop split mutation, determinize VOC, guard collisions (#2394)
- `train_test_split` seeded the global `random` module and shuffled the caller's list in place, so `DetectionDataset.split()` reordered its own `image_paths` and polluted process-wide randomness; use a local `random.Random` and shuffle a copy
- Pascal VOC class ids were assigned in `set`-iteration and filesystem-glob order, so the same dataset produced different `class_id` values across runs; sort class names and the loaded file list
- dataset exports keyed output files on basename, silently overwriting when two entries shared a name across directories (common after `merge()`); detect basename collisions and raise
- add regression tests for split determinism, VOC id stability, and export collisions

* fix(dataset): add LabelMe collision guard, hoist pre-flight checks, make guard private
* test(dataset): add collision guard tests for as_yolo, as_pascal_voc, and boundary cases
* docs(dataset): document ValueError raises, fix stale docstrings, add non-mutation guarantee

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-03 15:02:01 +02:00
Jirka Borovec 15dbbb5cb1
fix(annotators): clip crops, fix heatmap wrap, release capture (#2393)
- Fixed annotators to avoid internal deprecation warnings from image overlay usage while preserving the public deprecated wrapper
- Fixed CropAnnotator crashes for partially out-of-frame detections by clipping crops to scene bounds and skipping degenerate boxes
- Fixed HeatMapAnnotator heat disappearing after 256 accumulated frames
- Fixed video frame generation to release the capture when iteration ends early
- Updated documentation for overlay deprecation, crop clipping behavior, and video capture release guarantees

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-03 11:03:45 +02:00
Abhijith Neil Abraham 0d4c3a4fcf
fix(annotators): clip CropAnnotator boxes to the scene before cropping (#2391) 2026-07-03 10:33:49 +02:00
Abhijith Neil Abraham f196e15f26
fix: replace deprecated 2-D np.cross with explicit determinant (#2386)
- Add filterwarnings = ["error::DeprecationWarning"] to pyproject.toml so
  future np.cross 2-D reintroductions fail CI immediately (closes #2384)
- Add test_get_polygon_center_no_deprecation_warning: asserts no
  DeprecationWarning from get_polygon_center (Copilot inline comment)
- Add test_cross_product_no_deprecation_warning: asserts no DeprecationWarning
  from cross_product (Copilot inline comment)
- Add test_cross_product_sign (4 parametrised cases): above / below / on-line /
  offset-start — directly tests the inline determinant correctness
- Improve cross_product docstring: blank line after summary, adds Examples
  section with correct output, notes NumPy 2.0 rationale

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-02 19:01:39 +02:00
Jirka Borovec 99049d84e1
Fix: resolve major complex review (#2388)
- Fixed in-memory dict-form `DetectionDataset` image access, iteration, equality, and merge behavior, with deprecation messaging retained
- Fixed mAP to honor `metric_target` for mask and oriented-bounding-box evaluation, including correct IoU routing, area handling, crowd semantics, and missing-content errors
- Fixed `ConfusionMatrix.plot()` when plotting raw counts with default normalization disabled
- Improved mask mAP crowd handling performance and memory usage
- Updated the count-in-zone guide to use current APIs

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-02 18:14:04 +02:00
Agis Kounelis 8692148c67
fix(detection): make `get_anchors_coordinates` OBB-aware (#2382)
- Fixed `get_anchors_coordinates` to compute anchor positions from oriented bounding boxes when OBB geometry is available, ensuring anchor-based operations (such as zone counting and annotators) align with the rotated object instead of its axis-aligned bounding box.
- Preserved existing behavior for axis-aligned boxes, while continuing to use mask centroids for `CENTER_OF_MASS` anchors when masks are available.
- Improved the `get_anchors_coordinates` documentation with the updated anchor selection order, OBB usage examples, and notes describing OBB winding-order requirements and anchor tie-breaking behavior.

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-02 00:04:26 +02:00
Ruben 058d8fd990
perf(detection): count mask pixels with `count_nonzero` (#2361)
Mask pixel-area counting used `np.sum` — `np.array([np.sum(m) for m in
masks])` in `Detections.area` and `np.sum(mask, axis=(1, 2))` in the metrics
`get_mask_size_category`. For boolean masks `np.count_nonzero` (with no axis)
dispatches to NumPy's SIMD popcount over the raw byte buffer, whereas every
axis-reduction form — `np.sum(..., axis=...)` and even `np.count_nonzero(...,
axis=...)` — falls back to a slower generic reduction. So counting per mask
with `np.count_nonzero` is several times faster than the "obvious" vectorized
sum, while producing bit-identical integer counts.

Route both sites through `np.fromiter((np.count_nonzero(m) for m in masks),
dtype=np.int64, count=len(masks))`. `dtype=np.int64` preserves the documented
`Detections.area` mask-branch dtype on every platform (a bare
`np.array([...])` of Python ints would be int32 on Windows).

Measured ~5x on 640x640 masks (e.g. `Detections.area`, N=300: ~24ms -> ~4ms),
faster across densities. `get_mask_size_category` feeds the size-bucketed
F1/Precision/Recall/mAP/mAR metrics, where it is invoked repeatedly per
dataset. Counts are integer-exact (verified over 400 randomized trials plus
empty / all-true / all-false / 1x1 edge cases).

Adds parity tests for `Detections.area` (dense mask) and
`get_mask_size_category` against an `np.sum` reference.

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 23:22:46 +02:00
Ruben 8f576b02a8
fix(detection): scale `from_tensorflow` boxes by correct axes (#2360)
`Detections.from_tensorflow` scaled the normalized box coordinates by the
wrong image dimensions: the y coordinates (ymin/ymax, columns 0 and 2) were
multiplied by width and the x coordinates (xmin/xmax, columns 1 and 3) by
height. Tensorflow Hub object-detection models emit `detection_boxes` as
normalized `[ymin, xmin, ymax, xmax]`, so y must scale by height and x by
width.

The bug is masked on square images (width == height) but corrupts every
coordinate on the common non-square case — e.g. a box normalized to
`[0.1, 0.2, 0.5, 0.6]` on a 1000x500 image came out as
`[100, 100, 300, 500]` instead of the correct `[200, 50, 600, 250]`.

Swap the two multipliers so y scales by `resolution_wh[1]` (height) and x by
`resolution_wh[0]` (width). Adds a non-square regression test (the connector
was previously untested).

- Expand tensorflow_results arg to document required dict keys and tensor
  shapes so callers know what to pass before getting a KeyError
- Add Note: section documenting the [ymin, xmin, ymax, xmax] normalized
  box format; the inline comment was only visible to code readers
- Fix SOURCE_IMAGE_PATH undefined identifier → "<SOURCE_IMAGE_PATH>"
  string placeholder (consistent with other connector examples in file)

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 22:44:01 +02:00
Ruben a32323d5bd
perf(detection): vectorize `box_iou_batch_with_jaccard` (#2359)
`box_iou_batch_with_jaccard` computed COCO-style Jaccard IoU with a double
Python `for` loop calling a scalar `_jaccard` helper once per (detection,
ground-truth) pair — an O(N*M) per-element pattern in otherwise pure-NumPy
code. It is the inner IoU of `COCOEvaluator._compute_iou`, called once per
(image, category) during mAP evaluation, and is also public API
(`sv.box_iou_batch_with_jaccard`).

Replace the loop with a broadcasted NumPy implementation and drop the now
unused scalar `_jaccard`. The far corners are built as `x2 = x + w` and the
union is associated as `(area_det + area_gt - area_inter) + eps` so the
result is bit-identical to the previous per-pair output (verified to
`max|diff| = 0` over 4000 randomized trials including zero/negative-width
degenerate boxes and crowd flags). Crowd semantics are preserved: a crowd
ground truth uses the detection area as the union.

Speedup scales with batch size — ~1.6x at 5x5, ~27x at 15x60, ~66x at
50x100 — and is faster even at the smallest sizes, so there is no regime
where it regresses. End-to-end COCO mAP results are unchanged (the existing
metrics suite passes without modification).

Adds `TestBoxIouBatchWithJaccard`: parity against an independent per-pair
reference across empty / single / busy / degenerate+crowd batches, the crowd
union semantics, the empty-input contract, and the `is_crowd` length guard.

- Improved COCO-style Jaccard IoU batch evaluation performance while preserving existing results, crowd handling, degenerate-box behavior, and public API semantics
- Fixed empty-input returns to preserve the documented `(len(boxes_detection), len(boxes_true))` output shape
- Fixed `is_crowd` length validation to raise a descriptive `ValueError`
- Updated Jaccard IoU documentation to clarify COCO `[x, y, w, h]` input format, output orientation, and NaN propagation

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 22:43:26 +02:00
Jirka Borovec d590eb6658
perf(detection): keep mixed-mask Detections.merge compact (#2383)
- Improved `Detections.merge()` to preserve `CompactMask` output when merging dense and compact masks by converting dense masks to compact form, avoiding unnecessary full-mask materialization while keeping all-dense and all-compact behavior unchanged.
- Added validation to mixed-mask merging that raises `ValueError` when compact masks have inconsistent image shapes or dense mask dimensions do not match the compact mask image size.
- Added the public `CompactMask.image_shape` property for safe access to compact mask dimensions.
- Updated `Detections.merge()` documentation to describe mixed-mask merge behavior, output types, validation errors, the lossy dense-to-compact conversion outside detection bounding boxes, and that NMS/NMM pairwise operations do not preserve `CompactMask`.
- Added a comprehensive "Use Compact Masks" how-to guide covering compact mask ingestion, inference, annotator mask requirements, and mixed-mask merging, and integrated it into the documentation navigation.

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-01 21:01:29 +02:00
Jirka Borovec 04858a2727
feat: declare annotator mask requirements (#2370)
- Added a `requires_mask` flag to annotators so integrations can determine whether masks must be materialized before annotation.
- Updated mask-only annotators to declare `requires_mask=True`, while mask-optional annotators explicitly declare `requires_mask=False`, including compatibility support for `ComparisonAnnotator`.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 19:34:21 +02:00
Jirka Borovec 3f816c0b6c
perf: avoid compact mask materialization in polygon annotator (#2369)
Use CompactMask crops for polygon extraction and offset crop-local contours back into image coordinates.

Add a regression test covering tight crops, disconnected contours, empty masks, dense parity, and no integer CompactMask indexing.

- Add `_iter_mask_crops()` helper under shared-utilities seam: yields
  (detection_idx, mask_or_crop, offset_or_None) encapsulating the
  CompactMask vs dense isinstance dispatch in one place (eliminates 4th
  inline copy of the same pattern; see _paint_masks_by_area)
- Refactor PolygonAnnotator.annotate() to consume _iter_mask_crops;
  removes the 9-line inline dispatch block
- Add TODO comment at isinstance site flagging MaskLike Protocol as
  follow-up (separate PR; review item #3 self-resolved)
- Extend PolygonAnnotator.annotate() docstring with Note section
  covering CompactMask fast path and offset semantics
- Add N=0 empty CompactMask test (scene unchanged, no error)
- Add all-False mask test (no polygons drawn, documents boundary behavior)
- Add N=1 single-detection parity test (CompactMask == dense)
- Add float xyxy truncation test (sub-pixel xyxy → same output as int xyxy)
- Add disjoint-contour coordinate assertion: both blobs painted at correct
  image-space coords after crop→image offset translation
- Add PolygonAnnotator to TestCompactMaskParity.test_annotator_compact_mask_matches_dense_mask parametrize

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-01 18:05:46 +02:00
Jirka Borovec 3ecd5d0744
Optimize mask annotation ROI blending (#2368)
- Improved MaskAnnotator performance by blending mask overlays only within the affected ROI while preserving dense mask and CompactMask rendering behavior
- Fixed all-false masks to skip unnecessary ROI blending
- Updated compact-mask benchmark output to clarify annotation speedup reporting

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 15:55:21 +02:00
Jirka Borovec 098f5315b5
chore: update `uv.lock` to require Python 3.10+ (#2381) 2026-07-01 13:28:01 +02:00
Jirka Borovec beb047095f
feat: add compact RLE mask ingestion (#2367)
- Added compact COCO RLE mask ingestion with a `CompactMask` representation and optional compact mask parsing during inference for substantially lower memory usage on sparse segmentation results.
- Added `Detections.to_compact_masks()` to convert existing dense masks into compact masks while preserving detection and collection metadata.
- Improved compact mask decoding performance with cropped RLE processing, batched decoding on the fast path, vectorized decoding for small images, optimized RLE traversal, and faster delta decoding.
- Improved mask metrics to operate directly on `CompactMask` instances, preserving the compact representation while producing results equivalent to dense masks.
- Fixed mixed-modality inference handling by keeping detections and masks aligned, isolating malformed RLE failures to individual predictions where possible, and falling back safely when decoding cannot be completed.
- Fixed compact mask conversion and parsing to preserve dense-mask pixel content across public parsing and slicing paths, while correctly documenting and applying the intended bbox-cropping behavior for compact COCO RLE masks.
- Improved COCO RLE validation with checks for malformed payloads, invalid dimensions, count overflows, image size limits, count-sum mismatches, bounding-box mismatches, and safe fallback behavior for incompatible mask sizes.
- Added inference benchmarks and documentation demonstrating the memory and inference-time characteristics of compact masks, including guidance on their performance tradeoffs and behavior.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-01 12:50:40 +02:00
Jirka Borovec 4624dde770
fix(metrics): replace np.unique matching with greedy algorithm (#2380)
The two-pass np.unique deduplication in _match_detection_batch dropped
valid TP assignments when a prediction's best-IoU target was already
claimed by a higher-confidence prediction. The greedy one-pass algorithm
(sort by IoU desc, assign if neither target nor pred already matched)
was already used in _split_detections_by_outcome and
ConfusionMatrix.evaluate_detection_batch but missing from Recall,
F1Score, Precision, MeanAverageRecall, and MeanAveragePrecision.

- Fix all five _match_detection_batch implementations
- Add regression tests reproducing the issue #2378 example in each
  affected metric class (IoU matrix [[1.0, 0.667], [0.333, 0.538]])
- Add kind='stable' to np.argsort in _match_detection_batch across all 5 metric
  implementations (Recall, Precision, F1Score, MeanAverageRecall, deprecated
  MeanAveragePrecision) to ensure deterministic TP assignment when IoU values tie
- Add test_greedy_matching_two_valid_pairs to TestDetectionMetrics covering the
  deprecated MeanAveragePrecision._match_detection_batch with the issue #2378 IoU
  matrix, closing the one missing regression test flagged by /review

* refactor(metrics): extract _greedy_match helper to eliminate 5x duplication

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 12:30:09 +02:00
Agis Kounelis 97f5c0ca53
perf(annotators): skip corner circles when drawing square label backgrounds (#2346)
Both rounded-rectangle helpers (LabelAnnotator.draw_rounded_rectangle and the
public draw_rounded_rectangle in draw/utils) always drew two rectangles plus
four corner circles, even when border_radius is 0, which is the default for
LabelAnnotator and VertexLabelAnnotator. With a zero radius that is six cv2
calls per label per frame (the four circles are zero-radius no-ops) where one
fill rectangle does the same thing.

Add a square-corner fast path to both helpers. Output is pixel identical; only
the redundant calls go away. On a 1080p frame with 100 labels LabelAnnotator
drops from ~2.1 ms to ~1.3 ms (about 1.6x), and the rounded-rectangle call
itself is ~2.8x faster at radius 0. The radius > 0 path is unchanged.

Adds tests pinning square output to a plain rectangle for both helpers (the
public draw/utils function had no tests before).

- Rename LabelAnnotator.draw_rounded_rectangle to _draw_rounded_rectangle
  (accidentally public static method — now signals internal)
- Expand draw/utils.py border_radius docstring: document <= 0 and
  clamp-to-zero fast-path behaviour
- Add crash-era comment to both test files: border_radius < 0 previously
  raised cv2.error; fast path silently draws square corners instead
- Add clamped-to-zero test in both test files: positive radius on a
  1px-wide box clamps to 0 and triggers the fast path
- Strengthen positive-radius assertion: full center-row check + all four
  corners unpainted (replaces two-pixel spot check)
- Add pytest.param(id=) slugs to all parametrize decorators per
  CONTRIBUTING.md convention
- Add Google-style docstring with Args, Returns, Example to
  `LabelAnnotator.draw_rounded_rectangle` (was undocumented @staticmethod)
- Rename `testdraw_*` → `test_draw_*` in `TestLabelAnnotator` to restore
  consistent test naming broken by the earlier private-rename commit
- Expand `draw/utils.draw_rounded_rectangle` border_radius docstring:
  note that negative values previously raised `cv2.error` and now draw
  square corners silently; drop "as a fast path" implementation detail
- Add `Example:` block to `draw/utils.draw_rounded_rectangle`

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 01:25:25 +02:00
Agis Kounelis e1b7a16101
fix(detection): keep `from_inference` aligned on partial masks (#2362)
process_roboflow_result appended a mask only for predictions carrying one
(RLE or polygon), while xyxy/confidence/class_id were appended for every
prediction. A result mixing masked and box-only predictions (e.g. a
segmentation batch where one polygon is empty) produced a mask array shorter
than the boxes, so Detections.from_inference raised a shape-mismatch error.

Append None for box-only predictions and build the mask array only when every
prediction has a mask, otherwise drop masks to preserve alignment, mirroring
the tracker_id handling. Fully-masked and mask-free results are unchanged.

- Update `masks` Returns clause to document partial-drop case and corrupt-RLE blast radius (D1+C1)
- Remove stale "known limitation" note from from_inference docstring; describe actual behavior (D2)
- Extract _all_present_or_none() helper; eliminate duplicated partial-drop-warn pattern (S1)

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-07-01 00:52:57 +02:00
pre-commit-ci[bot] 15f56dea30
chore(pre_commit): ⬆ pre_commit autoupdate (#2375)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/JoC0de/pre-commit-prettier: v3.8.4 → v3.9.3](https://github.com/JoC0de/pre-commit-prettier/compare/v3.8.4...v3.9.3)
- [github.com/tox-dev/pyproject-fmt: v2.25.0 → v2.25.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.25.0...v2.25.1)
- [github.com/astral-sh/ruff-pre-commit: v0.15.18 → v0.15.20](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.18...v0.15.20)

* fix(pre_commit): 🎨 auto format pre-commit hooks
* fix(pyproject): restore valid mypy TOML overrides
* chore: update pyproject.toml to exclude examples and tests in mypy configuration

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
2026-06-30 23:02:13 +02:00
Jirka Borovec 934da124f5
chore(typing): Add explicit selection helpers (#2373)
* Add explicit selection helpers
* improve typing in detection metrics and update pre-commit dependencies

- Add explicit type annotation for `panel_array` in `_draw_panel` function.
- Update `.pre-commit-config.yaml` to include `tomli>=2.0.1` as an additional dependency for `pyproject-fmt`.

---------

Co-authored-by: Codex <codex@openai.com>
2026-06-29 15:39:58 +02:00
pre-commit-ci[bot] ad2c75021f
chore(pre_commit): ⬆ pre_commit autoupdate (#2248)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/JoC0de/pre-commit-prettier: v3.8.3 → v3.8.4](https://github.com/JoC0de/pre-commit-prettier/compare/v3.8.3...v3.8.4)
- [github.com/tox-dev/pyproject-fmt: v2.21.1 → v2.25.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.21.1...v2.25.0)
- [github.com/astral-sh/ruff-pre-commit: v0.15.12 → v0.15.18](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.12...v0.15.18)
- [github.com/pre-commit/mirrors-mypy: v1.20.2 → v2.1.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.20.2...v2.1.0)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-06-29 14:52:18 +02:00
Jirka Borovec 0a95bae8a8
chore: bump minimum Python to 3.10 (#2260)
- Drop Python 3.9 from CI test matrix
- requires-python = ">=3.10" in pyproject.toml
- ruff target-version py39 → py310
- mypy python_version 3.9 → 3.10
- Remove Python 3.9 classifier

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-29 14:45:30 +02:00
dependabot[bot] 5a90113b15
⬆️ Update mkdocstrings-python requirement from <2,>=1.10.9 to >=1.10.9,<3 (#2366)
* ⬆️ Update mkdocstrings-python requirement

Updates the requirements on [mkdocstrings-python](https://github.com/mkdocstrings/python) to permit the latest version.
- [Release notes](https://github.com/mkdocstrings/python/releases)
- [Changelog](https://github.com/mkdocstrings/python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/python/compare/1.10.9...2.0.5)

---
updated-dependencies:
- dependency-name: mkdocstrings-python
  dependency-version: 2.0.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(pre_commit): 🎨 auto format pre-commit hooks

* Update docs dependency stack for mkdocstrings 2

Move mkdocstrings-python handler options to their 2.x-compatible location, scope the docs dependency group to Python 3.10+, and keep the general pytest matrix from installing docs dependencies.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-06-29 11:20:32 +02:00
dependabot[bot] 58aacd3f74
⬆️ Update pytest requirement from <9,>=7.2.2 to >=7.2.2,<10 (#2363)
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.2.2...9.1.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.1.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-06-29 10:52:52 +02:00
dependabot[bot] b5f0752f41
⬆️ Update build requirement from <1.5,>=0.10 to >=0.10,<1.6 (#2365)
* ⬆️ Update build requirement from <1.5,>=0.10 to >=0.10,<1.6

Updates the requirements on [build](https://github.com/pypa/build) to permit the latest version.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/0.10.0...1.5.0)

---
updated-dependencies:
- dependency-name: build
  dependency-version: 1.5.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Apply suggestions from code review

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>

* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-29 10:48:06 +02:00
dependabot[bot] 939097bdce
⬆️ Update mkdocstrings requirement from <0.31,>=0.25.2 to >=0.25.2,<1.1 (#2364)
* ⬆️ Update mkdocstrings requirement

Updates the requirements on [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) to permit the latest version.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.25.2...1.0.4)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-version: 1.0.4
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Apply suggestions from code review

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>

* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-29 10:43:02 +02:00
Jirka Borovec 46fdd5ceba
fix(pre-commit): pin mypy hook python version (#2371)
Co-authored-by: Codex <codex@openai.com>
2026-06-29 10:19:51 +02:00
Jirka Borovec 09b21992c5
chore: port example typing refinements (#2358)
Co-authored-by: Codex <codex@openai.com>
2026-06-27 09:08:54 +02:00
Jirka Borovec 10b538373b
chore: postpone annotations and add validation refinements (#2357)
Add postponed annotations to test modules and modernize one test helper annotation for Python 3.9-compatible collection.

---------

Co-authored-by: Codex <codex@openai.com>
2026-06-27 08:36:26 +02:00
Jirka Borovec f34a940c0a
chore: postpone annotations in src typing (#2356)
* chore: postpone annotations in src typing
* chore: refine py310 source typing
* chore: add TypeGuard import for typing_extensions under TYPE_CHECKING
* chore: port source typing refinements
* chore(detection): update confusion matrix examples to use integer dtype
* chore: replace pipe-based typing with Union in key points module
* chore(detection): replace pipe-based typing with Union for compatibility
* chore: remove untyped import ignores for deprecated library

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-27 07:23:05 +02:00
jirka 4b867f282b chore: update .gitignore to include additional directories and files 2026-06-26 22:29:15 +02:00
Saif Khan 57bb5e7e8b
Add adaptive TP/FP/FN validation mosaic export (#2271)
- remove top-level cv2/annotator imports; lazy-load inside rendering functions
- remove save_result_images bool; save_directory_path is now keyword-only after metric_target
- drop hardcoded result/ subdirectory from benchmark output path
- propagate metric_target into _split_detections_by_outcome for correct OBB IoU dispatch
- add filename collision UserWarning in benchmark loop
- remove dead/unreachable combined None-check in _split_detections_by_outcome
- add Google-style docstrings to all 5 new private visualization functions
- add TestSplitDetectionsByOutcome covering 7 edge cases (empty inputs, cross-class, confidence-None)
- fix FP/FN pixel assertions to check interior box pixels rather than border/title regions
- fix benchmark_a_model.md: full panel names, add Visual Benchmarking section, update API examples

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-26 21:33:49 +02:00
LinasKo 2aa43bceab
Inference slicer batching (#1239)
- Port OBB sequential fallback to batch path (same guard as single-image path)
- Port compact_masks RLE compression into _run_callback_batch
- Port out-of-slice-bounds SupervisionWarnings to _run_callback_batch
- Add list-type and length-match guard before zip in _run_callback_batch
- Add OBB-with-thread_workers warning to batch path
- Widen callback param annotation to union of single-image and batch signatures
- Update class docstring: dual callback contract, batch_size arg, new Raises, usage example
- Remove redundant list() re-wraps in batch execution path
- Add TestInferenceSlicerBatch: 12 parametrised tests covering all new batch behaviours

---------

Co-authored-by: Linas Kondrackis <linas.ko+dev@skiff.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-26 19:15:54 +02:00
Vikas saini 7239af5048
Refactor/remove asserts annotators image (#2354)
- ValueError → TypeError in all three ensure_*_image_for_* decorators (conversion.py): the decorator intercepts non-ndarray/PIL inputs before the wrapped body runs, so the inner raises were unreachable; fixing at the decorator level fixes all annotators at once
- Remove 5 dead isinstance guards from key_points/annotators.py and 4 from utils/image.py (all now covered by the decorator fix)
- Remove dead assert isinstance(scene, Image.Image) from RichLabelAnnotator.annotate (ensure_pil_image_for_class_method guarantees PIL.Image before inner body)
- Add @ensure_cv2_image_for_class_method to VertexLabelAnnotator.annotate for PIL parity (only decorated annotator missing it)
- Rewrite tests to call public API directly (no __wrapped__ bypass); replace with TestAnnotatorInputValidation class (parametrized, IDs, AAA) + parametrized test_image_utils_wrong_type_raises
- Add Raises: TypeError sections to all 6 annotator .annotate() docstrings, 4 image util docstrings, and 3 decorator docstrings

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-26 17:51:01 +02:00
Agis Kounelis 27ba0aa92f
fix(detection): do not crash `from_inference` on partial `tracker_id` (#2353)
process_roboflow_result appended tracker_id only for predictions that carried
one, while xyxy/confidence/class_id were appended for every prediction. A
result where some predictions are tracked and others are not produced a
tracker_id array shorter than the boxes, so Detections.from_inference raised
"tracker_id must be a 1D np.ndarray with shape (N,)".

Collect tracker_id for every prediction (None when absent) and build the array
only when all detections carry one, otherwise leave it None. Fully-tracked and
untracked results are unchanged.

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-26 16:58:52 +02:00
jirka 25d98c85f3 test(dataset): refactor labelme tests with parametrize
- Merge test_polygon_shapes_require_image_dims and test_force_masks_requires_image_dims into test_requires_image_dims_when_mask_needed with polygon-shape and force-masks-rectangle ids

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-25 17:18:25 +02:00
Madhav-C 2169192492
feat(dataset): add LabelMe format support to DetectionDataset (#2299)
- Added LabelMe import and export support for DetectionDataset, including per-image JSON loading/saving alongside existing dataset formats
- Added LabelMe rectangle-to-box and polygon-to-mask conversion, with rectangle masks available when mask output is requested or polygon annotations are present
- Added LabelMe path-safety protections by resolving image paths by basename and rejecting unsafe or ambiguous image references
- Added validation for duplicate image basenames, malformed shape points, missing imagePath values, and invalid class IDs during LabelMe load/export
- Improved LabelMe handling by warning and skipping unsupported shape types
- Updated documentation and changelog with LabelMe workflow examples and supported-format references

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-06-25 17:07:39 +02:00
jirka a3a860cf3f test(dataset): refactor createml tests with parametrize
- Merge 4 separate malformed-annotation raises into parametrized test_raises_on_malformed_annotation
- Merge 3 path-security raises into parametrized test_raises_on_unsafe_image_path (lambda captures per-case images_directory_path)
- Merge 3 malformed-JSON raises into parametrized test_raises_on_malformed_json
- Merge 2 save/load round-trip tests into parametrized test_save_load_round_trip
- Drop unused DoesNotRaise/ExitStack and exception param from test_converts_annotations

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-25 15:43:02 +02:00
Madhav-C 9ff41b9706
feat(dataset): add CreateML format support to DetectionDataset (#2284)
- Added CreateML import/export support for detection datasets, including pixel-space center/width/height box conversion, class-name inference, global class-id consistency, and image path safety validation.
- Added optional progress bars for CreateML loading, exporting, and image saving.
- Improved CreateML validation with clear errors for malformed JSON, missing fields, duplicate images, null annotations, and unsafe image paths.
- Updated dataset documentation and references to include CreateML workflows and `from_createml` / `as_createml` usage.

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-06-25 15:26:34 +02:00
jirka 44b62cd164 test(detection): refactor GeoTIFF slicer tests with fixtures and parametrize
- Convert `_fixed_detection_callback` module fn to `fixed_detection_callback` fixture
- Add `make_raster_dataset` factory fixture replacing direct `_FakeRasterDataset(...)` calls in 8 tests
- Add `make_recording_callback` factory fixture replacing duplicated closure pattern
- Merge `test_windowed_raster_reads_correct_window_content` and `test_windowed_raster_matches_in_memory_array_with_overlap` into single parametrized `test_raster_tiles_match_array_tiles[no-overlap|with-overlap]`
- Drop verbose section divider comments

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-25 14:24:54 +02:00
Madhav-C a179d9120f
feat(detection): support windowed GeoTIFF reads in InferenceSlicer (#2281)
InferenceSlicer can now accept an open rasterio-style dataset and read each tile via a windowed read instead of loading the whole image into memory, enabling tiled inference on multi-GB aerial/drone GeoTIFFs. Detection is duck-typed so rasterio stays an optional dependency (supervision[geotiff]) and the library imports no rasterio symbols. Adds CRS projected validation and tests. Closes #2027.

- Add threading.Lock around raster.read() in _run_callback to prevent
  data race when thread_workers > 1 shares a DatasetReader (GDAL releases
  GIL inside GDALRasterIO — reads are genuinely concurrent C code)
- Return TypeGuard[WindowedRasterDataset] from _is_windowed_raster;
  TYPE_CHECKING guard imports typing_extensions for Python 3.9 compat
- Add @runtime_checkable to WindowedRasterDataset Protocol; crs typed
  as object|None; guard .is_projected via getattr(..., True)
- Extract _get_resolution_wh and _apply_overlap_filter helpers from
  __call__ to bring cyclomatic complexity under PLR0912 limit (16 → ~4)
- Widen callback type to Callable[[NDArray[Any]], Detections] to accept
  any dtype (uint16 raster tiles are not NDArray[uint8])
- Add Raises section to __call__ docstring for geographic CRS ValueError
- Add one-line summary to move_detections docstring
- Export WindowedRasterDataset from sv.__init__
- Move changelog entry from 0.29.1 (released) to UnReleased
- Add comment explaining rasterio>=1.3 lower bound in pyproject.toml
- Restructure tests: class grouping, parametrize CRS cases, add
  docstrings; add compact_masks, thread_workers>1, single-band,
  single-tile test cases


---------

Co-authored-by: madhavcodez <madhavcodez@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-25 14:07:42 +02:00
Dylan Parsons 14f6f245c8
Docs/convert detections doctests (#2351)
* docs: convert Detections.empty and Detections.merge examples to doctests

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Dylan Parsons <dylanparsons@users.noreply.github.com>
2026-06-24 08:19:04 +02:00
jirka 3e610a0558 Bump version to `0.30.0.dev` 2026-06-23 22:14:07 +02:00
Murillo Rodrigues af6365acfb
feat: add show_progress to dataset load/save operations (#2275)
- Added optional progress bars for dataset loading and saving across detection and classification datasets via a `show_progress` parameter, covering COCO, YOLO, Pascal VOC, and folder-structure workflows while remaining disabled by default for backward compatibility.
- Improved progress reporting accuracy by providing total item counts to progress bars during dataset loading operations.
- Updated dataset save and load APIs and documentation to consistently support progress display options across formats.
- Fixed documentation examples and doctests to remain runnable without requiring progress-bar-specific arguments.
- Fixed progress-bar integration and test coverage to ensure correct behavior for all supported dataset formats, save/load paths, and default no-progress behavior when `show_progress` is not enabled.

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-23 22:04:49 +02:00
Jirka Borovec 49ecac0376 Releasing supervision `0.29.1` (#2350) 2026-06-23 21:53:06 +02:00
dependabot[bot] 9251893bd9
⬆️ Bump jupyterlab from 4.5.7 to 4.5.9 in the uv group across 1 directory (#2348)
---
updated-dependencies:
- dependency-name: jupyterlab
  dependency-version: 4.5.9
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 16:17:44 +02:00
dependabot[bot] ccd7098f2f
⬆️ Bump actions/checkout from 6 to 7 in the github-actions group (#2347)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 08:55:18 +02:00
jirka 07d182be56 docs: refine Table of Contents formatting with collapsible details 2026-06-19 09:21:02 +02:00
Vikas saini cca3c14911
Docs: Refactor README formatting and fix pre-commit hook (#2316)
Updated README to improve formatting and structure.
Updated links, section headers, and example code paths in README.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-18 15:42:25 +02:00
Ruben 44546a13f2
fix(vlm): handle malformed Gemini/Qwen model output without crashing (#2342)
Two ways the VLM parsers crashed on adversarial model output instead of
degrading gracefully (the contract they already honor for invalid JSON):

1. Gemini 2.5: a mask value that is not a 'data:image/png;base64,' string
   appended an empty mask and then 'continue'd, skipping the confidence
   handler at the bottom of the loop. The item's box was recorded but its
   confidence was not, so the confidence array ended up shorter than xyxy
   and Detections.from_vlm raised a shape ValueError. Replaced the
   'continue' with an if/else so the confidence handler always runs.

2. Gemini 2.0 / Gemini 2.5 / Qwen 2.5: valid JSON whose top level is not a
   list, or whose elements are not dicts (e.g. '[1, 2, 3]'), raised
   TypeError from the 'key not in item' membership test. Added a top-level
   list guard (Gemini 2.0/2.5; Qwen already had one) and a per-element
   dict guard so wrong-shaped JSON degrades to empty Detections.

Add regression tests for the mask/confidence alignment and for graceful
degradation across all three parsers.

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-18 15:31:31 +02:00
Ruben 4b60bbc9cc
fix(dataset): stop Pascal VOC export from mutating source detections (#2341)
object_to_pascal_voc applied the 1-index offset in place (xyxy += 1).
Because Detections.__iter__ yields each row of xyxy as a view sharing
memory with detections.xyxy, detections_to_pascal_voc wrote the +1 shift
straight back into the caller's array. A single export shifted every box
by +1px; a second export compounded it, producing wrong XML. A single
export-then-reload happened to round-trip because from_pascal_voc
subtracts 1, which is why no test caught it.

Rebind to a new array (xyxy = xyxy + 1) instead of mutating in place.
On-disk output is unchanged; the source detections are left intact.

Add regression tests asserting object_to_pascal_voc does not mutate its
inputs and that two consecutive exports are identical and leave xyxy
unchanged.

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-18 15:29:37 +02:00
Agis Kounelis 6918d44190
docs(cookbooks): add Oriented Bounding Boxes cookbook (#2314)
- Added an end-to-end cookbook focused on oriented bounding boxes (OBB), demonstrating how OBB detections differ from axis-aligned boxes, why oriented overlap and NMS matter, how to use footprint-based filtering with `Detections.area`, and how to export annotations in YOLO OBB format.
- Added visual examples that clearly compare axis-aligned and oriented boxes, including a close-up showing how axis-aligned envelopes can significantly overestimate object footprints for angled objects.
- Improved the cookbook narrative to center on the practical consequences of using oriented versus axis-aligned boxes, including tighter localization and more appropriate NMS behavior for densely packed, rotated objects.
- Updated cookbook references, naming, dependency versions, image attribution, and changelog links to align with the released 0.29.0 documentation.
- Fixed the 0.29.0 changelog by removing a duplicate `Detections.area` entry and keeping the more accurate correctness-fix classification.

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-18 13:33:30 +02:00
Agis Kounelis a48532f6f5
perf(annotators): share mask painting and give HaloAnnotator the CompactMask path (#2339)
MaskAnnotator paints CompactMask detections into their bounding-box crop, but
HaloAnnotator never got that path: it materialized every mask full-frame, painted
via full-frame boolean indexing, and built its foreground mask as a 2M-element
Python list per frame. Extract the shared compact/dense painting into a single
_paint_masks_by_area helper used by both annotators. On a 1080p frame with 30
masks, HaloAnnotator on CompactMask runs about 4x faster; output is unchanged.

- Replace in-place `union` param with `collect_union: bool` return value;
  HaloAnnotator now captures the returned union array
- Add Google-style Args/Returns to `_paint_masks_by_area` docstring
- Fix HaloAnnotator.annotate() example (was maskless → no-op) and scene arg wording
- Add section comment above shared helper for discoverability
- Add union accumulation tests (dense + CompactMask paths via collect_union=True)
- Add test documenting out-of-bbox True-pixel divergence between compact and dense
- Add image-edge bbox test for CompactMask annotators
- Move helper tests into TestPaintMasksByArea and TestCompactMaskParity classes
- Rename test_annotate_with_empty_masks → test_annotate_with_all_false_mask

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-18 12:37:11 +02:00
Agis Kounelis 11a586c133
perf(detection): compute mask IoU via matmul instead of an (N, M, H, W) intermediate (#2323)
- Reimplemented dense mask IoU/IoS computation using matrix multiplication on flattened masks instead of constructing a full `(N, M, H, W)` overlap tensor
- Significantly reduced memory usage and improved performance for large mask sets while preserving identical IoU/IoS results
- Added automatic precision handling for large masks to keep intersection and area counts numerically accurate
- Improved memory-limit handling and chunking logic to reflect actual matmul memory usage
- Added validation that compared mask sets share the same spatial dimensions
- Added validation for invalid mask tensor ranks and input shapes
- Added safe handling for empty-mask inputs
- Added warnings when inputs exceed the minimum memory footprint that chunking cannot reduce
- Fixed large-mask area calculations that could produce incorrect IoU values
- Suppressed spurious runtime warnings during valid matrix-multiplication computations
- Added regression coverage for correctness, chunking, rectangular matrices, empty inputs, shape mismatches, large-mask precision, and memory-limit edge cases

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-18 10:45:56 +02:00
Piotr Skalski 31e84f7909
feat: add `KeyPoints.with_nms()` method (#2338)
* feat: add with_nms() method to KeyPoints class Derive axis-aligned bounding boxes from valid keypoints and delegate to box_non_max_suppression for filtering. Requires detection_confidence; supports class-aware and class-agnostic modes.

- Add overlap_metric: OverlapMetric = OverlapMetric.IOU param to KeyPoints.with_nms() for API parity with Detections.with_nms()
- Integrate self.visible into keypoint validity: valid = valid & self.visible when visible is not None
- Pass overlap_metric through to box_non_max_suppression
- Fix docstring: add Defaults to for threshold/class_agnostic, threshold range constraint, overlap_metric arg
- Add UnReleased changelog entry
- Add 5 new test cases: all-zero-skeleton-passes-through, visible-mask-excludes-keypoints-from-bbox, single-valid-keypoint-zero-area-bbox, threshold boundary 0.0/1.0
- Add missing raises test: no-detection-confidence-class-agnostic
- Update `with_nms` method to raise `ValueError` instead of `AssertionError` for missing required fields (`detection_confidence`, `class_id` when `class_agnostic=False`).
- Adjust corresponding test to check for `ValueError` with match argument.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-18 10:42:43 +02:00
jirka 7e765eb054 docs: reformat entries in changelog for improved readability 2026-06-17 22:41:17 +02:00
Ruben 393ff52954
fix(json_sink): serialize NumPy scalars in `custom_data` (#2334)
- Fixed `JSONSink` to correctly serialize NumPy scalar values stored in `custom_data`
- Added JSON serialization support for NumPy arrays by converting them to standard JSON-compatible lists
- Prevented buffered export failures caused by non-serializable NumPy values during `json.dump`
- Added regression coverage for multiple NumPy scalar types, NumPy arrays, and unsupported-object error handling
- Updated documentation and changelog to describe NumPy serialization behavior in `JSONSink`

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-17 22:27:03 +02:00
Ruben c9962c9262
fix(smoother): handle detections without confidence (#2333)
- Fixed `DetectionsSmoother` to work with detections that have no confidence scores
- Changed confidence aggregation to average only the confidence values that are present, leaving confidence as `None` when no values exist
- Fixed smoothing of mixed-confidence tracks (some frames with confidence, some without) while preserving available confidence information
- Fixed crashes when merging smoothed tracks that disagree on confidence availability by normalizing confidence fields before merge
- Added regression coverage for no-confidence, mixed-confidence, multi-track, full-window, and tracker-id-missing scenarios
- Updated documentation and changelog to reflect the new confidence-handling behavior

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-17 21:16:43 +02:00
Ruben 04b6d45ea2
fix(polygons): honor target point count in `approximate_polygon` (#2332)
- Fixed `approximate_polygon` so the returned polygon respects the requested point-count reduction target instead of returning an over-budget approximation
- Preserved the minimum valid polygon size (3 points) while simplifying polygons
- Added validation requiring `epsilon_step > 0` to prevent invalid and non-terminating configurations
- Added regression tests covering target-point budgeting, polygon validity, invalid percentage values, and invalid `epsilon_step` values
- Improved documentation with clearer behavior guarantees, validation rules, examples, and edge-case explanations

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-17 21:11:26 +02:00
Ruben 2854932965
fix(metrics): count false positives on background images and absent classes (#2331)
- Fixed Precision and F1Score metrics to correctly count false positives on images with no ground-truth objects
- Fixed Precision and F1Score metrics to include prediction-only classes when computing class statistics and confusion-matrix aggregates
- Corrected MICRO and MACRO averaging so false positives from absent classes affect the score as expected
- Preserved WEIGHTED averaging behavior by weighting only classes with ground-truth support
- Added safeguards for all-background evaluation batches, returning stable zero-valued weighted scores when no support exists
- Fixed handling of predictions with `class_id=None` in background-only evaluation paths
- Added regression coverage for background-image false positives, absent-class predictions, averaging modes, and zero-support edge cases
- Updated metric documentation and result metadata to reflect that tracked classes now include prediction-only classes

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-17 19:46:10 +02:00
Jirka Borovec bcc785fbb5
fix(ci): clear latest alias before deploying docs to `release/latest` (#2340)
mike deploy fails when target name exists as an alias; delete it first.

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-17 19:36:25 +02:00
Ruben c0df72b84a
perf: vectorize `mask_to_xyxy` and `KeyPoints.as_detections` (bit-identical) (#2330)
- Vectorized `mask_to_xyxy` by replacing per-mask pixel scans with batched occupancy-profile reductions, yielding large speedups while preserving identical outputs
- Added direct test coverage for `mask_to_xyxy`, including edge and corner-pixel mask cases
- Vectorized `KeyPoints.as_detections` by computing all bounding boxes in a single batch operation instead of constructing and merging per-skeleton `Detections`
- Vectorized keypoint-confidence aggregation in `KeyPoints.as_detections` using NumPy reductions
- Preserved exact output behavior for bounding boxes, confidence values, class IDs, metadata, selected-keypoint subsets, and missing-keypoint handling
- Added regression coverage for selected-keypoint indexing, mixed valid/invalid skeleton batches, detection-confidence paths, and confidence aggregation behavior
- Fixed strict mypy typing issues introduced by the vectorized implementations
- Improved documentation for `mask_to_xyxy` and `selected_keypoint_indices` behavior

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-17 18:58:47 +02:00
Piotr Skalski 15cf80abac
fix: accept deprecated `confidence` kwarg in `KeyPoints()` constructor (#2335)
- Move conflict ValueError before warn_deprecated so stray warning not
  emitted on invalid calls (both kwargs passed)
- Add backtick-quoted version numbers to deprecation message for style
  consistency with other warn_deprecated calls in the same class
- Make ValueError message actionable: name deprecated param and remedy
- Add Google-style docstring to __init__: Args + Raises sections
- Fix pre-existing mypy error in detection/core.py (_merge_obb_corners
  had bare np.ndarray without type args)
- Add docstrings to all test methods in TestDeprecatedConfidenceConstructor
- Parametrize ValueError test with both kwarg orderings (confidence-first
  and keypoint-confidence-first) to document order-independence
- Add test_constructor_normal_keypoint_confidence_path: confirms custom
  __init__ initialises all fields and emits no warning on normal path
- Add test_constructor_confidence_none_does_not_warn: guards the
  `if confidence is not None` branch against guard-condition typos
- Add test_constructor_data_none_defaults_to_empty_dict: tests the
  None->{} normalisation path introduced by the custom __init__
- Add test_keypoints_init_covers_all_dataclass_fields: drift guard that
  asserts dataclasses.fields(KeyPoints) == __init__ params, catching
  future field additions that forget to update __init__

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-17 16:15:34 +02:00
Jirka Borovec b9c24ddafd
docs(changelog): backfill missing `0.29.0` entries and fix duplicate (#2329) 2026-06-16 12:16:38 +02:00
Abdelrahman Gomaa c2490e6015
chore: preserve all polygons when exporting multi-part masks to COCO (#2322)
* test(coco): strengthen multi-polygon export test coverage
* docs(coco): document segmentation shape and iscrowd routing in docstring
* test(coco): parametrize multipart polygon tests

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-16 00:43:19 +02:00
Jirka Borovec a914a00ab8
docs: fix broken links in changelog file (#2328) 2026-06-16 00:16:00 +02:00
dependabot[bot] d3a0a84dd5
⬆️ Bump the uv group across 1 directory with 2 updates (#2326)
Bumps the uv group with 2 updates in the / directory: [cryptography](https://github.com/pyca/cryptography) and [tornado](https://github.com/tornadoweb/tornado).


Updates `cryptography` from 46.0.7 to 48.0.1
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.7...48.0.1)

Updates `tornado` from 6.5.6 to 6.5.7
- [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.5.6...v6.5.7)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 48.0.1
  dependency-type: indirect
- dependency-name: tornado
  dependency-version: 6.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 23:45:40 +02:00
SkalskiP 1e551ec9c0 releasing `0.29.0` 2026-06-15 23:36:28 +02:00
SkalskiP 1fa735d0d9 releasing `0.29.0rc1` 2026-06-15 23:24:53 +02:00
Piotr Skalski 62a24d7960
Unify deprecation policy: enforce 3 minor release minimum window (#2324)
* Unify deprecation policy: 3 minor releases minimum

* Add 0.29.0 changelog entry

* Bump version to 0.29.0
2026-06-15 23:04:55 +02:00
Piotr Skalski 3d07099cf9
Fix ellipse annotators to draw level-by-level instead of point-by-point (#2325) 2026-06-15 22:51:06 +02:00
Agis Kounelis 483e3e9335
fix(detection): make `oriented_box_iou_batch` exact and gate non-overlapping pairs (#2317)
* fix(detection): make oriented_box_iou_batch exact and gate non-overlapping pairs
* test(detection): cover gate passthrough and invalid metric in oriented IoU
* test(detection): add edge-case OBB coverage for oriented_box_iou_batch

- 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)
- 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

* refactor(detection): fuse _overlapping_envelope_pairs to halve peak memory
* test(detection): fix degenerate-collinear OBB NMM expectation for exact IoU

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-15 20:54:06 +02:00
Jirka Borovec e0e1abd865
fix(obb): NMM now computes geometric union via min-area rotated rect (#2312)
* fix(detection): OBB NMM now computes geometric union via min-area rotated rect

Previously with_nmm for OBB detections kept the winner's OBB geometry unchanged
(only confidence was merged), making it inconsistent with AABB NMM which expands
to the union envelope. Now computes cv2.minAreaRect over all N×4 corners from
the merge group — the MARC degenerates to the axis-aligned union for zero-rotation
OBBs, preserving full consistency with AABB NMM.

- Replace winner-OBB xyxy patch with MARC of all merged corners
- Update ORIENTED_BOX_COORDINATES in data to reflect merged geometry
- Rename test to reflect new expected behaviour (union, not winner AABB)
- Add consistency test asserting axis-aligned OBB NMM == AABB NMM xyxy

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* code(detection): defensive reshape + clarify xyxy-override intent in OBB NMM

- Add .reshape(4, 2) to OBB corner extraction loop so flat-adjacent shapes are normalised before cv2.minAreaRect
- Add inline comment at xyxy override: OBB groups intentionally discard AABB-union xyxy from reduce() to stay consistent with MARC corners

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* test(detection): expand OBB NMM coverage — rotated, 3-group, passthrough, class-agnostic, IOS, flat-format

- Add test_rotated_obb_merge_produces_marc: two 45-degree OBBs, assert MARC encompasses all corners
- Add test_three_detection_group_merge: three overlapping OBBs, assert merged len==1 and envelope spans all inputs
- Add test_single_detection_passthrough_preserves_obb: non-overlapping OBB passes through unchanged
- Add test_class_agnostic_obb_merge: class_agnostic=True merges cross-class OBBs
- Add test_overlap_metric_ios_obb_merge: IOS metric merges contained OBBs
- Add test_flat_n8_obb_format_raises_value_error: documents that (N,8) flat format is unsupported (canonical is (N,4,2))
- Import OverlapMetric for IOS test

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* docs(detection): document OBB NMM MARC semantics in with_nmm + changelog entry

- Add Note section to with_nmm docstring explaining MARC behavior: union for zero-rotation OBBs, MARC for rotated OBBs, single-group passthrough
- Add changelog UnReleased entry for #2312 behavioral change

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* fix(detection): OBB NMM uses winner's angle instead of free MARC to avoid overshoot

cv2.minAreaRect picks a 45-degree rect for diagonal staircase arrangements of
axis-aligned boxes, producing an AABB like [-10,-10,54,54] that extends outside
every input. Fix: lock merged OBB to winner's angle by projecting all corners
onto the winner's principal axes (from first edge vector), computing AABB there,
and back-rotating — for zero-rotation inputs this gives exactly the axis-aligned
union; for same-angle groups the result equals the prior MARC.

- Remove cv2 dependency from the OBB merge block (pure numpy now)
- Add test_diagonal_staircase_obb_merge_stays_within_union regression test
- Rename test to reflect winner-angle semantics

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* fix(detection): fix changelog wording + add explicit OBB shape guard in NMM

- docs/changelog.md: replace stale MARC/cv2.minAreaRect wording with
  winner's-angle description matching the actual implementation
- core.py: validate ORIENTED_BOX_COORDINATES shape is (N, 4, 2) at the
  start of the OBB merge block; raises ValueError("corners must have
  shape (N, 4, 2)") for flat (N, 8) input instead of silently mis-reshaping

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* refactor: parametrize OBB NMM tests in TestDetectionsWithNmm Consolidate 7 individual OBB NMM test methods into a single parametrized test_obb_nmm_merge with explicit expected_confidence and expected_corners assertions. Add cases for mixed-angle merges, multiple merge groups, and degenerate collinear OBBs. Add standalone test_obb_nmm_empty_detections for empty inputs.

* refactor(tests): simplify OBB NMM test cases by replacing `np.array` usage with nested lists

- Update test parameters to use plain Python lists instead of `numpy` arrays for corner definitions.
- Adjust the `_make_obb_detections` setup to preprocess corners into `numpy` arrays.
- Add explicit conversion of `expected_corners` to `numpy` arrays in the assertions.

* feat: add xyxyxyxy_to_xyxy utility for OBB-to-AABB conversion Vectorized conversion of oriented bounding box corners (N, 4, 2) to axis-aligned bounding boxes (N, 4). Used internally in with_nmm and exposed via top-level import.

* deprecate: mark merge_inner_detections_objects for removal in 0.34.0 Function is unused dead code with no external callers. Decorator emits FutureWarning while preserving existing behavior.

* refactor: extract _merge_obb_corners and _merge_detection_group from with_nmm Replace inline OBB post-processing and reduce-based merging with two private helpers using single-pass area-weighted confidence. Deprecate merge_inner_detection_object_pair and merge_inner_detections_objects_without_iou (0.29.0 -> 0.34.0). Rename TestDetectionsWithNmm -> TestDetectionsWithNMM and expand TestMergeDetectionGroup to assert all output fields via expected_detections.

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: SkalskiP <piotr.skalski92@gmail.com>
2026-06-15 16:23:31 +02:00
Tamil Adhavan S K 117a9baab8
fix(coco): preserve segmentation in as_coco() round-trip (#2321)
* fix(coco): preserve segmentation in as_coco() round-trip (#2285)
* fix(coco): address Copilot review — normalize flat polygon lists, tighten test assertions
* fix(coco): type annotation, config constant, merge-compat, lint fixes
* test(coco): add two-polygon and RLE round-trip tests
* fix(tests): update expected_results for always-store coco_raw_segmentation
* fix(coco): use COCO_RAW_SEGMENTATION key in export fallback
* fix(coco): adjust mask_bool assignment to ensure proper usage in iscrowd logic
* test: simplify COCO segmentation regression cases
* fix(coco): add type annotation for raw_segs to ensure clarity
* test(coco): improve docstring and add comments for segmentation round-trip validation

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-06-15 15:00:27 +02:00
dependabot[bot] 497f336d13
⬆️ Bump tornado from 6.5.5 to 6.5.6 in the uv group across 1 directory (#2315)
Bumps the uv group with 1 update in the / directory: [tornado](https://github.com/tornadoweb/tornado).


Updates `tornado` from 6.5.5 to 6.5.6
- [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.5.5...v6.5.6)

---
updated-dependencies:
- dependency-name: tornado
  dependency-version: 6.5.6
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 01:03:43 +02:00
jirka ef8ba557a1 releasing `0.29.0.rc0` 2026-06-11 18:37:56 +02:00
Piotr Skalski caf060521d
refactor: enrich `KeyPoints` with visibility mask, split confidence fields, and redesign uncertainty annotator (#2286)
- Added `KeyPoints.visible` mask support for per-keypoint visibility
- Split confidence into `keypoint_confidence` and `detection_confidence`
- Kept legacy `KeyPoints.confidence` as deprecated forwarding alias
- Updated `KeyPoints` slicing/filtering to preserve visibility and confidence fields
- Fixed `KeyPoints.__getitem__` row-index normalization for NumPy scalar, 0-D array, and boolean indexing
- Fixed `detection_confidence` indexing to use normalized row indices
- Updated `VertexAnnotator` to skip invisible keypoints
- Updated `EdgeAnnotator` to skip invisible keypoints and edges
- Added per-class skeleton support to `EdgeAnnotator`
- Added multi-skeleton support to `VertexLabelAnnotator`
- Added label validation for `VertexLabelAnnotator`
- Added color-list length validation for keypoint annotators
- Added `VertexEllipseAreaAnnotator`
- Added `VertexEllipseOutlineAnnotator`
- Added `VertexEllipseHaloAnnotator`
- Kept/exported `VertexEllipseAnnotator` alongside the new ellipse variants
- Standardized keypoint annotator docstrings and executable examples
- Added/updated `validate_detection_confidence` and `validate_visible`
- Removed/cleaned old keypoint validator shims
- Added regression tests for visibility, confidence fields, multi-skeleton behavior, and indexing edge cases
- Updated helpers and RF-DETR/keypoint tests for the new confidence/visibility model
- Added API design principles to contributing docs
- Ignored local multi-skeleton test script in `.gitignore`

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-06-11 18:34:33 +02:00
Jirka Borovec b8ebc14489
docs(agents): refine AGENTS.md (#2308) 2026-06-10 22:25:31 +02:00
Tamil Adhavan S K 8a4063086f
chore: update YOLO OBB annotation export support (#2302)
* fix(yolo): validate OBB corner shape and test is_obb=False passthrough
* docs(config): add attribute docstring to ORIENTED_BOX_COORDINATES

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-10 15:28:42 +02:00
Shahid Ul Islam a549f44792
Add OBB support to ConfusionMatrix via MetricTarget (#2247)
* feat(metrics): OBB support in ConfusionMatrix, MASKS guard, shape validation
* refactor(tests): extract reusable ConfusionMatrix MASKS test cases
* refine(metrics): exclude metric_target from ConfusionMatrix.__eq__
* refactor(metrics): consolidate _assert_supported_target call sites
* fix(metrics): normalise OBB (N,4,2) shape explicitly in detections_to_tensor
* test(metrics): add OBB wrong-target-cols case to validate_input_tensors
* test(metrics): add OBB edge-case parametrize cases to detections_to_tensor
* test(metrics): replace validate_input_tensors with _validate_input_tensors in test_detection
* refactor(metrics): make ConfusionMatrix unhashable
* fix(metrics): add shape validation to evaluate_detection_batch
* fix(metrics): cast class_id to float32 in detections_to_tensor
* test(metrics): fix test_evaluate_detection_batch targets shape

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-10 13:29:33 +02:00
Ruben cb9d3dccb8
chore(deprecation): use `TargetMode` enum to silence deprecate `FutureWarnings` on import (#2304)
* fix(deprecation): use TargetMode enum to silence deprecate FutureWarnings on import
* chore: bump `pydeprecate` dependency to v0.9 and update lockfile

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-06-09 23:40:16 +02:00
Agis Kounelis ace3ebd03e
fix(detection): make `Detections.area` OBB-aware (#2306)
* fix(detection): make Detections.area OBB-aware

When detections carry ORIENTED_BOX_COORDINATES (the four xyxyxyxy corners),
the area property returned the area of the derived axis-aligned bounding
box instead of the rotated body. The AABB overestimates by up to ~2x for a
45-degree rotation, which silently miscomputes downstream values — most
visibly the area-sorted z-ordering inside MaskAnnotator / HaloAnnotator,
and any user code that filters detections by area.

* docs(detection): use string literal in Detections.area doctest
* test(detection): single-line docstring on test_uses_oriented_box_corners_when_present
* fix(detection): validate (N,4,2) shape of OBB data field in Detections.area
* perf(detection): replace np.roll pair with cross-diagonal shoelace in Detections.area
* perf(detection): cast x/y slices to float64 instead of full corners array
* refactor(detection): extract obb_polygon_area to detection/utils/boxes.py
* test(detection): add test_raises_on_malformed_obb_coordinates_shape
* test(detection): assert per-branch dtype contract for Detections.area
* docs(detection): document OBB dispatch contract and dtype in Detections.area docstring

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-09 22:20:34 +02:00
Jirka Borovec 9faa4f6133
chore: update `mdformat` hook arguments to disable wrapping (#2307)
* chore: update `mdformat` hook arguments to disable wrapping
* fix(pre_commit): 🎨 auto format pre-commit hooks
* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-09 16:15:05 +02:00
Jirka Borovec 97f4951f08
docs(contributing): formalise test conventions and doctest guidelines (#2305)
* formalise test conventions and doctest guidelines
* strengthen doctest rule and add syntax guide
* relax doctest rule to strong recommendation
* fix line-length ref and heading depth rules

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-09 15:51:18 +02:00
Agis Kounelis 3b485f719a
refine(detection): make `with_nms` and `with_nmm` OBB-aware (#2303)
* fix(detection): make with_nms and with_nmm OBB-aware
* perf(detection): bound rasterization canvas in oriented_box_iou_batch
* fix(detection): with_nmm OBB/AABB xyxy fix; 3-path dispatch docs
* fix(detection): shape validation, NMM assert, docstring/Examples
* test(detection): with_nmm fallback, OBB AABB fix, boundary and IOS tests
* docs(changelog): document OBB with_nms/nmm behaviour change for #2303
* docs(detection): convert OBB NMS/NMM examples to doctests
* refactor(test): group OBB NMS/NMM tests into classes
* refactor(test): merge duplicate NMS class-awareness tests via parametrize
* refactor(test): parametrize overlap-metric and dispatch tests

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-06-09 13:11:09 +02:00
Agis Kounelis c92458b70e
fix(dataset): preserve OBB rotation in `DetectionDataset.as_yolo` (#2289)
DetectionDataset.from_yolo accepts is_obb=True and stores the four
corners in detections.data["xyxyxyxy"], but DetectionDataset.as_yolo
has no matching option and only reads xyxy/mask. The standard
from_yolo -> split -> as_yolo flow silently writes 5-token
axis-aligned lines, and re-loading the saved file with is_obb=True
crashes the validator because it expects 9 tokens.

Add is_obb to as_yolo, save_yolo_annotations, and
detections_to_yolo_annotations. When True, the four corners from
data["xyxyxyxy"] are serialized via the existing object_to_yolo
polygon path. Masks are ignored, mirroring from_yolo(is_obb=True)
semantics. A missing xyxyxyxy raises ValueError early.

- Add UserWarning in as_yolo when area/approx params passed with is_obb=True (silently ignored)
- Add UserWarning in detections_to_yolo_annotations when mask present + is_obb=True
- Update ValueError message to include expected shape (N, 4, 2) for manual callers
- Add Google-style docstrings to detections_to_yolo_annotations and save_yolo_annotations
- Add test: N>1 OBB detections per image (corner indexing via data-dict slicing)
- Add test: dataset round-trip with background-only (no label file) image
- Add test: as_yolo() without is_obb=True on OBB-loaded dataset emits 5-token lines
- Replace all tempfile.TemporaryDirectory / os.path.join / os.makedirs
  with pytest tmp_path and pathlib Path
- Merge 3 load-mask tests into parametrized test_load_yolo_annotations_mask_behaviour
  (obb-no-mask, obb-force_masks-ignored, segmentation-produces-mask)
- Merge token-count tests into parametrized test_dataset_as_yolo_obb_output_token_count
  (obb-save-nine-tokens, default-save-five-tokens)
- Split corner accuracy into dedicated test_dataset_as_yolo_obb_round_trip_corner_accuracy
- Drop import os and import tempfile

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-06-08 15:31:04 +02:00
dependabot[bot] eef8c96d95
⬆️ Bump the github-actions group with 2 updates (#2300)
Bumps the github-actions group with 2 updates: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `astral-sh/setup-uv` from 8.1.0 to 8.2.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](08807647e7...fac544c07d)

Updates `codecov/codecov-action` from 6 to 7
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-08 13:45:31 +02:00
Andrew Barnes b82d6f95be
fix: normalize file extension filters (#2298)
* fix: normalize file extension filters
* fix(file): add is_file and empty-ext guards
* test(file): collapse extension tests into parametrized form
* fix: match multi-part extension suffix tails

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
2026-06-08 13:41:25 +02:00
Andrew Barnes b18a30ea1a
fix: support grayscale letterbox images (#2297)
- Delete letterbox_image alpha block (lines 266-270): block wrote to
  caller's input array, not image_with_borders; used wrong coordinate
  system (resized vs original dims); redundant since cv2.copyMakeBorder
  already sets alpha=0 in padded regions when given a 3-element value
- Add test_letterbox_image_for_rgba_opencv_image: asserts padded alpha=0,
  interior alpha preserved, and input array not mutated after call
- Update letterbox_image docstring: image param lists (H,W,3)/(H,W,4)/
  (H,W)/PIL shapes; add Note on BGRA alpha behavior; add grayscale doctest

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-07 11:06:21 -06:00
Andrew Barnes 35006d7342
fix: sort YOLO class names by numeric keys (#2296)
* fix: sort yolo class names by numeric keys
* fix(yolo): reject double-hyphen keys in _is_int_like predicate
* fix(yolo): raise ValueError for mixed numeric/non-numeric names keys
* test(yolo): parametrize _extract_class_names with all key-type cases
* refine(yolo): rename lambda param key→k to avoid shadowing outer variable
* docs(yolo): comment bool guard in _is_int_like
* docs(yolo): docstring for _extract_class_names

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-07 10:54:43 -06:00
Jirka Borovec 3410d92daa
refactor: privatize validation helpers (#2294)
* refactor: privatize validation helpers
* fix: correct error message in _validate_keypoint_confidence

- Fix f"({n, m})" -> f"({n}, {m})" (set literal -> proper shape string)
- Fix error message "1D" -> "2D" to match actual array dimensionality

* fix: correct error message in _validate_xy

- Fix f"({n, m},)" -> f"({n}, {m}, 2) or ({n}, {m}, 3)"
- Fix error message "2D" -> "3D" to match actual array dimensionality

* bump pydeprecate version to >=0.9,<0.10 and fix type ignore tag for decorator
* chore: extend deprecation removal timeline to 0.32.0 across validators

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-06 13:15:54 -06:00
Piotr Skalski 5a8e211a04 Docs/api design principles contributing (#2292)
* fix: resolve ruff line-length and mypy union syntax errors in keypoints module

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: add API design principles to CONTRIBUTING.md

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 18:09:09 -06:00
Piotr Skalski 1e8a48559b
Revert "feat: store keypoints on detections (#2290)" (#2291)
This reverts commit e03111e67c.
2026-06-04 11:43:48 -06:00
Jirka Borovec e03111e67c
feat: store keypoints on detections (#2290)
* docs: add API design principles to contribution guidelines
* feat: store keypoints on detections
* test: add "keypoints" to internal test cases
* docs: document keypoints field semantics and add docstring + dtype guard
* refactor: deduplicate keypoints shape check and add K-mismatch guard
* docs: clarify Detections.keypoints vs sv.KeyPoints decision rule and add KeyPoints filter example
* feat(key_points): add KeyPoints.from_detections() cross-container adapter
* test: extend keypoints test coverage — dtype guard, __eq__, dynamic field sets
* test: expand keypoints test coverage (M7/M8)
* fix: correct validate_xy expected_shape and dimensionality message
* fix: add ndim guard in KeyPoints.from_detections
* test: add unit tests for KeyPoints.from_detections adapter
* refactor: fix class_id cast and import formatting in key_points

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-04 09:09:37 -06:00
Lourdhu Raju 0c67942d32
Refactor/polygon zone doctest (#2264)
Replaces the static python code block with a pycon doctest using
primitive numpy inputs, so the example is now executed and verified
by `pytest --doctest-modules`. Removes the external YOLO, ByteTrack,
and cv2 dependencies from the example.

Follows the same pattern as PR #2207 (LineZone). Part of the
documentation-as-tests effort tracked in #2106.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 08:33:46 +02:00
Ritwij Aryan Parmar 7d2259669b
Fix OBB IoU for non-square canvases (#2282)
* Fix OBB IoU canvas dimensions
* test(metrics): fix OBB metric parametrize — remove MAP, add smoke test
* test(metrics): clarify MAP OBB test is smoke test only
* test(iou): parametrize OBB scaling invariance test with y-dominant case
* fix(iou): add empty-input guard and x/y axis docs to oriented_box_iou_batch

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-06-01 09:20:38 +02:00
tarunbommawar27 fb02e5c959
docs: clarify MaskAnnotator mask requirements (#2279)
* docs: clarify MaskAnnotator mask requirements
* Potential fix for pull request finding

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-29 10:24:45 +02:00
Jirka Borovec 918b6139ef
feat(keypoints): add keypoint conversion bridge (#2277)
* feat(keypoints): add RF-DETR keypoint conversion bridge
* feat: add RF-DETR keypoint uncertainty visualization
* refactor(keypoints): remove deprecated RF-DETR keypoint conversion logic
* refactor: improve internal handling of keypoint data and detection utilities
* fix(keypoints): handle non-finite confidence values and empty keypoint arrays
* docs(keypoint): add VertexEllipseAnnotator to annotators docs
* docs(keypoints): add Example block to KeyPoints.from_rfdetr docstring
* docs(keypoints): document source_shape HW ordering in from_rfdetr
* feat(keypoints): validate precision_cholesky shape in from_rfdetr
* refine(keypoints): add warning log for silent precision matrix failures
* fix(keypoints): fix mypy type errors in VertexEllipseAnnotator
* test(keypoints): add confidence_threshold filter test for VertexEllipseAnnotator
* test(keypoints): add max_axis_length cap and constructor validator tests
* docs(keypoints): document max_axis_length=None risk in VertexEllipseAnnotator
* docs(keypoints): add Raises section to VertexEllipseAnnotator.annotate docstring
* docs(keypoints): document confidence scale convention in from_rfdetr
* docs(keypoints): note from_rfdetr input convention in KeyPoints class docstring
* fix(types): improve type hinting for internal and keypoints modules
* fix(keypoints): handle None class_id in from_rfdetr conversion

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-28 10:36:41 +02:00
Madhav-C 81218c5f1b
fix(coco): emit 1-indexed `category_id` in COCO export (#2276)
* fix): emit 1-indexed category_id in COCO export
* test): add regression and guard tests for 1-indexed category_id
* docs): warn in coco_annotations_to_detections that remap is required

---------

Co-authored-by: madhavcodez <madhavcodez@users.noreply.github.com>
Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-27 10:40:32 +02:00
Mahbod 2c3a2ef6f9
fix(detection): preserve `class_name` string dtype on empty `Detections.from_inference` (#2270)
* fix): preserve class_name string dtype on empty Detections.from_inference
* test): fix stale float64 dtype expectation in test_process_roboflow_result
* docs): document data[class_name] contract in from_inference Returns
* fix): set string-dtype class_name on empty from_ultralytics and from_vlm paths
* test): strengthen from_inference empty-path dtype test
* test): cover SDK .dict() path for empty predictions in from_inference
* docs): add docstring to process_roboflow_result
* test): use dtype.kind comparison for empty/non-empty class_name
* docs): update example in `process_roboflow_result` docstring

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-27 10:39:49 +02:00
Mahbod 2fdb970430
fix(annotators): avoid divide-by-zero in HeatMapAnnotator on empty detections (#2269)
When HeatMapAnnotator is called on a fresh annotator with empty detections
(common on the first frames of a video before the model produces any output),
self.heat_mask is all zeros, so temp / temp.max() raises
RuntimeWarning: invalid value encountered in divide and produces nan/inf
in-flight. Skip the normalisation when temp.max() == 0; the resulting
all-zero heat mask filters out via the > 0 check below, so the scene is
returned unchanged.

- Fix `kernel_size: int = 25` → `int | None = 25`; document None disables blur
- Add Note to annotate docstring: empty detections returns scene unchanged
- Add happy path test: single detection must produce visible heat output
- Add stateful tests: empty→real and real→empty sequence coverage

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-26 22:27:07 +02:00
dependabot[bot] fb2dec9775
⬆️ Update pydeprecate requirement from <0.8,>=0.7 to >=0.7,<0.9 (#2268)
Updates the requirements on [pydeprecate](https://github.com/Borda/pyDeprecate) to permit the latest version.
- [Release notes](https://github.com/Borda/pyDeprecate/releases)
- [Changelog](https://github.com/Borda/pyDeprecate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Borda/pyDeprecate/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: pydeprecate
  dependency-version: 0.8.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 11:30:37 +02:00
Madhav-C befdb7c661
fix(dataset): make COCO annotation/image ids chainable across splits (#2267)
* fix(dataset): make COCO annotation/image ids chainable across splits (#768)

Exporting train/valid/test splits with DetectionDataset.as_coco
previously restarted image_id and annotation_id at 1 for every split,
producing three JSON files whose ids collided and could not be safely
merged into a single COCO collection.

Adds optional starting_image_id and starting_annotation_id parameters
to save_coco_annotations and DetectionDataset.as_coco (default 1 to
preserve existing behavior) and returns a (next_image_id,
next_annotation_id) tuple so callers can feed the result of one
export straight into the next:

    next_image, next_ann = train.as_coco(annotations_path="train.json")
    next_image, next_ann = valid.as_coco(
        annotations_path="valid.json",
        starting_image_id=next_image,
        starting_annotation_id=next_ann,
    )
    test.as_coco(
        annotations_path="test.json",
        starting_image_id=next_image,
        starting_annotation_id=next_ann,
    )

The images-only branch of as_coco (annotations_path=None) round-trips
the starting ids unchanged so chaining still works there.

Adds 4 regression tests covering defaults, custom starting ids,
end-to-end three-split chaining with global uniqueness assertions,
and the images-only round-trip.

* docs: address review polish on COCO id-chaining
* fix(dataset): align save_coco_annotations approximation_percentage default to 0.0
* docs(dataset): add one-line summary to save_coco_annotations docstring
* docs: add changelog entry for COCO id chaining (PR #2267)
* docs(dataset): document file_name uniqueness limitation in save_coco_annotations
* feat(dataset): validate starting_image_id and starting_annotation_id >= 1
* docs(dataset): add Example section to save_coco_annotations docstring
* docs(dataset): unpack final as_coco return value in chaining example
* test(dataset): add COCO chaining tests and fix test helper for zero detections

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-22 21:41:42 +02:00
Copilot cb259065a5
Add `from_coco` regression coverage for multi-segment COCO masks (#2258)
* test: cover from_coco multi-segment masks
* test: add from_coco multi-segment regression
* test: extract coco multi-segment fixture
* test(coco): move multi-segment mask test to TestFromCocoMasks
* test(coco): fix fixture area and document bbox intent
* test(coco): add force_masks=True sibling test for multi-segment masks
* test(coco): add uneven-length segments case to TestFromCocoMasks
* test(coco): parametrize force_masks in TestFromCocoMasks

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-22 20:42:11 +02:00
Madhav-C 4b856f1c04
docs(examples): fix outdated CLI flag names in `heatmap_and_track` README (#2266)
The README listed `--track_threshold` and `--match_threshold`, but
script.py exposes `--track_activation_threshold` and
`--minimum_matching_threshold` (the CLI surface is derived from the
main() signature by jsonargparse.auto_cli). Following the README as
written produced "unknown argument" errors.

Aligns the README with the actual CLI surface.
2026-05-22 20:00:30 +02:00
Copilot 6461d3fbac
Restructure annotator docs tabs to avoid Material’s 20-tab limit (#2257)
* Initial plan
* fix: split annotator docs tabs into categories
* test: harden annotator docs regression test
* docs: fix blur example assignment
* docs(annotators): restructure tabs, harden regression tests
* docs(annotators): remove broken oriented box preview image

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-22 19:54:33 +02:00
Jirka Borovec 2c13b269e6
Update changelog after version `0.28.0` release
Updated the date modified and added a new entry for version 0.28.0 with details on sv.CompactMask.
2026-05-22 19:14:29 +02:00
Jirka Borovec f1176270eb
Bump version to `0.29.0.dev` 2026-05-22 19:12:20 +02:00
Copilot 5b883fed5b
Guard `InferenceSlicer` against OBB callback crashes when `thread_workers > 1` (#2256)
* fix: serialize OBB inference slicer callbacks
* test: simplify OBB slicer regression test
* refactor: simplify OBB slicer fallback path
* test: make OBB slicer regression deterministic
* fix(slicer): add thread_workers validation and lock for OBB warn flag
* docs(slicer): document OBB fallback, merge order, perf note, dual-use key

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-05-22 18:53:26 +02:00
Jirka Borovec e7376d558e
ci(tests): disable matplotlib GUI and exclude non-test dirs from collection (#2262) 2026-05-20 23:40:21 +02:00
dependabot[bot] 17d7717515
⬆️ Bump pymdown-extensions from 10.16.1 to 10.21.3 in the uv group across 1 directory (#2255)
Bumps the uv group with 1 update in the / directory: [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions).


Updates `pymdown-extensions` from 10.16.1 to 10.21.3
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.16.1...10.21.3)

---
updated-dependencies:
- dependency-name: pymdown-extensions
  dependency-version: 10.21.3
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-20 11:59:25 +02:00
dependabot[bot] 7e6df44d86
⬆️ Bump idna from 3.10 to 3.15 in the uv group across 1 directory (#2254)
Bumps the uv group with 1 update in the / directory: [idna](https://github.com/kjd/idna).


Updates `idna` from 3.10 to 3.15
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md)
- [Commits](https://github.com/kjd/idna/compare/v3.10...v3.15)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.15'
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-20 11:50:16 +02:00
Youssef Ibrahim 01ec36b31d
fix: return empty int ndarray instead of None for class_id on empty VLM parse (#2239)
When from_paligemma or from_google_gemini_2_0 find no detections (no regex
matches, JSON decode error, or empty bounding-box list), they previously
returned None for class_id. All other early-exit and filter paths already
return a zero-length ndarray of dtype int. This inconsistency causes
downstream AttributeError when callers unconditionally call .shape or
iterate over the result.

Affected paths:
- from_paligemma: matches.shape[0] == 0 branch
- from_google_gemini_2_0: JSONDecodeError branch and len(xyxy) == 0 branch

---------

Co-authored-by: YousefZahran1 <youssefzahran.y@gmail.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-05-19 14:31:44 +02:00
Prem f4b0767a88
docs: add sample image download and sv.plot_image() to detect_and_annotate tutorial (#2242)
* docs: add sample image download and sv.plot_image() to detect_and_annotate tutorial
* Potential fix for pull request finding

---------

Co-authored-by: PatelPrem21 <prem_23247@ldrp.ac.in>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 14:22:49 +02:00
SATISH K C 7e28315595
fix: preserve audio stream in process_video (#2252)
- Move `tempfile.mkstemp` + `os.close` inside `try` block so OSError (disk full,
  unwritable dir) is caught by the existing `except Exception` handler instead of
  propagating to the caller, preserving the warn-and-degrade contract
- Pass `dir=os.path.dirname(os.path.abspath(video_path))` so the temp file is on
  the same filesystem as the output, restoring `os.rename` semantics in `shutil.move`
- Initialise `tmp_path = None` before `try`; guard `finally` with
  `tmp_path is not None` to satisfy mypy and avoid referencing an unbound name
- Add `-loglevel error -nostats` so ffmpeg only writes actual errors to stderr
  (eliminates progress/stats spam that would buffer in PIPE indefinitely)
- Decode `result.stderr` and include it in the warning when ffmpeg exits
  non-zero, so failure messages surface diagnostically instead of being discarded
- Change bare `process_video(...)` call to `sv.process_video(...)` so the
  example matches the public API pattern and does not raise NameError for users
  copying the snippet
- Remove unused `import cv2` which was never referenced in the example body
- Clarify that missing/failing ffmpeg warns and continues rather than raising
- Add install hint for ffmpeg (apt/brew)
- Note that audio is truncated to match the processed video duration (-shortest)
- test_mux_audio_moves_file_on_success: mock subprocess.run returncode=0;
  assert shutil.move is called once with video_path as destination — catches
  any regression that drops the move call after a successful ffmpeg run
- test_mux_audio_swallows_subprocess_exception: mock subprocess.run raising
  OSError; assert no exception escapes _mux_audio and original file is intact
- Fix failed_result.stderr = b"" in test_mux_audio_warns_on_ffmpeg_failure
  to match the updated _mux_audio which now decodes result.stderr
- Skip _mux_audio when writer_worker.is_alive() after join timeout to avoid
  muxing an incomplete output file
- Fix test_mux_audio_moves_file_on_success: patch os.replace (not shutil.move)
  to match implementation changed in 2027938d
- Move four test_mux_audio_* free functions into TestMuxAudio class
- Strip mux_audio_ prefix from method names; class carries the unit
- Condense multi-line docstrings to single-line per testing rules
- Collapse test_warns_when_ffmpeg_missing, test_warns_on_ffmpeg_failure,
  test_swallows_subprocess_exception into one parametrized
  test_file_unchanged_on_failure[ffmpeg_missing|ffmpeg_fails|subprocess_raises]
- Promote two class methods back to module-level functions
- Collapse nested with-patch statements into single with a, b: form

---------

Co-authored-by: jirka <6035284+Borda@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 14:09:45 +02:00
Jirka Borovec b68b41ae7a
ci(docs): skip RC tags, strip .postX, fix latest alias deploy (#2253)
- Skip mike deploy for RC releases (tags containing rc/RC)
- Strip .postX suffix before deploying (0.27.0.post1 → 0.27.0)
- Add -u flag to latest deploy to handle existing alias

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-19 12:02:24 +02:00
dependabot[bot] 6fb4e80837
⬆️ Bump mistune from 3.1.3 to 3.2.1 in the uv group across 1 directory (#2250)
Bumps the uv group with 1 update in the / directory: [mistune](https://github.com/lepture/mistune).


Updates `mistune` from 3.1.3 to 3.2.1
- [Release notes](https://github.com/lepture/mistune/releases)
- [Changelog](https://github.com/lepture/mistune/blob/main/docs/changes.rst)
- [Commits](https://github.com/lepture/mistune/compare/v3.1.3...v3.2.1)

---
updated-dependencies:
- dependency-name: mistune
  dependency-version: 3.2.1
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-14 13:02:13 +02:00
JFrench-Enterprise 7dce60697f
Simplified wording Update README.md (#2249)
---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-13 18:25:06 +02:00
dependabot[bot] 26ae68a2b6
⬆️ Bump AButler/upload-release-assets from 3.0 to 4.0 in the github-actions group (#2243)
Bumps the github-actions group with 1 update: [AButler/upload-release-assets](https://github.com/abutler/upload-release-assets).


Updates `AButler/upload-release-assets` from 3.0 to 4.0
- [Release notes](https://github.com/abutler/upload-release-assets/releases)
- [Commits](https://github.com/abutler/upload-release-assets/compare/v3.0...v4.0)

---
updated-dependencies:
- dependency-name: AButler/upload-release-assets
  dependency-version: '4.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 17:58:05 +02:00
dependabot[bot] 878e6414da
⬆️ Bump jupyter-server from 2.16.0 to 2.18.0 in the uv group across 1 directory (#2245)
Bumps the uv group with 1 update in the / directory: [jupyter-server](https://github.com/jupyter-server/jupyter_server).


Updates `jupyter-server` from 2.16.0 to 2.18.0
- [Release notes](https://github.com/jupyter-server/jupyter_server/releases)
- [Changelog](https://github.com/jupyter-server/jupyter_server/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter-server/jupyter_server/compare/v2.16.0...v2.18.0)

---
updated-dependencies:
- dependency-name: jupyter-server
  dependency-version: 2.18.0
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 17:57:51 +02:00
Jirka Borovec 2beeda7851
docs: link "Memory-Efficient Instance Segmentation" notebook and update deprecations (#2241)
* link "Memory-Efficient Instance Segmentation" notebook and update deprecations
* fix(notebook): correct broken Colab badge URL in compact-mask-sam3

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
2026-05-01 00:49:15 +02:00
dependabot[bot] 6ec8fa9a23
⬆️ Bump notebook from 7.4.4 to 7.5.6 in the uv group across 1 directory (#2238)
Bumps the uv group with 1 update in the / directory: [notebook](https://github.com/jupyter/notebook).


Updates `notebook` from 7.4.4 to 7.5.6
- [Release notes](https://github.com/jupyter/notebook/releases)
- [Changelog](https://github.com/jupyter/notebook/blob/@jupyter-notebook/tree@7.5.6/CHANGELOG.md)
- [Commits](https://github.com/jupyter/notebook/compare/@jupyter-notebook/tree@7.4.4...@jupyter-notebook/tree@7.5.6)

---
updated-dependencies:
- dependency-name: notebook
  dependency-version: 7.5.6
  dependency-type: direct:development
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-30 23:52:40 +02:00
Jirka Borovec 1f199de502
drop outdated release_process.md 2026-04-30 23:52:00 +02:00
Jirka Borovec d8a25481c4
chore(notebooks): migrate demo scripts to Jupyter notebooks (#2240)
- Removed `convert_to_ipynb.sh` script and standalone `.py` demo files.
- Added `docs/notebooks/compact-mask-sam3.ipynb` showcasing new features (`sv.CompactMask` and `sv.Detections.from_sam3`) introduced in version `0.28.0`.
2026-04-30 23:49:11 +02:00
274 changed files with 44168 additions and 7002 deletions

View File

@ -2,129 +2,81 @@
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
community-reports@roboflow.com.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at community-reports@roboflow.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][mozilla coc].
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][mozilla coc].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][faq]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][faq]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
[faq]: https://www.contributor-covenant.org/faq
[homepage]: https://www.contributor-covenant.org

View File

@ -11,13 +11,14 @@ Please read and adhere to our [Code of Conduct](https://supervision.roboflow.com
## Table of Contents
- [Contribution Guidelines](#contribution-guidelines)
- [Contributing Features](#contributing-features)
- [Contributing Features](#contributing-features)
- [API Design Principles](#api-design-principles)
- [How to Contribute Changes](#how-to-contribute-changes)
- [Installation for Contributors](#installation-for-contributors)
- [Code Style and Quality](#code-style-and-quality)
- [Pre-commit tool](#pre-commit-tool)
- [Docstrings](#docstrings)
- [Type checking](#type-checking)
- [Pre-commit tool](#pre-commit-tool)
- [Docstrings](#docstrings)
- [Type checking](#type-checking)
- [Documentation](#documentation)
- [Cookbooks](#cookbooks)
- [Tests](#tests)
@ -41,6 +42,15 @@ For example, counting objects that cross a line anywhere on an image is a common
Before you contribute a new feature, consider submitting an Issue to discuss the feature so the community can weigh in and assist.
### API Design Principles
Supervision APIs should remain generic, composable, and predictable across model families. Before adding a new integration, annotator option, or data conversion method, check the existing `sv.Detections`, `sv.KeyPoints`, and annotator patterns and follow these principles:
1. **Model integrations normalize raw external outputs into existing Supervision containers.** Use `sv.Detections` for detection, segmentation, and other instance-level predictions that include boxes, masks, class ids, confidence scores, or extra per-instance fields. Use `sv.KeyPoints` for standalone keypoint or pose predictions when keypoints exist independently of detection boxes (e.g. pure pose estimation, landmark detection on pre-cropped images). Use `Detections.keypoints` when keypoints are always co-incident with boxes from the same model — the field stores an `(n, K, 2)` or `(n, K, 3)` array where the optional third channel is per-point confidence in `[0, 1]`.
2. **Do not add a `from_<model>` method when the model already returns a Supervision object.** `from_*` methods are for converting raw outputs from external packages such as Ultralytics, Transformers, Inference, or MediaPipe. If a model's `predict()` method already returns `sv.Detections`, keep that result type and store additional structured payloads in `detections.data` or `detections.metadata` using documented keys.
3. **Annotators render data; filtering and visibility are container state.** Filtering by confidence, class id, tracker id, geometry, or custom data should happen before annotation through the container slicing APIs, for example `detections[detections.confidence > 0.7]` or `key_points[key_points.confidence > 0.5]`. Per-point presentation state, such as a `KeyPoints.visible` mask, may live on the container and be honored consistently by annotators.
4. **Annotator constructor arguments should describe visual presentation, not model-quality gates.** Use constructor arguments for color, thickness, opacity, text, position, style, and generic visualization parameters such as sigma levels. Annotators may skip invalid geometry defensively, including missing points, zero-area boxes, non-finite coordinates, or points marked invisible on the container. They should not introduce confidence thresholds or model-specific quality gates as rendering options.
## How to Contribute Changes
First, fork this repository to your own GitHub account. Click "fork" in the top corner of the `supervision` repository to get started:
@ -132,63 +142,63 @@ Before starting your work on the project, set up your development environment:
1. **Clone your fork of the project:**
**Option A: Recommended for most contributors (shallow clone of develop branch):**
**Option A: Recommended for most contributors (shallow clone of develop branch):**
```bash
git clone --depth 1 -b develop https://github.com/YOUR_USERNAME/supervision.git
cd supervision
```
```bash
git clone --depth 1 -b develop https://github.com/YOUR_USERNAME/supervision.git
cd supervision
```
Replace `YOUR_USERNAME` with your GitHub username.
Replace `YOUR_USERNAME` with your GitHub username.
> **Note**: Using `--depth 1` creates a shallow clone with minimal history and `-b develop` ensures you start with the development branch. This significantly reduces download size while providing everything needed to contribute.
> **Note**: Using `--depth 1` creates a shallow clone with minimal history and `-b develop` ensures you start with the development branch. This significantly reduces download size while providing everything needed to contribute.
**Option B: Full repository clone (if you need complete history):**
**Option B: Full repository clone (if you need complete history):**
```bash
git clone https://github.com/YOUR_USERNAME/supervision.git
cd supervision
git checkout develop
```
```bash
git clone https://github.com/YOUR_USERNAME/supervision.git
cd supervision
git checkout develop
```
2. **Set up the upstream remote:**
```bash
git remote add upstream https://github.com/roboflow/supervision.git
git fetch upstream
```
```bash
git remote add upstream https://github.com/roboflow/supervision.git
git fetch upstream
```
3. **Create and activate a virtual environment:**
**On Linux/macOS:**
**On Linux/macOS:**
```bash
python3 -m venv .venv
source .venv/bin/activate
```
```bash
python3 -m venv .venv
source .venv/bin/activate
```
**On Windows:**
**On Windows:**
```cmd
python -m venv .venv
.venv\Scripts\activate
```
```cmd
python -m venv .venv
.venv\Scripts\activate
```
4. **Install `uv`:**
Follow the instructions on the [uv installation page](https://docs.astral.sh/uv/getting-started/installation/).
Follow the instructions on the [uv installation page](https://docs.astral.sh/uv/getting-started/installation/).
5. **Install project dependencies:**
```bash
uv pip install -r pyproject.toml --group dev --group docs --extra metrics
```
```bash
uv pip install -r pyproject.toml --group dev --group docs --extra metrics
```
6. **Verify the setup:**
```bash
uv run pytest
```
```bash
uv run pytest
```
## 🎨 Code Style and Quality
@ -202,27 +212,27 @@ To run the pre-commit tool, follow these steps:
1. **Install pre-commit** (already included if you followed the installation steps above):
```bash
uv sync --group dev
```
```bash
uv sync --group dev
```
2. **Navigate to the project's root directory** (if not already there).
3. **Run pre-commit checks**:
```bash
uv run pre-commit run --all-files
```
```bash
uv run pre-commit run --all-files
```
This will execute the pre-commit hooks configured for this project. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the command until all issues are resolved.
This will execute the pre-commit hooks configured for this project. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the command until all issues are resolved.
4. **Install pre-commit as a git hook** (optional but recommended):
```bash
uv run pre-commit install
```
```bash
uv run pre-commit install
```
This will automatically run pre-commit checks every time you make a `git commit`.
This will automatically run pre-commit checks every time you make a `git commit`.
### Docstrings
@ -230,9 +240,41 @@ All new functions and classes in `supervision` should include docstrings. This i
`supervision` adheres to the [Google Python docstring style](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods). Please refer to the style guide while writing docstrings for your contribution.
Every docstring should include a usage example. When the example only uses `supervision`, NumPy, and the standard library — no optional extras, no external files or network access — strongly prefer `>>>` doctest format so it is automatically verified by the test suite. See [Doctests](#doctests) below for syntax guidance and for when fenced ```` ```python ```` blocks are appropriate instead.
### Type checking
Currently, there is no systematic type checking with mypy implemented in the project. This is a known limitation that may be addressed in future updates.
Type hints are required on all new code. mypy is enforced by the pre-commit hook configured in `.pre-commit-config.yaml` — your PR will fail CI if mypy reports errors.
### Readability
Avoid multi-branch conditional expressions inside function or constructor arguments. If an argument needs more than a simple `a if condition else b`, assign it to a named local variable before the call.
### Performance
- Avoid unnecessary copies of NumPy arrays.
- Prefer vectorized operations over Python loops in hot paths.
- Lazy-import heavy framework dependencies (`torch`, `transformers`, `ultralytics`) inside the function that needs them — never at module top level.
### Deprecation policy
**Minimum window**: deprecated APIs must remain for at least **3 minor releases** before removal. Example: deprecated in `0.29.0` → removed in `0.32.0`.
Use the appropriate mechanism depending on what is being deprecated:
- **Module-level alias**: `supervision.utils.internal.warn_deprecated` in the deprecated module's `__init__.py`
- **Renamed parameter**: `supervision.utils.internal.deprecated_parameter` decorator
- **Public function, method, or class**: `@deprecated` from `pydeprecate`
Always specify both the deprecation version and the planned removal version in the message or decorator arguments.
### Deprecated module aliases
`supervision.keypoint` is deprecated since `0.27.0` and will be removed in `0.30.0`. Always import from `supervision.key_points`:
```python
from supervision.key_points import KeyPoints # correct
```
## 📝 Documentation
@ -242,15 +284,15 @@ To run the documentation locally:
1. **Install documentation dependencies** (if not already installed):
```bash
uv sync --group docs
```
```bash
uv sync --group docs
```
2. **Start the documentation server**:
```bash
uv run mkdocs serve
```
```bash
uv run mkdocs serve
```
3. **Access the documentation** at `http://127.0.0.1:8000` in your browser.
@ -258,9 +300,7 @@ You can learn more about mkdocs on the [mkdocs website](https://www.mkdocs.org/)
## 🧑‍🍳 Cookbooks
We are always looking for new examples and cookbooks to add to the `supervision`
documentation. If you have a use case that you think would be helpful to others, please
submit a PR with your example. Here are some guidelines for submitting a new example:
We are always looking for new examples and cookbooks to add to the `supervision` documentation. If you have a use case that you think would be helpful to others, please submit a PR with your example. Here are some guidelines for submitting a new example:
- Create a new notebook in the [`docs/notebooks`](https://github.com/roboflow/supervision/tree/develop/docs/notebooks) folder.
- Add a link to the new notebook in [`docs/theme/cookbooks.html`](https://github.com/roboflow/supervision/blob/develop/docs/theme/cookbooks.html). Make sure to add the path to the new notebook, as well as a title, labels, author and supervision version.
@ -286,6 +326,87 @@ To run tests with coverage:
uv run pytest --cov=supervision
```
### Test Structure
Follow **Arrange-Act-Assert (AAA)**: one setup block, one action, one assertion group per test. Never put two independent actions in the same test.
**Class grouping:** Group related tests into a class. The class name carries the unit under test; method names describe the expected outcome only — not the mechanism.
```python
class TestDetectionsWithNms:
def test_keeps_highest_confidence_detection(self): ...
def test_suppresses_lower_score_when_overlap_exceeds_threshold(self): ...
def test_raises_when_confidence_missing(self): ...
```
**Parametrize aggressively:** Three or more structurally identical tests should become a single `@pytest.mark.parametrize` case. Use `pytest.param(..., id="slug")` per case — not `ids=[...]` on the decorator — so the ID stays co-located with its arguments and survives reordering.
```python
@pytest.mark.parametrize(
("overlap_metric", "expected_keep"),
[
pytest.param(OverlapMetric.IOU, [True, True], id="iou-keeps-both"),
pytest.param(OverlapMetric.IOS, [True, False], id="ios-suppresses-small"),
],
)
def test_overlap_metric_determines_suppression(
overlap_metric: OverlapMetric, expected_keep: list[bool]
) -> None:
"""Small box inside large: IOU keeps both; IOS suppresses small."""
...
```
**Docstrings:** Every test function/method requires at minimum a one-line docstring (within the project line length configured in `pyproject.toml`). Describe the scenario, not the implementation.
### Doctests
**Guidance:** when an example uses only `supervision`, NumPy, and the standard library — no optional extras (e.g. no `--extra metrics` packages), no external files, no network, no devices — prefer `>>>` doctest format so it is automatically verified by the test suite. Fenced ```` ```python ```` blocks are appropriate when the example cannot reasonably be executed (e.g. loading a third-party model, reading a video file) or when the primary purpose is demonstrating error/exception behaviour rather than return values.
Doctests run automatically as part of the test suite via `--doctest-modules` in `pyproject.toml`. The `ELLIPSIS` and `NORMALIZE_WHITESPACE` flags are enabled globally, so `...` matches any output fragment and minor whitespace differences are ignored.
```bash
uv run pytest --doctest-modules src/
```
**Writing a doctest**
Use the `Example:` section of a Google-style docstring. Prefix each input line with `>>>` and each continuation line with `...`. Place expected output immediately after the last input line with no blank line between them.
```python
def clip_boxes(xyxy: np.ndarray, resolution_wh: tuple) -> np.ndarray:
"""Clip bounding boxes to frame boundaries.
Args:
xyxy: Box coordinates as (N, 4) float array.
resolution_wh: Frame size as (width, height).
Returns:
Clipped boxes as (N, 4) float array.
Example:
>>> import numpy as np
>>> import supervision as sv
>>> boxes = np.array([[-10, -5, 120, 80]], dtype=np.float32)
>>> sv.clip_boxes(boxes, resolution_wh=(100, 60))
array([[ 0., 0., 100., 60.]], dtype=float32)
"""
```
### Key rules
- **Single-line expression** — write the repr as expected output: `>>> len(result)``1`
- **Multi-line statement** — use `...` continuation: `>>> arr = np.array([` / `... [1, 2],` / `... ])`
- **Print output** — write the printed string as expected output (no quotes).
- **`None` return** — no output line needed (suppress with assignment or `_ =`).
- **Large/variable arrays** — use `ELLIPSIS`: `array([...])` matches any content.
- **`# doctest: +SKIP`** — use only as a last resort for genuinely non-runnable lines (e.g. a GPU-only call inside an otherwise runnable example). Prefer splitting the example into two blocks instead.
Fenced ```` ```python ```` blocks remain appropriate for:
- Examples that import optional extras (`supervision[metrics]`, `torch`, `ultralytics`).
- Examples that read files, capture video, or require a running service.
- Illustrative pseudocode that is intentionally incomplete.
## 🔍 PR Review Guidelines
These guidelines help reviewers provide consistent, actionable feedback efficiently. Your goals: validate completeness, identify risks, provide actionable feedback, and highlight quality gaps.

View File

@ -2,17 +2,17 @@
This file provides context-aware guidance for GitHub Copilot when working in the Supervision repository.
---
______________________________________________________________________
## 📚 Repository Overview
**Supervision** is a Python library providing reusable computer vision utilities for working with object detection models (YOLO, SAM, etc.). It offers tools for detections processing, tracking, annotation, and dataset management.
- **Languages**: Python 3.9+
- **Languages**: Python 3.10+
- **Key Dependencies**: NumPy, OpenCV, SciPy
- **License**: MIT
---
______________________________________________________________________
## 🏗️ Project Structure
@ -30,7 +30,7 @@ supervision/
└── examples/ # Usage examples
```
---
______________________________________________________________________
## 🔧 Development Commands
@ -61,7 +61,7 @@ uv run pytest --cov=supervision
uv run mkdocs serve
```
---
______________________________________________________________________
## 💻 Code Conventions
@ -77,8 +77,8 @@ uv run mkdocs serve
- **Linting**: Enforced by `ruff-check` (pre-commit)
- **Type Hints**: Required on all new code
- **Docstrings**: Required using [Google Python style](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods)
- Must include usage examples with primitive values
- Serve as runnable documentation
- Must include usage examples with primitive values
- Serve as runnable documentation
### Performance
@ -92,7 +92,7 @@ uv run mkdocs serve
- Maintain backward compatibility unless explicitly breaking
- Prefer functional utilities over complex classes
---
______________________________________________________________________
## 🧪 Testing Requirements
@ -103,7 +103,7 @@ All new features must include:
- Clear test names describing what they validate
- Proper assertions (not just "no exception raised")
---
______________________________________________________________________
## 📝 Documentation Requirements
@ -114,7 +114,7 @@ For new public functions/classes:
- Entry in appropriate `docs/*.md` file
- Reference in `mkdocs.yml` navigation
---
______________________________________________________________________
## 🔍 Pull Request Reviews
@ -129,7 +129,7 @@ Quick checklist:
- Score code quality, testing, docs (n/5 scale)
- Use inline comments + GitHub suggestion format
---
______________________________________________________________________
## 🌿 Branching & Commits
@ -137,10 +137,10 @@ Quick checklist:
- Use **conventional commits**: `feat:`, `fix:`, `docs:`, `refactor:`, `perf:`, `test:`, `chore:`
- All PRs target `develop` branch
---
______________________________________________________________________
## 🎯 Context-Aware Behavior
**For general development tasks**: Follow [AGENTS.md](../AGENTS.md)
**For pull request reviews**: Follow [PR Review Guidelines](CONTRIBUTING.md#pr-review-guidelines)
**For detailed processes**: Consult [CONTRIBUTING.md](CONTRIBUTING.md)
- **For general development tasks**: Follow [AGENTS.md](../AGENTS.md)
- **For pull request reviews**: Follow [PR Review Guidelines](CONTRIBUTING.md#pr-review-guidelines)
- **For detailed processes**: Consult [CONTRIBUTING.md](CONTRIBUTING.md)

View File

@ -5,6 +5,8 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
commit-message:
prefix: ⬆️
target-branch: "develop"
@ -17,6 +19,8 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
commit-message:
prefix: ⬆️
target-branch: "develop"

12
.github/lychee.toml vendored
View File

@ -9,9 +9,16 @@ accept = [
200, # OK
408, # Request Timeout
# 429 means the server received the request and is actively rate-limiting — the URL is
# reachable. Real dead links return 404, 410, 5xx, or fail to connect; none of those
# produce a 429, so accepting it here does not hide broken links.
# reachable. Real dead links return 404, 410, or fail to connect/resolve; none of
# those produce a 429, so accepting it here does not hide broken links.
429, # Too Many Requests (rate-limited but reachable; does not mask dead links)
# CI regularly sees momentary 502/503/504 from large, healthy hosts (github.com,
# supervision.roboflow.com), and in-run retries tend to land inside the same
# incident window. Genuinely dead links surface as 404, 410, or connection/DNS
# failures, which remain rejected.
502, # Bad Gateway (transient upstream hiccup)
503, # Service Unavailable (transient overload or maintenance)
504, # Gateway Timeout (transient upstream hiccup)
]
exclude = [
@ -19,6 +26,7 @@ exclude = [
"http://127.0.0.1:8000", # hint for local docs server
"https://sam2.metademolab.com/", # returns 403 Forbidden
"https://snyk.io/advisor/python/supervision/badge.svg", # badge URL
"https://trendshift.io", # badge API times out in CI
"https://universe.roboflow.com/",
"https://universe.roboflow.com/model-examples/segmented-animals-basic",
# fixme: this page returns 401 Unauthorized when accessed and 404 Not Found when accessed with browser,

109
.github/scripts/check_doctest_fences.py vendored Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""Validate doctest prompt formatting in source docstrings."""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
DOCTEST_PROMPT_RE = re.compile(r"^\s*>>>")
FENCE_RE = re.compile(r"^\s*```(?P<language>[A-Za-z0-9_-]*)\s*$")
def _check_content(content: str, path: Path) -> list[str]:
r"""Return doctest fence violations for file content.
Examples:
```pycon
>>> from pathlib import Path
>>> _check_content('```pycon\n>>> len([1])\n1\n\n```\n', Path('src/a.py'))
[]
>>> _check_content('>>> len([1])\n1\n', Path('src/a.py'))
['src/a.py:1: doctest prompt must be inside a ```pycon fenced block']
>>> violations = _check_content(
... '```pycon\n>>> len([1])\n1\n```\n', Path('src/a.py')
... )
>>> violations == [
... 'src/a.py:4: pycon doctest block must include exactly one blank line '
... 'before the closing fence'
... ]
True
```
"""
violations: list[str] = []
active_fence_language: str | None = None
in_invalid_doctest_block = False
line_before_previous = ""
previous_line = ""
for line_number, line in enumerate(content.splitlines(), start=1):
fence_match = FENCE_RE.match(line)
if fence_match is not None:
in_invalid_doctest_block = False
if active_fence_language is None:
active_fence_language = fence_match.group("language")
else:
has_exactly_one_blank_line = (
previous_line.strip() == "" and line_before_previous.strip() != ""
)
if active_fence_language == "pycon" and not has_exactly_one_blank_line:
violations.append(
f"{path}:{line_number}: pycon doctest block must include "
"exactly one blank line before the closing fence"
)
active_fence_language = None
line_before_previous = previous_line
previous_line = line
continue
if not line.strip():
in_invalid_doctest_block = False
if (
DOCTEST_PROMPT_RE.match(line)
and active_fence_language != "pycon"
and not in_invalid_doctest_block
):
violations.append(
f"{path}:{line_number}: doctest prompt must be inside a "
"```pycon fenced block"
)
in_invalid_doctest_block = True
line_before_previous = previous_line
previous_line = line
return violations
def check_file(path: Path) -> list[str]:
"""Return doctest fence violations for a single source file."""
if not path.is_file() or path.suffix != ".py" or "src" not in path.parts:
return []
return _check_content(content=path.read_text(encoding="utf-8"), path=path)
def main() -> int:
"""Run the doctest fence check for pre-commit supplied files."""
parser = argparse.ArgumentParser(
description="Validate doctest prompts in src/ are fenced as pycon blocks."
)
parser.add_argument("files", nargs="*", type=Path)
args = parser.parse_args()
violations = [
violation for path in args.files for violation in check_file(path=path)
]
if violations:
print("\n".join(violations))
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

125
.github/scripts/verify_clean_wheel.py vendored Normal file
View File

@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Verify a built Supervision wheel works without OpenCV."""
from __future__ import annotations
import argparse
import subprocess
import zipfile
from email import message_from_bytes
from email.message import Message
from pathlib import Path
_MANIFEST_CHECKS = {
"import-supervision",
"no-cv2-module",
"fallback-backend",
"bgr-to-gray",
"draw-rectangle",
"required-pyav",
}
def _wheel_metadata(wheel: Path) -> Message:
"""Read the core metadata embedded in a wheel archive."""
with zipfile.ZipFile(wheel) as archive:
metadata_paths = [
name for name in archive.namelist() if name.endswith("/METADATA")
]
if len(metadata_paths) != 1:
raise ValueError(
f"expected one METADATA file in {wheel}, found {metadata_paths}"
)
return message_from_bytes(archive.read(metadata_paths[0]))
def _validate_metadata(wheel: Path) -> None:
"""Reject wheels that retain an OpenCV runtime requirement or extra."""
metadata = _wheel_metadata(wheel)
requirements = metadata.get_all("Requires-Dist", [])
extras = metadata.get_all("Provides-Extra", [])
if any("opencv" in requirement.lower() for requirement in requirements):
raise ValueError(
f"OpenCV runtime requirement remains in {wheel}: {requirements}"
)
if any("opencv" in extra.lower() for extra in extras):
raise ValueError(f"OpenCV extra remains in {wheel}: {extras}")
def _validate_manifest(manifest: Path) -> None:
"""Keep the installed-wheel fallback smoke contract explicit and complete."""
checks = {
s
for line in manifest.read_text(encoding="utf-8").splitlines()
if (s := line.strip()) and not s.startswith("#")
}
if checks != _MANIFEST_CHECKS:
raise ValueError(
f"unexpected fallback manifest {checks}; expected {_MANIFEST_CHECKS}"
)
def _run_installed_wheel_probe(python: Path) -> None:
"""Exercise the installed fallback without allowing the source tree on sys.path."""
source = """
import importlib.util
from importlib import metadata
from pathlib import Path
import av
import numpy as np
import supervision
from supervision import _cv2
package_path = Path(supervision.__file__).resolve()
if "site-packages" not in package_path.parts:
raise AssertionError(
f"supervision did not import from site-packages: {package_path}"
)
if importlib.util.find_spec("cv2") is not None:
raise AssertionError("cv2 is installed in the clean-wheel environment")
opencv_distributions = [
distribution.metadata["Name"]
for distribution in metadata.distributions()
if "opencv" in distribution.metadata["Name"].lower()
]
if opencv_distributions:
raise AssertionError(
"OpenCV distributions remain in the clean-wheel environment: "
f"{opencv_distributions}"
)
if _cv2.BACKEND_NAME != "fallback":
raise AssertionError(f"expected fallback backend, got {_cv2.BACKEND_NAME!r}")
image = np.array([[[0, 0, 255]]], dtype=np.uint8)
assert _cv2.cvtColor(image, _cv2.COLOR_BGR2GRAY).tolist() == [[76]]
canvas = np.zeros((3, 3, 3), dtype=np.uint8)
assert _cv2.rectangle(canvas, (0, 0), (2, 2), (1, 2, 3), -1) is canvas
assert canvas.tolist() == [[[1, 2, 3]] * 3] * 3
assert av.__version__
"""
subprocess.run( # noqa: S603 - the caller passes the clean CI interpreter explicitly.
[str(python), "-c", source],
check=True,
cwd=Path.cwd().parent,
)
subprocess.run( # noqa: S603 - the caller passes the clean CI interpreter explicitly.
[str(python), "-m", "pip", "check"], check=True
)
def main() -> None:
"""Validate one wheel against a previously prepared clean environment."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--wheel", type=Path, required=True)
parser.add_argument("--python", type=Path, required=True)
parser.add_argument("--manifest", type=Path, required=True)
args = parser.parse_args()
_validate_metadata(args.wheel)
_validate_manifest(args.manifest)
_run_installed_wheel_probe(args.python)
if __name__ == "__main__":
main()

View File

@ -20,10 +20,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 🐍 Install uv and set Python version ${{ inputs.python-version }}
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: ${{ inputs.python-version }}
activate-environment: true

View File

@ -22,10 +22,10 @@ jobs:
timeout-minutes: 10
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 🐍 Install uv and set Python
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.10"
activate-environment: true

View File

@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7.0.1
- name: 🔗 Link Checker
uses: lycheeverse/lychee-action@v2

View File

@ -22,31 +22,57 @@ jobs:
link-check: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
run-tests:
name: Import Test and Pytest Run
name: Pytest Run
# needs: build # todo: consider using this build package for testing
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
cv2: ["none"]
include:
- { os: "ubuntu-latest", python-version: "3.13", cv2: "opencv-python" }
- { os: "windows-latest", python-version: "3.13", cv2: "opencv-python" }
- { os: "macos-latest", python-version: "3.13", cv2: "opencv-python" }
- { os: "ubuntu-latest", python-version: "3.13", cv2: "opencv-python-headless" }
- { os: "windows-latest", python-version: "3.13", cv2: "opencv-python-headless" }
- { os: "macos-latest", python-version: "3.13", cv2: "opencv-python-headless" }
runs-on: ${{ matrix.os }}
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
- name: 🚀 Install Packages
run: uv sync --frozen --group dev --group docs --extra metrics
run: uv sync --frozen --group dev --extra metrics
- name: 📷 Install selected OpenCV package
if: matrix.cv2 != 'none'
run: uv pip install ${{ matrix.cv2 }}
- name: 🧭 Confirm selected cv2 backend
env:
EXPECTED_BACKEND: ${{ matrix.cv2 == 'none' && 'fallback' || 'opencv' }}
run: |
import os
from supervision import _cv2
expected = os.environ["EXPECTED_BACKEND"]
assert _cv2.BACKEND_NAME == expected, f"expected {expected!r}, got {_cv2.BACKEND_NAME!r}"
shell: python
- name: 📦 Run the Import test
run: python -c "import supervision; from supervision import assets; from supervision import metrics; print(supervision.__version__)"
- name: 📋 Print installed packages
run: uv pip list
- name: 🧪 Run the Test
run: pytest src/ tests/ --cov=supervision --cov-report=xml
@ -56,7 +82,7 @@ jobs:
coverage report
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: "coverage.xml"
@ -68,23 +94,66 @@ jobs:
- name: Minimize uv cache
run: uv cache prune --ci
clean-wheel:
name: Clean Wheel on ${{ matrix.os }} / Python ${{ matrix.python-version }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
python-version: ["3.10", "3.13"]
runs-on: ${{ matrix.os }}
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
- name: 🏗️ Build the wheel
run: |
uv sync --frozen --group build
uv build --wheel
- name: 🧼 Create an isolated wheel environment
shell: bash
run: |
uv venv --clear --seed .clean-wheel
if [ "$RUNNER_OS" = "Windows" ]; then
echo "CLEAN_PYTHON=$PWD/.clean-wheel/Scripts/python.exe" >> "$GITHUB_ENV"
else
echo "CLEAN_PYTHON=$PWD/.clean-wheel/bin/python" >> "$GITHUB_ENV"
fi
- name: 📦 Install and verify the wheel without OpenCV
shell: bash
run: |
uv pip install --python "$CLEAN_PYTHON" --strict dist/*.whl
"$CLEAN_PYTHON" .github/scripts/verify_clean_wheel.py \
--wheel dist/*.whl \
--python "$CLEAN_PYTHON" \
--manifest tests/cv2/installed_wheel_fallback_manifest.txt
testing-guardian:
runs-on: ubuntu-latest
needs: run-tests
needs: [run-tests, clean-wheel]
if: always()
steps:
- name: 📋 Display test result
run: echo "${{ needs.run-tests.result }}"
run: echo "tests=${{ needs.run-tests.result }}, clean-wheel=${{ needs.clean-wheel.result }}"
- name: ❌ Fail guardian on test failure
if: needs.run-tests.result == 'failure'
if: needs.run-tests.result == 'failure' || needs.clean-wheel.result == 'failure'
run: exit 1
# Ensure that cancelled or skipped test runs still cause this guardian job to fail,
# using an explicit exit code instead of relying on timeout behavior.
- name: ⚠️ cancelled or skipped...
if: contains(fromJSON('["cancelled", "skipped"]'), needs.run-tests.result)
if: contains(fromJSON('["cancelled", "skipped"]'), needs.run-tests.result) || contains(fromJSON('["cancelled", "skipped"]'), needs.clean-wheel.result)
run: |
echo "run-tests job result is '${{ needs.run-tests.result }}'; failing explicitly."
exit 1
- name: ✅ tests succeeded
if: needs.run-tests.result == 'success'
run: echo "All tests completed successfully in job 'run-tests'."
run: echo "All tests completed successfully."

View File

@ -32,12 +32,12 @@ jobs:
timeout-minutes: 10
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: 🐍 Install uv and set Python
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
python-version: "3.10"
activate-environment: true
@ -62,15 +62,32 @@ jobs:
env:
MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.GITHUB_TOKEN }}
run: |
if mike list | grep -Eq '^latest(\s|$)'; then mike delete latest; fi
mike deploy --push latest
- name: 🏷️ Determine release deployment metadata
id: release_metadata
run: |
is_rc=false
release_tag=""
if [[ "$GITHUB_EVENT_NAME" == "release" ]]; then
release_tag="${GITHUB_REF_NAME#v}"
release_tag="${release_tag%.post*}"
release_tag_lower="${release_tag,,}"
# Match RC suffixes with separators (1.0-rc1, 1.0.rc1) or compact form (1.0rc1).
if [[ "$release_tag_lower" =~ (^|[._-])rc[0-9]+$ ]] || [[ "$release_tag_lower" =~ [0-9]rc[0-9]+$ ]]; then
is_rc=true
fi
fi
echo "is_rc=$is_rc" >> "$GITHUB_OUTPUT"
echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT"
- name: 🚀 Deploy Release Docs
if: github.event_name == 'release' && github.event.action == 'published'
if: github.event_name == 'release' && github.event.action == 'published' && steps.release_metadata.outputs.is_rc != 'true'
env:
MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.GITHUB_TOKEN }}
run: |
release_tag="${GITHUB_REF_NAME#v}"
mike deploy --push "$release_tag"
mike deploy --push "${{ steps.release_metadata.outputs.release_tag }}"
# IndexNow key: 0d5d9799b1cc4a39825146388c6781eb
# This key must stay in sync across three files:
@ -84,7 +101,7 @@ jobs:
(github.event_name == 'push' && github.ref == 'refs/heads/develop') ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/release/latest') ||
(github.event_name == 'release' && github.event.action == 'published')
(github.event_name == 'release' && github.event.action == 'published' && steps.release_metadata.outputs.is_rc != 'true')
run: |
cp docs/robots.txt /tmp/robots.txt
cp docs/llms.txt /tmp/llms.txt

View File

@ -3,9 +3,12 @@ name: Publish Supervision Pre-Releases to PyPI
on:
push:
tags:
- "[0-9]+.[0-9]+[0-9]+.[0-9]+a[0-9]"
- "[0-9]+.[0-9]+[0-9]+.[0-9]+b[0-9]"
- "[0-9]+.[0-9]+[0-9]+.[0-9]+rc[0-9]"
- "[0-9]+.[0-9]+.[0-9]+a[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+b[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.a[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.b[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.rc[0-9]+"
workflow_dispatch:
pull_request:
branches: [main, develop]
@ -43,6 +46,6 @@ jobs:
- name: 🚀 Publish to PyPi
if: github.event_name != 'pull_request'
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
with:
attestations: true

View File

@ -40,7 +40,7 @@ jobs:
- name: 📦 Upload assets to Release
if: github.event_name == 'release'
uses: AButler/upload-release-assets@v3.0
uses: AButler/upload-release-assets@v4.0
with:
files: "dist/*"
repo-token: ${{ secrets.GITHUB_TOKEN }}
@ -48,6 +48,6 @@ jobs:
- name: 🚀 Publish to PyPi
# We only want to publish to PyPi if the event is a release and it's not a pre-release.
if: (github.event_name == 'release' && github.event.release.prerelease != true) || github.event_name == 'workflow_dispatch'
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
with:
attestations: true

View File

@ -38,7 +38,7 @@ jobs:
- name: 🚀 Publish to Test-PyPi
if: github.event_name != 'pull_request'
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
with:
repository-url: https://test.pypi.org/legacy/
attestations: true

22
.gitignore vendored
View File

@ -156,11 +156,27 @@ Desktop.ini
# local data
data/
examples/*/outputs/
!src/supervision/_cv2/data/
!src/supervision/_cv2/data/*
*.mp4
*.pt
# some artifacts
/*.py
/*.jpg
/*.png
/*.tif
/*.json
# Claude working scratchpad (plans, lessons, ephemeral artefacts)
.claude/logs
.claude/logs/
.claude/state/
.claude/worktrees/
.developments/
.plans/
.notes/
.reports/
.temp/
.tmp/
@ -169,3 +185,7 @@ _resolutions/
_reviews/
tasks/
*.local.md
output/
notebooks/
releases/

View File

@ -1,5 +1,5 @@
default_language_version:
python: python3
python: python3.10
ci:
autofix_prs: true
@ -8,6 +8,14 @@ ci:
autoupdate_commit_msg: "chore(pre_commit): ⬆ pre_commit autoupdate"
repos:
- repo: local
hooks:
- id: check-doctest-fences
name: check doctest fences
entry: python .github/scripts/check_doctest_fences.py
language: python
files: ^src/.*\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
@ -28,8 +36,8 @@ repos:
- id: end-of-file-fixer
- id: mixed-line-ending
- repo: https://github.com/JoC0de/pre-commit-prettier
rev: v3.8.3 # using tag; previously pinned SHA when tags were not persistent
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.9.6
hooks:
- id: prettier
files: \.(ya?ml|toml)$
@ -37,9 +45,11 @@ repos:
args: ["--print-width=120"]
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.21.1
rev: v2.26.0
hooks:
- id: pyproject-fmt
additional_dependencies:
- "tomli>=2.0.1"
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.25
@ -47,7 +57,7 @@ repos:
- id: validate-pyproject
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
rev: v0.16.1
hooks:
- id: ruff-check
args: ["--fix"]
@ -58,24 +68,37 @@ repos:
rev: 1.0.0
hooks:
- id: mdformat
name: mdformat (gfm)
exclude: ^docs/
additional_dependencies:
- "mdformat-frontmatter"
- "mdformat-gfm"
- "mdformat-ruff"
args: ["--number", "--wrap=no"]
- id: mdformat
name: mdformat (mkdocs)
files: ^docs/
additional_dependencies:
- "mdformat-frontmatter"
- "mdformat-mkdocs[recommended]>=2.1.0"
- "mdformat-ruff"
args: ["--number"]
exclude: ^(docs/changelog\.md|docs/deprecated\.md)$
args: ["--number", "--wrap=no"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.20.2
rev: v2.3.0
hooks:
- id: mypy
language_version: python3.11
additional_dependencies:
- "numpy>=2.0"
- "types-PyYAML"
- "types-requests"
- "types-tqdm"
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
rev: v2.4.3
hooks:
- id: codespell
exclude: ^src/supervision/_cv2/data/hershey_fonts\.json$
additional_dependencies:
- "tomli>=2.0.1"

181
AGENTS.md
View File

@ -1,98 +1,153 @@
# Agent Guidelines for `supervision`
These instructions define how AI agents (GitHub Copilot, Claude, etc.) should behave when
assigned an issue, task, or multi-step problem in this repository.
Behave like a senior contributor: precise, efficient, maintainable. When this file and [CONTRIBUTING.md](.github/CONTRIBUTING.md) conflict, **CONTRIBUTING.md wins**.
Behave like a senior contributor: precise, efficient, aligned with the project's
philosophy, and focused on maintainability and clarity.
---
______________________________________________________________________
## 1. Before You Code
- Read the task/issue thoroughly before acting.
- Identify missing information; ask **one targeted clarification question** if needed.
- Outline a step-by-step plan before making changes.
- Check whether the feature or fix already exists under a different name.
- Confirm alignment with the repository's architecture (`src/supervision/`).
- Read the task thoroughly; group clarifications into one ask.
- Outline a plan before making changes.
- Check whether the feature already exists under a different name.
- Confirm alignment with `src/supervision/` architecture.
---
______________________________________________________________________
## 2. Repository Conventions
## 2. Repository Architecture
All work must follow the conventions of the `supervision` library
(see [CONTRIBUTING.md](.github/CONTRIBUTING.md) for full details).
**Package root**: `src/supervision/` — all library code. **Tests**: `tests/` — mirrors `src/supervision/`. **Public API**: `src/supervision/__init__.py`.
### Branching & Commits
```
src/supervision/
├── detection/
│ ├── core.py — Detections dataclass; all model connectors as classmethods
│ ├── compact_mask.py — compact mask representation
│ ├── vlm.py — VLM connectors (Florence-2, Gemini, Qwen, PaliGemma)
│ ├── utils/ — pure NumPy helpers: boxes, converters, iou_and_nms, masks, polygons
│ ├── line_zone.py — LineZone
│ └── tools/ — InferenceSlicer, PolygonZone, CSVSink, JSONSink, DetectionsSmoother
├── annotators/core.py — BoxAnnotator, MaskAnnotator, LabelAnnotator, … each: .annotate(scene, detections)
├── key_points/ — KeyPoints, EdgeAnnotator, VertexAnnotator (use this, NOT keypoint/ — see §4)
├── tracker/ — DEPRECATED
├── dataset/core.py — DetectionDataset / ClassificationDataset (YOLO / COCO / Pascal VOC)
├── geometry/core.py — Point, Rect, Vector, Position
├── metrics/ — mAP, confusion matrix (requires --extra metrics)
├── utils/internal.py — warn_deprecated, deprecated_parameter, internal helpers
└── config.py — string constants; always import from here, never use literals
```
- Branch from `develop` using prefixes: `feat/`, `fix/`, `docs/`, `refactor/`, `test/`, `chore/`.
- Use **conventional commits**: `feat:`, `fix:`, `docs:`, `refactor:`, `perf:`, `test:`, `chore:`.
- PRs must target the `develop` branch.
### Key design patterns
### Code Style
- **`Detections` is the lingua franca** — every connector, tracker, and annotator speaks `Detections`. New connector = `@classmethod from_<framework>(cls, result) -> Detections`.
- **Annotators are composable** — receive `scene` (BGR `np.ndarray`) + `detections`, return annotated copy.
- **`data` dict extensibility** — per-detection metadata in `detections.data` as `np.ndarray` aligned with `xyxy`. Keys are constants from `config.py`.
- **Vectorized throughout** — NumPy arrays, no Python loops in hot paths. Never write `for det in detections`.
- **Lazy-import heavy deps**`torch`, `transformers`, `ultralytics` must be imported inside the function that needs them, never at module top level.
- **Formatting and linting** are enforced by **pre-commit**.
The hook chain typically includes: ruff-check, ruff-format, codespell, mdformat,
prettier, pyproject-fmt, and standard pre-commit-hooks (trailing whitespace, YAML, TOML, etc.).
- **Type hints**: required on all new code. Type checking with mypy is encouraged but not
currently enforced systematically by pre-commit; see [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md)
for the latest type-checking expectations.
- **Docstrings**: Google Python docstring style. Required for all new functions and classes.
Docstrings should include usage examples demonstrating the function with primitive values
so they serve as runnable documentation.
______________________________________________________________________
### API Consistency
## 3. Agent-Critical Rules
- Follow existing naming patterns.
- Maintain backward compatibility unless explicitly allowed.
- Prefer functional utilities over complex classes unless justified.
These supplement [CONTRIBUTING.md](.github/CONTRIBUTING.md) — covering gaps or agent-specific failure modes.
### Performance
**Doc headings**: `###` max in docstrings and docs. `####` renders identically to bold in mkdocs — use `**bold**` instead.
- Avoid unnecessary copies of NumPy arrays.
- Prefer vectorized operations over Python loops in hot paths.
- Use OpenCV operations efficiently.
**Type hints**: required on all new code. mypy is enforced by pre-commit (`.pre-commit-config.yaml`).
---
**Function docstrings**: every new or modified function, including private helpers and tests, must have a succinct docstring explaining its purpose. Put function-level why/what/how context inside the function docstring, not in a comment before the function. Public APIs still require the full Google-style structure described below.
## 3. Implementing Features
**Readable argument lists**: do not put multi-branch conditional expressions inside function or constructor arguments. If an argument needs more than a simple `a if condition else b`, assign it to a named local variable before the call.
- Provide a minimal, clean implementation.
- Include type hints and Google-style docstrings with usage examples.
- All new functionality must be covered with tests, including edge cases.
- Add or update documentation (docstrings + mkdocs entries if applicable).
- Ensure compatibility with core dependencies: NumPy, OpenCV, SciPy.
**Inline comments**: write code so the intent is clear from names, small helpers, and straightforward control flow. For non-trivial logic inside a function that still needs context, add concise inline comments explaining why the code exists, what invariant it protects, and how the tricky part works. Do not put comments before functions; use the function docstring instead. Do not comment obvious assignments, mechanical plumbing, lint-only changes, typing-only changes, or pure docs edits.
---
**Doctest determinism** — output must be reproducible across platforms:
## 4. Fixing Bugs
- Use `# doctest: +ELLIPSIS` for floats that vary by platform.
- Seed any RNG before calling it.
- Never assert `dict` or `set` iteration order.
- No network or filesystem access outside `supervision/assets/`.
1. Reproduce and understand the root cause.
2. Write a test that reproduces the bug (it should fail before the fix).
3. Apply a minimal, targeted fix.
4. Verify the test passes and no other components break.
**⚠ Test structure** — agents frequently fail here; read [CONTRIBUTING.md §Tests](.github/CONTRIBUTING.md#-tests) carefully: AAA structure, class grouping, parametrize with `pytest.param(..., id="slug")`, one-line docstring per test.
---
For branching, commit, code style, and API design conventions see [CONTRIBUTING.md](.github/CONTRIBUTING.md).
## 5. Refactoring
______________________________________________________________________
- Preserve behavior and API stability.
- Improve readability or performance.
- Reduce duplication.
- Avoid large, sweeping refactors unless explicitly requested.
## 4. Deprecated Module Aliases
---
`supervision.keypoint` deprecated since `0.27.0`, removed in `0.31.0`. Always import from `supervision.key_points`, not `supervision.keypoint`.
## 6. Before You Commit
______________________________________________________________________
Always run these before committing:
## 5. Deprecating APIs
**Minimum window**: deprecated APIs must remain for at least **3 minor releases** before removal. Example: deprecated in `0.29.0` → removed in `0.32.0`.
- Module-level: `supervision.utils.internal.warn_deprecated` in the deprecated module's own `__init__.py`
- Parameter renamed (old→new): `supervision.utils.internal.deprecated_parameter` decorator
- Public function, method, or class: `@deprecated` from `pydeprecate`
Always name the version introduced and the removal version:
```python
warn_deprecated("'foo' deprecated in `0.29.0`, removed in `0.32.0`. Use 'bar'.")
```
______________________________________________________________________
## 6. Implementing Features
- Minimal implementation; type hints and Google docstrings with usage examples.
- Tests covering new functionality and edge cases (see [CONTRIBUTING.md §Tests](.github/CONTRIBUTING.md#-tests)).
- Update docstrings and mkdocs entries as needed.
- Update [docs/changelog.md](docs/changelog.md) for every functional change or bug fix, including user-visible behavior changes. Skip changelog entries for lint-only, type-only, formatting-only, and pure documentation-only changes.
**Extending `Detections`**: store metadata in `detections.data` as `np.ndarray` aligned with `xyxy`; define the key as a constant in `config.py` (e.g. `CLASS_NAME_DATA_FIELD`, `ORIENTED_BOX_COORDINATES`).
**New model connector** (`detection/core.py`):
```python
@classmethod
def from_myframework(cls, result) -> "Detections":
import myframework # noqa: F401 — lazy import
xyxy = ... # (N, 4)
return cls(
xyxy=xyxy,
confidence=...,
class_id=...,
data={CLASS_NAME_DATA_FIELD: np.array([...])},
)
```
VLM connectors go in `detection/vlm.py`, not `core.py`.
______________________________________________________________________
## 7. Bugs & Refactoring
**Bugs**: reproduce → write failing test → minimal fix → verify no regressions.
**Refactoring**: preserve behavior and API; reduce duplication; avoid sweeping changes unless requested; apply §5 deprecation when removing public API.
______________________________________________________________________
## 8. Before You Commit
```bash
uv run pytest --cov=supervision
uv run pre-commit run --all-files
```
- All pre-commit hooks must pass (formatting, linting, type checking, spell check, etc.).
- All tests must pass before opening a PR. Note: some existing tests in the repo may
already be failing — your changes must not introduce new failures.
- Fix any issues reported and re-run until clean.
Capture a baseline before changes to avoid introducing new failures:
```bash
STASH_BEFORE=$(git rev-parse refs/stash 2>/dev/null)
git stash push --include-untracked
uv run pytest -q 2>&1 | tee /tmp/baseline.txt
[ "$(git rev-parse refs/stash 2>/dev/null)" != "$STASH_BEFORE" ] && git stash pop
uv run pytest -q 2>&1 | tee /tmp/after.txt
diff /tmp/baseline.txt /tmp/after.txt
```
Any test passing in baseline but failing after = blocker.

View File

@ -2,20 +2,8 @@ MIT License
Copyright (c) 2022 Roboflow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

216
README.md
View File

@ -1,6 +1,6 @@
<div align="center">
<p>
<a align="center" href="" target="https://supervision.roboflow.com">
<a align="center" href="https://supervision.roboflow.com" target="_blank">
<img
width="100%"
src="https://media.roboflow.com/open-source/supervision/rf-supervision-banner.png?updatedAt=1678995927529"
@ -14,16 +14,9 @@
<br>
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
[![codecov](https://codecov.io/gh/roboflow/supervision/graph/badge.svg?token=HMNJ5FVZ36)](https://codecov.io/gh/roboflow/supervision)
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision) [![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision) [![license](https://img.shields.io/pypi/l/supervision)](LICENSE.md) [![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision) [![codecov](https://codecov.io/gh/roboflow/supervision/graph/badge.svg?token=HMNJ5FVZ36)](https://codecov.io/gh/roboflow/supervision)
[![snyk](https://snyk.io/advisor/python/supervision/badge.svg)](https://snyk.io/advisor/python/supervision)
[![colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb)
[![gradio](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/Roboflow/Annotators)
[![discord](https://img.shields.io/discord/1159501506232451173?logo=discord&label=discord&labelColor=fff&color=5865f2&link=https%3A%2F%2Fdiscord.gg%2FGbfgXGJ8Bk)](https://discord.gg/GbfgXGJ8Bk)
[![snyk](https://snyk.io/advisor/python/supervision/badge.svg)](https://snyk.io/advisor/python/supervision) [![colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb) [![gradio](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/Roboflow/Annotators) [![discord](https://img.shields.io/discord/1159501506232451173?logo=discord&label=discord&labelColor=fff&color=5865f2&link=https%3A%2F%2Fdiscord.gg%2FGbfgXGJ8Bk)](https://discord.gg/GbfgXGJ8Bk)
<div align="center">
<a href="https://trendshift.io/repositories/124" target="_blank"><img src="https://trendshift.io/api/badge/repositories/124" alt="roboflow%2Fsupervision | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
@ -31,14 +24,29 @@
</div>
## 👋 hello
<details>
<summary><strong>📑 Table of Contents</strong></summary>
**We write your reusable computer vision tools.** Whether you need to load your dataset from your hard drive, draw detections on an image or video, or count how many detections are in a zone. You can count on us! 🤝
- [👋 Hello](#-hello)
- [💻 Install](#-install)
- [🔥 Quickstart](#-quickstart)
- [Models](#models)
- [Annotators](#annotators)
- [Datasets](#datasets)
- [🎬 Tutorials](#-tutorials)
- [💜 Built with Supervision](#-built-with-supervision)
- [📚 Documentation](#-documentation)
- [🏆 Contribution](#-contribution)
## 💻 install
</details>
Pip install the supervision package in a
[**Python>=3.9**](https://www.python.org/) environment.
## 👋 Hello
**We are your essential toolkit for computer vision.** From data loading to real-time zone counting, we provide the building blocks so you can focus on building applications around your models. 🤝
## 💻 Install
Pip install the supervision package in a [**Python>=3.10**](https://www.python.org/) environment.
```bash
pip install supervision
@ -46,9 +54,9 @@ pip install supervision
Read more about conda, mamba, and installing from source in our [guide](https://roboflow.github.io/supervision/).
## 🔥 quickstart
## 🔥 Quickstart
### models
### Models
Supervision was designed to be model agnostic. Just plug in any classification, detection, or segmentation model. For your convenience, we have created [connectors](https://supervision.roboflow.com/latest/detection/core/#detections) for the most popular libraries like Ultralytics, Transformers, MMDetection, or Inference. Other integrations, like `rfdetr`, already return `sv.Detections` directly.
@ -59,7 +67,7 @@ import supervision as sv
from PIL import Image
from rfdetr import RFDETRSmall
image = Image.open(...)
image = Image.open("path/to/image.jpg")
model = RFDETRSmall()
detections = model.predict(image, threshold=0.5)
@ -72,25 +80,25 @@ len(detections)
- inference
Running with [Inference](https://github.com/roboflow/inference) requires a [Roboflow API KEY](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key).
Running with [Inference](https://github.com/roboflow/inference) requires a [Roboflow API KEY](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key).
```python
import supervision as sv
from PIL import Image
from inference import get_model
```python
import supervision as sv
from PIL import Image
from inference import get_model
image = Image.open(...)
model = get_model(model_id="rfdetr-small", api_key="ROBOFLOW_API_KEY")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
image = Image.open("path/to/image.jpg")
model = get_model(model_id="rfdetr-small", api_key="ROBOFLOW_API_KEY")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
len(detections)
# 5
```
len(detections)
# 5
```
</details>
### annotators
### Annotators
Supervision offers a wide range of highly customizable [annotators](https://supervision.roboflow.com/latest/detection/annotators/), allowing you to compose the perfect visualization for your use case.
@ -98,7 +106,8 @@ Supervision offers a wide range of highly customizable [annotators](https://supe
import cv2
import supervision as sv
image = cv2.imread(...)
image = cv2.imread("path/to/image.jpg")
# Assuming detections are obtained from a model
detections = sv.Detections(...)
box_annotator = sv.BoxAnnotator()
@ -107,7 +116,7 @@ annotated_frame = box_annotator.annotate(scene=image.copy(), detections=detectio
https://github.com/roboflow/supervision/assets/26109316/691e219c-0565-4403-9218-ab5644f39bce
### datasets
### Datasets
Supervision provides a set of [utils](https://supervision.roboflow.com/latest/datasets/core/) that allow you to load, split, merge, and save datasets in one of the supported formats.
@ -131,97 +140,97 @@ for path, image, annotation in ds:
pass
```
<details close>
<details>
<summary>👉 more dataset utils</summary>
- load
```python
dataset = sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
)
```python
dataset = sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
)
dataset = sv.DetectionDataset.from_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
dataset = sv.DetectionDataset.from_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
dataset = sv.DetectionDataset.from_coco(
images_directory_path=...,
annotations_path=...,
)
```
dataset = sv.DetectionDataset.from_coco(
images_directory_path=...,
annotations_path=...,
)
```
- split
```python
train_dataset, test_dataset = dataset.split(split_ratio=0.7)
test_dataset, valid_dataset = test_dataset.split(split_ratio=0.5)
```python
train_dataset, test_dataset = dataset.split(split_ratio=0.7)
test_dataset, valid_dataset = test_dataset.split(split_ratio=0.5)
len(train_dataset), len(test_dataset), len(valid_dataset)
# (700, 150, 150)
```
len(train_dataset), len(test_dataset), len(valid_dataset)
# (700, 150, 150)
```
- merge
```python
ds_1 = sv.DetectionDataset(...)
len(ds_1)
# 100
ds_1.classes
# ['dog', 'person']
```python
ds_1 = sv.DetectionDataset(...)
len(ds_1)
# 100
ds_1.classes
# ['dog', 'person']
ds_2 = sv.DetectionDataset(...)
len(ds_2)
# 200
ds_2.classes
# ['cat']
ds_2 = sv.DetectionDataset(...)
len(ds_2)
# 200
ds_2.classes
# ['cat']
ds_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
ds_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
- save
```python
dataset.as_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
)
```python
dataset.as_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
)
dataset.as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
dataset.as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
dataset.as_coco(
images_directory_path=...,
annotations_path=...,
)
```
dataset.as_coco(
images_directory_path=...,
annotations_path=...,
)
```
- convert
```python
sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
).as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
```
```python
sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
).as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
```
</details>
## 🎬 tutorials
## 🎬 Tutorials
Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](./examples), [cheatsheet](https://roboflow.github.io/cheatsheet-supervision/), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!
@ -241,7 +250,7 @@ Want to learn how to use Supervision? Explore our [how-to guides](https://superv
<div><strong>Created: 11 Jan 2024</strong></div>
<br/>Learn how to track and estimate the speed of vehicles using YOLO, ByteTrack, and Roboflow Inference. This comprehensive tutorial covers object detection, multi-object tracking, filtering detections, perspective transformation, speed estimation, visualization improvements, and more.</p>
## 💜 built with supervision
## 💜 Built with Supervision
Did you build something cool using supervision? [Let us know!](https://github.com/roboflow/supervision/discussions/categories/built-with-supervision)
@ -251,11 +260,11 @@ https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-
https://github.com/roboflow/supervision/assets/26109316/3ac6982f-4943-4108-9b7f-51787ef1a69f
## 📚 documentation
## 📚 Documentation
Visit our [documentation](https://roboflow.github.io/supervision) page to learn how supervision can help you build computer vision applications faster and more reliably.
## 🏆 contribution
## 🏆 Contribution
We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md) to get started. Thank you 🙏 to all our contributors!
@ -267,8 +276,6 @@ We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md)
<br>
<div align="center">
<div align="center">
<a href="https://youtube.com/roboflow">
<img
@ -303,6 +310,7 @@ We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md)
src="https://media.roboflow.com/notebooks/template/icons/purple/forum.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949633584"
width="3%"
/>
</a>
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
<a href="https://blog.roboflow.com">
<img
@ -310,6 +318,4 @@ We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md)
width="3%"
/>
</a>
</a>
</div>
</div>

View File

@ -5,8 +5,7 @@ description: API reference for supervision's assets module — download sample v
# Assets
Supervision offers an assets download utility that allows you to download image and video files
that you can use in your demos.
Supervision offers an assets download utility that allows you to download image and video files that you can use in your demos.
<div class="md-typeset">
<h2><a href="#supervision.assets.downloader.download_assets.download_assets">download_assets</a></h2>

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,13 @@
---
comments: true
description: API reference for supervision's DetectionDataset and ClassificationDataset — load, merge, split, and convert datasets in YOLO, COCO, and VOC formats.
description: API reference for supervision's DetectionDataset and ClassificationDataset — load, merge, split, and convert datasets in YOLO, COCO, VOC, CreateML, and LabelMe formats.
---
# Datasets
!!! warning
Dataset API is still fluid and may change. If you use Dataset API in your project until further notice, freeze the
`supervision` version in your `requirements.txt` or `setup.py`.
Dataset API is still fluid and may change. If you use Dataset API in your project until further notice, freeze the `supervision` version in your `requirements.txt` or `setup.py`.
<div class="md-typeset">
<h2>DetectionDataset</h2>

View File

@ -5,20 +5,32 @@ status: deprecated
# Deprecated
These features are phased out due to better alternatives or potential issues in future versions. Deprecated functionalities are supported for **five subsequent releases**, providing time for users to transition to updated methods.
These features are phased out due to better alternatives or potential issues in future versions. Deprecated functionalities are typically supported for multiple subsequent releases, providing time for users to transition to updated methods.
- `overlap_ratio_wh` in [`InferenceSlicer.__init__`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/) is deprecated and will be removed in `supervision-0.27.0`. Please set it to `None` and use `overlap_wh` instead.
- `sv.LMM` enum is deprecated and will be removed in `supervision-0.31.0`. Use `sv.VLM` instead.
- [`sv.Detections.from_lmm`](https://supervision.roboflow.com/0.26.0/detection/core/#supervision.detection.core.Detections.from_lmm) property is deprecated and will be removed in `supervision-0.31.0`. Use [`sv.Detections.from_vlm`](https://supervision.roboflow.com/0.26.0/detection/core/#supervision.detection.core.Detections.from_vlm) instead.
- [`sv.ByteTrack`](https://supervision.roboflow.com/latest/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) is deprecated in `supervision-0.28.0` in favour of `ByteTrackTracker` from the external [`trackers`](https://pypi.org/project/trackers/) package (`pip install trackers`). The update method is renamed from `update_with_detections()` to `update()`. Removal is planned for `supervision-0.31.0`.
- `supervision.keypoint` module is deprecated in `supervision-0.27.0`; use `supervision.key_points` instead. It will be removed in `supervision-0.31.0`.
- `create_tiles` in `supervision.utils.image` is deprecated in `supervision-0.27.0`. It will be removed in `supervision-0.31.0`.
- `ensure_cv2_image_for_processing` in `supervision.utils.conversion` is deprecated in `supervision-0.27.0`. It will be removed in `supervision-0.31.0`.
- Keypoint validation utilities in `supervision.validators` are deprecated in `supervision-0.27.0`. They will be removed in `supervision-0.31.0`.
- `normalized_xyxy` argument in [`sv.denormalize_boxes`](https://supervision.roboflow.com/latest/detection/utils/boxes/#supervision.detection.utils.boxes.denormalize_boxes) is deprecated in `supervision-0.27.0` and renamed to `xyxy`. Passing `normalized_xyxy=` emits a `FutureWarning`; support will be removed in `supervision-0.31.0`.
- `supervision.dataset.utils` import path for [`sv.rle_to_mask`](https://supervision.roboflow.com/latest/detection/utils/converters/#supervision.detection.utils.converters.rle_to_mask) and [`sv.mask_to_rle`](https://supervision.roboflow.com/latest/detection/utils/converters/#supervision.detection.utils.converters.mask_to_rle) is deprecated in `supervision-0.28.0`. These functions moved to `supervision.detection.utils.converters` and will be removed from `supervision.dataset.utils` in `supervision-0.31.0`.
- `sv.LMM` enum is deprecated in `supervision-0.27.0` and will be removed in `supervision-0.31.0`. Use `sv.VLM` instead.
- [`sv.Detections.from_lmm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_lmm) classmethod is deprecated in `supervision-0.26.0` and will be removed in `supervision-0.31.0`. Use [`sv.Detections.from_vlm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_vlm) instead.
- `KeyPoints.confidence` is deprecated in `supervision-0.29.0`. Use `KeyPoints.keypoint_confidence` instead. It will be removed in `supervision-0.32.0`.
- Public `validate_*` helper functions are deprecated in `supervision-0.29.0` and will be removed in `supervision-0.32.0`. Supervision internals now use private `_validate_*` helpers.
# Removed
### 0.27.0
- `overlap_ratio_wh` parameter in [`sv.InferenceSlicer`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/) has been removed. Use the pixel-based `overlap_wh` parameter instead.
- `overlap_filter_strategy` parameter in [`sv.InferenceSlicer`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/) has been removed. Use `overlap_strategy` instead.
### 0.26.0
- The `sv.DetectionDataset.images` property has been removed in `supervision-0.26.0`. Please loop over images with `for path, image, annotation in dataset:`, as that does not require loading all images into memory. Also, constructing `sv.DetectionDataset` with parameter `images` as `Dict[str, np.ndarray]` is deprecated and has been removed in `supervision-0.26.0`. Please pass a list of paths `List[str]` instead.
- The name `sv.BoundingBoxAnnotator` is deprecated and has been removed in `supervision-0.26.0`. It has been renamed to [`sv.BoxAnnotator`](https://supervision.roboflow.com/0.22.0/detection/annotators/#supervision.annotators.core.BoxAnnotator).
### 0.24.0
- The `frame_resolution_wh ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) has been removed.

View File

@ -7,510 +7,531 @@ description: API reference for supervision's annotator classes — draw bounding
Annotators accept detections and apply box or mask visualizations to the detections. Annotators have many available styles.
=== "Box"
=== "Outlines"
```python
import supervision as sv
=== "Box"
image = ...
detections = sv.Detections(...)
```python
import supervision as sv
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
image = ...
detections = sv.Detections(...)
<div class="result" markdown>
![bounding-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/bounding-box-annotator-example-purple.png){ align=center width="800" }
</div>
=== "RoundBox"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
round_box_annotator = sv.RoundBoxAnnotator()
annotated_frame = round_box_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![round-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/round-box-annotator-example-purple.png){ align=center width="800" }
</div>
=== "BoxCorner"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
corner_annotator = sv.BoxCornerAnnotator()
annotated_frame = corner_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![box-corner-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-corner-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Color"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
color_annotator = sv.ColorAnnotator()
annotated_frame = color_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![box-mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Circle"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
circle_annotator = sv.CircleAnnotator()
annotated_frame = circle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![circle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/circle-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Dot"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
dot_annotator = sv.DotAnnotator()
annotated_frame = dot_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![dot-annotator-example](https://media.roboflow.com/supervision-annotator-examples/dot-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Triangle"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
triangle_annotator = sv.TriangleAnnotator()
annotated_frame = triangle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![triangle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/triangle-annotator-example.png){ align=center width="800" }
</div>
=== "Ellipse"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
ellipse_annotator = sv.EllipseAnnotator()
annotated_frame = ellipse_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![ellipse-annotator-example](https://media.roboflow.com/supervision-annotator-examples/ellipse-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Halo"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
halo_annotator = sv.HaloAnnotator()
annotated_frame = halo_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![halo-annotator-example](https://media.roboflow.com/supervision-annotator-examples/halo-annotator-example-purple.png){ align=center width="800" }
</div>
=== "PercentageBar"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
percentage_bar_annotator = sv.PercentageBarAnnotator()
annotated_frame = percentage_bar_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![percentage-bar-annotator-example](https://media.roboflow.com/supervision-annotator-examples/percentage-bar-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Mask"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
mask_annotator = sv.MaskAnnotator()
annotated_frame = mask_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Polygon"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
polygon_annotator = sv.PolygonAnnotator()
annotated_frame = polygon_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![polygon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/polygon-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Label"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence in zip(
detections["class_name"],
detections.confidence,
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections,
)
]
```
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
annotated_frame = label_annotator.annotate(
scene=image.copy(), detections=detections, labels=labels
)
```
<div class="result" markdown>
<div class="result" markdown>
![bounding-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/bounding-box-annotator-example-purple.png){ align=center width="800" }
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
</div>
</div>
=== "RoundBox"
=== "RichLabel"
```python
import supervision as sv
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
image = ...
detections = sv.Detections(...)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence in zip(
detections["class_name"],
detections.confidence,
round_box_annotator = sv.RoundBoxAnnotator()
annotated_frame = round_box_annotator.annotate(
scene=image.copy(),
detections=detections,
)
]
```
rich_label_annotator = sv.RichLabelAnnotator(
font_path="TTF_FONT_PATH",
text_position=sv.Position.CENTER,
)
annotated_frame = rich_label_annotator.annotate(
scene=image.copy(),
detections=detections,
labels=labels,
)
```
<div class="result" markdown>
<div class="result" markdown>
![round-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/round-box-annotator-example-purple.png){ align=center width="800" }
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
</div>
</div>
=== "BoxCorner"
=== "Icon"
```python
import supervision as sv
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
image = ...
detections = sv.Detections(...)
corner_annotator = sv.BoxCornerAnnotator()
annotated_frame = corner_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
icon_paths = ["<ICON_PATH>" for _ in detections]
<div class="result" markdown>
icon_annotator = sv.IconAnnotator()
annotated_frame = icon_annotator.annotate(
scene=image.copy(),
detections=detections,
icon_path=icon_paths,
)
```
![box-corner-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-corner-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![icon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/icon-annotator-example.png){ align=center width="800" }
=== "Circle"
</div>
```python
import supervision as sv
<!-- === "Crop"
image = ...
detections = sv.Detections(...)
```python
import supervision as sv
circle_annotator = sv.CircleAnnotator()
annotated_frame = circle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
image = ...
detections = sv.Detections(...)
<div class="result" markdown>
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
![circle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/circle-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![crop-annotator-example](https://media.roboflow.com/supervision-annotator-examples/crop-annotator-example.png){ align=center width="800" }
=== "Ellipse"
</div>
```python
import supervision as sv
-->
image = ...
detections = sv.Detections(...)
=== "Blur"
ellipse_annotator = sv.EllipseAnnotator()
annotated_frame = ellipse_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
```python
import supervision as sv
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![ellipse-annotator-example](https://media.roboflow.com/supervision-annotator-examples/ellipse-annotator-example-purple.png){ align=center width="800" }
blur_annotator = sv.BlurAnnotator()
annotated_frame = (blur_annotator.annotate(scene=image.copy(), detections=detections),)
```
</div>
<div class="result" markdown>
=== "Polygon"
![blur-annotator-example](https://media.roboflow.com/supervision-annotator-examples/blur-annotator-example-purple.png){ align=center width="800" }
```python
import supervision as sv
</div>
image = ...
detections = sv.Detections(...)
=== "Pixelate"
polygon_annotator = sv.PolygonAnnotator()
annotated_frame = polygon_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
```python
import supervision as sv
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![polygon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/polygon-annotator-example-purple.png){ align=center width="800" }
pixelate_annotator = sv.PixelateAnnotator()
annotated_frame = pixelate_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
</div>
<div class="result" markdown>
=== "Shading"
![pixelate-annotator-example](https://media.roboflow.com/supervision-annotator-examples/pixelate-annotator-example-10.png){ align=center width="800" }
=== "Color"
</div>
```python
import supervision as sv
=== "Trace"
image = ...
detections = sv.Detections(...)
```python
import supervision as sv
from ultralytics import YOLO
color_annotator = sv.ColorAnnotator()
annotated_frame = color_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
model = YOLO("yolov8x.pt")
<div class="result" markdown>
trace_annotator = sv.TraceAnnotator()
![box-mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-mask-annotator-example-purple.png){ align=center width="800" }
video_info = sv.VideoInfo.from_video_path(video_path="...")
frames_generator = sv.get_video_frames_generator(source_path="...")
tracker = sv.ByteTrack()
</div>
with sv.VideoSink(target_path="...", video_info=video_info) as sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
detections = tracker.update_with_detections(detections)
annotated_frame = trace_annotator.annotate(
scene=frame.copy(),
detections=detections,
=== "Halo"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
halo_annotator = sv.HaloAnnotator()
annotated_frame = halo_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![halo-annotator-example](https://media.roboflow.com/supervision-annotator-examples/halo-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Mask"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
mask_annotator = sv.MaskAnnotator()
annotated_frame = mask_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
!!! note
`MaskAnnotator` expects `detections.mask` to contain instance segmentation masks aligned to the image passed to `annotate`. For dense masks, provide a boolean array of shape `(N, H, W)` where `(H, W)` matches the image height and width (it also accepts `sv.CompactMask`). If your model returns framework-specific results, convert them to `sv.Detections` first, for example with `sv.Detections.from_ultralytics(...)` or `sv.Detections.from_inference(...)`.
<div class="result" markdown>
![mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Markers"
=== "Dot"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
dot_annotator = sv.DotAnnotator()
annotated_frame = dot_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![dot-annotator-example](https://media.roboflow.com/supervision-annotator-examples/dot-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Triangle"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
triangle_annotator = sv.TriangleAnnotator()
annotated_frame = triangle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![triangle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/triangle-annotator-example.png){ align=center width="800" }
</div>
=== "Labels"
=== "Label"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence in zip(
detections["class_name"],
detections.confidence,
)
sink.write_frame(frame=annotated_frame)
```
]
<div class="result" markdown>
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
annotated_frame = label_annotator.annotate(
scene=image.copy(), detections=detections, labels=labels
)
```
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/trace-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
=== "HeatMap"
</div>
```python
import supervision as sv
from ultralytics import YOLO
=== "RichLabel"
model = YOLO("yolov8x.pt")
```python
import supervision as sv
heat_map_annotator = sv.HeatMapAnnotator()
image = ...
detections = sv.Detections(...)
video_info = sv.VideoInfo.from_video_path(video_path="...")
frames_generator = sv.get_video_frames_generator(source_path="...")
with sv.VideoSink(target_path="...", video_info=video_info) as sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
annotated_frame = heat_map_annotator.annotate(
scene=frame.copy(),
detections=detections,
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence in zip(
detections["class_name"],
detections.confidence,
)
sink.write_frame(frame=annotated_frame)
```
]
<div class="result" markdown>
rich_label_annotator = sv.RichLabelAnnotator(
font_path="TTF_FONT_PATH",
text_position=sv.Position.CENTER,
)
annotated_frame = rich_label_annotator.annotate(
scene=image.copy(),
detections=detections,
labels=labels,
)
```
![heat-map-annotator-example](https://media.roboflow.com/supervision-annotator-examples/heat-map-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
=== "Background Color"
</div>
```python
import supervision as sv
=== "Transformative"
image = ...
detections = sv.Detections(...)
=== "Blur"
background_overlay_annotator = sv.BackgroundOverlayAnnotator()
annotated_frame = background_overlay_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
```python
import supervision as sv
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![background-overlay-annotator-example](https://media.roboflow.com/supervision-annotator-examples/background-color-annotator-example-purple.png){ align=center width="800" }
blur_annotator = sv.BlurAnnotator()
annotated_frame = blur_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
</div>
<div class="result" markdown>
=== "Comparison"
![blur-annotator-example](https://media.roboflow.com/supervision-annotator-examples/blur-annotator-example-purple.png){ align=center width="800" }
```python
import supervision as sv
</div>
image = ...
detections_1 = sv.Detections(...)
detections_2 = sv.Detections(...)
=== "Pixelate"
comparison_annotator = sv.ComparisonAnnotator()
annotated_frame = comparison_annotator.annotate(
scene=image.copy(),
detections_1=detections_1,
detections_2=detections_2,
)
```
```python
import supervision as sv
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![comparison-annotator-example](https://media.roboflow.com/supervision-annotator-examples/comparison-annotator-example.png){ align=center width="800" }
pixelate_annotator = sv.PixelateAnnotator()
annotated_frame = pixelate_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
</div>
<div class="result" markdown>
![pixelate-annotator-example](https://media.roboflow.com/supervision-annotator-examples/pixelate-annotator-example-10.png){ align=center width="800" }
</div>
<!-- === "Crop"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![crop-annotator-example](https://media.roboflow.com/supervision-annotator-examples/crop-annotator-example.png){ align=center width="800" }
</div>
-->
=== "Tracking & Aggregation"
=== "Trace"
```python
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x.pt")
trace_annotator = sv.TraceAnnotator()
video_info = sv.VideoInfo.from_video_path(video_path="...")
frames_generator = sv.get_video_frames_generator(source_path="...")
tracker = sv.ByteTrack()
with sv.VideoSink(target_path="...", video_info=video_info) as sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
detections = tracker.update_with_detections(detections)
annotated_frame = trace_annotator.annotate(
scene=frame.copy(),
detections=detections,
)
sink.write_frame(frame=annotated_frame)
```
<div class="result" markdown>
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/trace-annotator-example-purple.png){ align=center width="800" }
</div>
=== "HeatMap"
```python
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x.pt")
heat_map_annotator = sv.HeatMapAnnotator()
video_info = sv.VideoInfo.from_video_path(video_path="...")
frames_generator = sv.get_video_frames_generator(source_path="...")
with sv.VideoSink(target_path="...", video_info=video_info) as sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
annotated_frame = heat_map_annotator.annotate(
scene=frame.copy(),
detections=detections,
)
sink.write_frame(frame=annotated_frame)
```
<div class="result" markdown>
![heat-map-annotator-example](https://media.roboflow.com/supervision-annotator-examples/heat-map-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Others"
=== "PercentageBar"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
percentage_bar_annotator = sv.PercentageBarAnnotator()
annotated_frame = percentage_bar_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![percentage-bar-annotator-example](https://media.roboflow.com/supervision-annotator-examples/percentage-bar-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Icon"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
icon_paths = ["<ICON_PATH>" for _ in detections]
icon_annotator = sv.IconAnnotator()
annotated_frame = icon_annotator.annotate(
scene=image.copy(),
detections=detections,
icon_path=icon_paths,
)
```
<div class="result" markdown>
![icon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/icon-annotator-example.png){ align=center width="800" }
</div>
=== "Background Color"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
background_overlay_annotator = sv.BackgroundOverlayAnnotator()
annotated_frame = background_overlay_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![background-overlay-annotator-example](https://media.roboflow.com/supervision-annotator-examples/background-color-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Comparison"
```python
import supervision as sv
image = ...
detections_1 = sv.Detections(...)
detections_2 = sv.Detections(...)
comparison_annotator = sv.ComparisonAnnotator()
annotated_frame = comparison_annotator.annotate(
scene=image.copy(),
detections_1=detections_1,
detections_2=detections_2,
)
```
<div class="result" markdown>
![comparison-annotator-example](https://media.roboflow.com/supervision-annotator-examples/comparison-annotator-example.png){ align=center width="800" }
</div>
<div class="md-typeset">
<h2>Try Supervision Annotators on your own image</h2>

View File

@ -4,8 +4,13 @@ comments: true
# Legacy Metrics
Starting with `0.23.0`, a new metrics module is being introduced to supervision.
Metrics here are part of the legacy evaluation API and will be deprecated in the future.
Starting with `0.23.0`, a new metrics module is being introduced to supervision. Metrics here are part of the legacy evaluation API and will be deprecated in the future.
Install the metrics extra before using this page's APIs:
```bash
pip install "supervision[metrics]"
```
<div class="md-typeset">
<h2><a href="#supervision.metrics.detection.ConfusionMatrix">ConfusionMatrix</a></h2>

View File

@ -4,4 +4,51 @@ comments: true
# InferenceSlicer
## GeoTIFF Datasets
Install the optional GeoTIFF dependencies before running this example:
```bash
pip install "supervision[geotiff]"
wget -O RGB.byte.tif https://raw.githubusercontent.com/rasterio/rasterio/main/tests/data/RGB.byte.tif
```
`InferenceSlicer` can read an open `rasterio` dataset window-by-window. This keeps large GeoTIFFs out of memory while passing each tile to the callback as an `(H, W, C)` NumPy array.
```python
import numpy as np
import rasterio
import supervision as sv
def callback(tile: np.ndarray) -> sv.Detections:
h, w = tile.shape[:2]
return sv.Detections(
xyxy=np.array([[w * 0.25, h * 0.25, w * 0.75, h * 0.75]], dtype=float),
confidence=np.array([0.9]),
class_id=np.array([0]),
)
slicer = sv.InferenceSlicer(
callback=callback,
slice_wh=(256, 256),
overlap_wh=(64, 64),
overlap_filter=sv.OverlapFilter.NONE,
)
with rasterio.open("RGB.byte.tif") as dataset:
detections = slicer(dataset)
print(len(detections))
```
GeoTIFF inputs must use a projected coordinate reference system. Reproject geographic rasters before passing them to `InferenceSlicer`.
<div class="md-typeset">
<h2><a href="#supervision.detection.tools.inference_slicer.WindowedRasterDataset">WindowedRasterDataset</a></h2>
</div>
:::supervision.detection.tools.inference_slicer.WindowedRasterDataset
:::supervision.detection.tools.inference_slicer.InferenceSlicer

View File

@ -33,3 +33,9 @@ comments: true
</div>
:::supervision.detection.utils.boxes.denormalize_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.boxes.xyxyxyxy_to_xyxy">xyxyxyxy_to_xyxy</a></h2>
</div>
:::supervision.detection.utils.boxes.xyxyxyxy_to_xyxy

View File

@ -76,3 +76,9 @@ status: new
</div>
:::supervision.detection.utils.converters.mask_to_rle
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.converters.is_compressed_rle">is_compressed_rle</a></h2>
</div>
:::supervision.detection.utils.converters.is_compressed_rle

View File

@ -52,12 +52,24 @@ comments: true
:::supervision.detection.utils.iou_and_nms.box_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.iou_and_nms.box_soft_non_max_suppression">box_soft_non_max_suppression</a></h2>
</div>
:::supervision.detection.utils.iou_and_nms.box_soft_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.iou_and_nms.mask_non_max_suppression">mask_non_max_suppression</a></h2>
</div>
:::supervision.detection.utils.iou_and_nms.mask_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.iou_and_nms.mask_soft_non_max_suppression">mask_soft_non_max_suppression</a></h2>
</div>
:::supervision.detection.utils.iou_and_nms.mask_soft_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.iou_and_nms.box_non_max_merge">box_non_max_merge</a></h2>
</div>
@ -69,3 +81,15 @@ comments: true
</div>
:::supervision.detection.utils.iou_and_nms.mask_non_max_merge
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.iou_and_nms.oriented_box_non_max_suppression">oriented_box_non_max_suppression</a></h2>
</div>
:::supervision.detection.utils.iou_and_nms.oriented_box_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.iou_and_nms.oriented_box_non_max_merge">oriented_box_non_max_merge</a></h2>
</div>
:::supervision.detection.utils.iou_and_nms.oriented_box_non_max_merge

View File

@ -5,6 +5,12 @@ status: new
# Masks Utils
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.masks.mask_to_roi">mask_to_roi</a></h2>
</div>
:::supervision.detection.utils.masks.mask_to_roi
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.masks.move_masks">move_masks</a></h2>
</div>
@ -28,3 +34,9 @@ status: new
</div>
:::supervision.detection.utils.masks.filter_segments_by_distance
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.masks.calculate_masks_centroids">calculate_masks_centroids</a></h2>
</div>
:::supervision.detection.utils.masks.calculate_masks_centroids

View File

@ -3,7 +3,25 @@ comments: true
status: new
---
# VLMs Utils
# VLM Utils
<div class="md-typeset">
<h2><a href="#supervision.detection.vlm.VLM">VLM</a></h2>
</div>
:::supervision.detection.vlm.VLM
<div class="md-typeset">
<h2><a href="#supervision.detection.vlm.LMM">LMM</a></h2>
</div>
:::supervision.detection.vlm.LMM
<div class="md-typeset">
<h2><a href="#supervision.detection.vlm.validate_vlm_parameters">validate_vlm_parameters</a></h2>
</div>
:::supervision.detection.vlm.validate_vlm_parameters
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.vlms.edit_distance">edit_distance</a></h2>

View File

@ -25,6 +25,8 @@ pip install "supervision[metrics]"
Sample asset utilities are part of the base package under `supervision.assets`.
Supervision does not install OpenCV. Its image, drawing, and file-video APIs use the included fallback when `cv2` is unavailable, and automatically use a compatible `cv2` already present in your environment. See the [OpenCV migration guide](how_to/opencv_migration.md) when upgrading an existing environment or choosing an OpenCV wheel yourself.
## Which object detection models work with Supervision?
Supervision is model agnostic. `sv.Detections` includes converters for Ultralytics YOLO, Roboflow Inference, Hugging Face Transformers outputs, SAM, Detectron2, MMDetection, YOLO-NAS, PaddleDet, NCNN, Azure AI Vision, and VLM parsers including Florence-2, PaliGemma, Qwen VL, Gemini, DeepSeek VL 2, and Moondream. Keypoint outputs have separate `sv.KeyPoints` converters, including MediaPipe.
@ -35,11 +37,11 @@ You can annotate images and video, filter detections, track objects, count objec
## How do I track objects across video frames?
Assign persistent tracker IDs before visualization. The built-in `sv.ByteTrack` wrapper accepts `Detections` through `update_with_detections()`. After tracking, combine the output with annotators such as `sv.TraceAnnotator`, `sv.BoxAnnotator`, and `sv.LabelAnnotator`.
Assign persistent tracker IDs before visualization. The built-in `sv.ByteTrack` wrapper accepts `Detections` through `update_with_detections()`, but it is deprecated in favor of `ByteTrackTracker` from the external `trackers` package. After tracking, combine the output with annotators such as `sv.TraceAnnotator`, `sv.BoxAnnotator`, and `sv.LabelAnnotator`.
## What dataset formats does Supervision support?
For detection datasets, Supervision supports YOLO, COCO JSON, and Pascal VOC. Use `DetectionDataset.from_yolo()`, `DetectionDataset.from_coco()`, or `DetectionDataset.from_pascal_voc()` to load datasets, and the matching `as_*` methods to export them.
For detection datasets, Supervision supports YOLO, COCO JSON, Pascal VOC, CreateML, and LabelMe. Use `DetectionDataset.from_yolo()`, `DetectionDataset.from_coco()`, `DetectionDataset.from_pascal_voc()`, `DetectionDataset.from_createml()`, or `DetectionDataset.from_labelme()` to load datasets, and the matching `as_*` methods to export them.
## How do I count objects in a zone?
@ -47,12 +49,33 @@ Use `sv.PolygonZone` for arbitrary polygon regions and `sv.LineZone` for line-cr
## How do I benchmark a model?
Use `supervision.metrics.mean_average_precision.MeanAveragePrecision` for mAP and `sv.ConfusionMatrix` for confusion matrices. Accumulate predictions and ground-truth `Detections`, then call `compute()` to calculate metrics.
Install `supervision[metrics]`, then use `supervision.metrics.mean_average_precision.MeanAveragePrecision` for mAP and `sv.ConfusionMatrix` for confusion matrices. Accumulate predictions and ground-truth `Detections`, then call `compute()` to calculate metrics.
## Is Supervision free to use?
Yes. Supervision is free and open source under the MIT license.
## How do I process frames from a webcam with supervision?
Supervision does not support live camera capture. Manage the capture device yourself with `cv2.VideoCapture`, which works regardless of which OpenCV wheel (`opencv-python` or `opencv-python-headless`) is installed, and pass individual frames to supervision annotators:
```python
import cv2 # requires: pip install opencv-python (or opencv-python-headless)
import supervision as sv
cap = cv2.VideoCapture(0)
annotator = sv.BoxAnnotator()
while True:
ret, frame = cap.read()
if not ret:
break
# run your detector, then annotate:
# annotated = annotator.annotate(frame, detections)
cap.release()
```
## Where is the source code?
The source code is available at [github.com/roboflow/supervision](https://github.com/roboflow/supervision).

View File

@ -42,14 +42,9 @@ We'll use the following libraries:
- `supervision` to evaluate the model results
```bash
pip install roboflow supervision
pip install git+https://github.com/roboflow/inference.git@linas/allow-latest-rc-supervision
pip install roboflow inference "supervision[metrics]"
```
!!! info
We're updating `inference` at the moment. Please install it as shown above.
Here's how you can download a dataset:
```python
@ -130,8 +125,7 @@ Evaluating your model requires careful selection of the dataset. Which images sh
- **Validation Set**: This is the set of images used to validate the model during training. Every Nth training epoch, the model is evaluated on the validation set. Often the training is stopped once the validation loss stops improving. Therefore, even while the images aren't used to train the model, it still indirectly influences the training outcome.
- **Test Set**: This is the set of images kept aside for model testing. It is exactly the set you should use for benchmarking. If the dataset was split correctly, none of these images would be shown to the model during training.
Therefore, an unrelated dataset or the `test` set is the best choice for benchmarking.
Several other problems may arise:
Therefore, an unrelated dataset or the `test` set is the best choice for benchmarking. Several other problems may arise:
- **Extra Classes**: An unrelated dataset may contain additional classes which you may need to [filter out](https://supervision.roboflow.com/how_to/filter_detections/#by-set-of-classes) before computing metrics.
- **Class Mismatch**: In an unrelated dataset, the class names or IDs may be different to what your model produces, you'll need to remap them, which is [shown in this guide](#running-a-model).
@ -145,8 +139,7 @@ At this stage, you should have:
- A dataset of labeled images to evaluate the model.
- A model prepared for benchmarking.
With these ready, we can now run the model and obtain predictions.
We'll use `supervision` to create a dataset iterator, and then run the model on each image.
With these ready, we can now run the model and obtain predictions. We'll use `supervision` to create a dataset iterator, and then run the model on each image.
=== "Inference"
@ -198,8 +191,7 @@ We'll use `supervision` to create a dataset iterator, and then run the model on
## Remapping classes
Did you notice an issue in the above logic?
Since we're using an unrelated dataset, the class names and IDs may be different from what the model was trained on.
Did you notice an issue in the above logic? Since we're using an unrelated dataset, the class names and IDs may be different from what the model was trained on.
We need to remap them to match the dataset classes. Here's how to do it:
@ -259,8 +251,7 @@ Let's also remove the predictions that are not in the dataset classes.
Dataset class names and IDs can be found in the `data.yaml` file, or by printing `dataset.classes`.
Each model will have a different class mapping, so make sure to check the model's documentation. In this case, the model was trained on the COCO dataset, with a class
configuration found [here](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/datasets/coco8.yaml).
Each model will have a different class mapping, so make sure to check the model's documentation. In this case, the model was trained on the COCO dataset, with a class configuration found [here](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/datasets/coco8.yaml).
```python
import supervision as sv
@ -293,8 +284,7 @@ Let's also remove the predictions that are not in the dataset classes.
## Visualizing Predictions
The first step in evaluating your models performance is to visualize its predictions.
This gives an intuitive sense of how well your model is detecting objects and where it might be failing.
The first step in evaluating your models performance is to visualize its predictions. This gives an intuitive sense of how well your model is detecting objects and where it might be failing.
```python
import supervision as sv
@ -334,6 +324,20 @@ Here, predictions in purple are targets (ground truth), and predictions in teal
See [annotator documentation](https://supervision.roboflow.com/latest/detection/annotators/) for even more options.
## Visual Benchmarking
To inspect where a model succeeds and fails, pass `save_directory_path` to `sv.ConfusionMatrix.benchmark(...)`. For every dataset image it writes a 2x2 result grid — `Ground Truth`, `True Positives`, `False Positives`, and `False Negatives` panels — directly into that directory, reusing the original image filenames. This makes it easy to skim through per-image outcomes alongside the aggregate confusion matrix.
```python
import supervision as sv
confusion_matrix = sv.ConfusionMatrix.benchmark(
dataset=test_set,
callback=callback,
save_directory_path="./results",
)
```
## Benchmarking Metrics
With multiple models, fine details matter. Visual inspection may not be enough. `supervision` provides a collection of metrics that help obtain precise numerical results of model performance.
@ -467,7 +471,7 @@ Yes, if you want to evaluate their bounding boxes. Convert model outputs to `Det
### What is a ConfusionMatrix and how do I use it?
`sv.ConfusionMatrix` visualizes true positives, false positives, and false negatives per class. Create one with `sv.ConfusionMatrix.from_detections(predictions=predictions, targets=targets, classes=classes, conf_threshold=0.5, iou_threshold=0.5)`, then call `metric.plot()` to render a heatmap.
`sv.ConfusionMatrix` visualizes true positives, false positives, and false negatives per class. Create one with `sv.ConfusionMatrix.from_detections(predictions=predictions, targets=targets, classes=classes, conf_threshold=0.5, iou_threshold=0.5)`, then call `confusion_matrix.plot()` to render a heatmap. If you want per-image validation visualizations saved to disk, pass `save_directory_path="./results"` to `sv.ConfusionMatrix.benchmark(...)`; it will write 2x2 result grids directly into that directory using the original image filenames, with `Ground Truth`, `True Positives`, `False Positives`, and `False Negatives` panels.
## Author

View File

@ -24,7 +24,7 @@ download_assets(VideoAssets.VEHICLES_2)
First, we need to initialize a model. Let's use a YOLOv8 model with the default COCO checkpoint. We also need to load a video on which to run inference.
Create a YOLO model instance and load the source video using supervision's `VideoInfo` helper. The model will process each frame during inference, while `VideoInfo` extracts resolution and frame-rate metadata needed by the polygon zone annotator. A shared color palette ensures consistent zone coloring throughout the output video.
Create a YOLO model instance and download the source video. The model will process each frame during inference. A shared color palette ensures consistent zone coloring throughout the output video.
```python
import numpy as np
@ -32,13 +32,13 @@ import supervision as sv
import cv2
from ultralytics import YOLO
from supervision.assets import VideoAssets, download_assets
model = YOLO("yolov8s.pt")
VIDEO = str(VideoAssets.VEHICLES_2)
VIDEO = download_assets(VideoAssets.VEHICLES_2)
colors = sv.ColorPalette.default()
video_info = sv.VideoInfo.from_video_path(VIDEO)
colors = sv.ColorPalette.DEFAULT
```
## Calculate Coordinates
@ -80,10 +80,7 @@ With the coordinates of the zones to draw ready, we can set up our zones:
Instantiate a `PolygonZone` for each polygon array, pairing it with a `PolygonZoneAnnotator` for visual overlay and a `BoxAnnotator` for drawing detection boxes. Each zone will later trigger on incoming detections to determine which objects fall inside its boundaries, enabling per-zone counting in the inference callback.
```python
zones = [
sv.PolygonZone(polygon=polygon, frame_resolution_wh=video_info.resolution_wh)
for polygon in polygons
]
zones = [sv.PolygonZone(polygon=polygon) for polygon in polygons]
zone_annotators = [
sv.PolygonZoneAnnotator(
zone=zone,
@ -98,8 +95,6 @@ box_annotators = [
sv.BoxAnnotator(
color=colors.by_idx(index),
thickness=4,
text_thickness=4,
text_scale=2,
)
for index in range(len(polygons))
]
@ -121,9 +116,7 @@ def process_frame(frame: np.ndarray, i) -> np.ndarray:
):
mask = zone.trigger(detections=detections)
detections_filtered = detections[mask]
frame = box_annotator.annotate(
scene=frame, detections=detections_filtered, skip_label=True
)
frame = box_annotator.annotate(scene=frame, detections=detections_filtered)
frame = zone_annotator.annotate(scene=frame)
return frame

View File

@ -13,20 +13,25 @@ date_modified: 2026-04-22
# Detect and Annotate
Supervision provides a seamless process for annotating predictions generated by various
object detection and segmentation models. This guide shows how to perform inference
with the [Inference](https://github.com/roboflow/inference),
[Ultralytics](https://github.com/ultralytics/ultralytics) or
[Transformers](https://github.com/huggingface/transformers) packages. Following this,
you'll learn how to import these predictions into Supervision and use them to annotate
source image.
!!! tip "Sample Image"
Don't have an image? Download the one used in this tutorial:
```bash
wget https://media.roboflow.com/notebooks/examples/dog.jpeg
```
```
Then replace `<SOURCE_IMAGE_PATH>` with `"dog.jpeg"`.
```
Supervision provides a seamless process for annotating predictions generated by various object detection and segmentation models. This guide shows how to perform inference with the [Inference](https://github.com/roboflow/inference), [Ultralytics](https://github.com/ultralytics/ultralytics) or [Transformers](https://github.com/huggingface/transformers) packages. Following this, you'll learn how to import these predictions into Supervision and use them to annotate source image.
![basic-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_1.png)
## Run Detection
First, you'll need to obtain predictions from your object detection or segmentation
model.
First, you'll need to obtain predictions from your object detection or segmentation model.
To run inference, initialize your chosen model and pass the source image to its predict or infer method. Supervision supports Roboflow Inference, Ultralytics YOLO, and Hugging Face Transformers -- select the tab matching your framework. The result is a framework-specific object you will convert to a `Detections` instance in the next step.
@ -37,7 +42,7 @@ To run inference, initialize your chosen model and pass the source image to its
from inference import get_model
model = get_model(model_id="yolov8n-640")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model.infer(image)[0]
```
@ -48,7 +53,7 @@ To run inference, initialize your chosen model and pass the source image to its
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model(image)[0]
```
@ -62,7 +67,7 @@ To run inference, initialize your chosen model and pass the source image to its
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open("<SOURCE_IMAGE_PATH>")
image = Image.open("dog.jpeg")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
@ -91,7 +96,7 @@ Each supported framework has a dedicated class method on `sv.Detections` that co
from inference import get_model
model = get_model(model_id="yolov8n-640")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
```
@ -106,7 +111,7 @@ Each supported framework has a dedicated class method on `sv.Detections` that co
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```
@ -124,7 +129,7 @@ Each supported framework has a dedicated class method on `sv.Detections` that co
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open("<SOURCE_IMAGE_PATH>")
image = Image.open("dog.jpeg")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
@ -161,7 +166,7 @@ To draw bounding boxes and class labels on your image, create a `BoxAnnotator` a
from inference import get_model
model = get_model(model_id="yolov8n-640")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
@ -182,7 +187,7 @@ To draw bounding boxes and class labels on your image, create a `BoxAnnotator` a
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
@ -206,7 +211,7 @@ To draw bounding boxes and class labels on your image, create a `BoxAnnotator` a
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open("<SOURCE_IMAGE_PATH>")
image = Image.open("dog.jpeg")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
@ -233,9 +238,7 @@ To draw bounding boxes and class labels on your image, create a `BoxAnnotator` a
## Display Custom Labels
By default, [`sv.LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator)
will label each detection with its `class_name` (if possible) or `class_id`. You can
override this behavior by passing a list of custom `labels` to the `annotate` method.
By default, [`sv.LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator) will label each detection with its `class_name` (if possible) or `class_id`. You can override this behavior by passing a list of custom `labels` to the `annotate` method.
=== "Inference"
@ -245,7 +248,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
from inference import get_model
model = get_model(model_id="yolov8n-640")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
@ -272,7 +275,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
@ -302,7 +305,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open("<SOURCE_IMAGE_PATH>")
image = Image.open("dog.jpeg")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
@ -335,11 +338,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
## Annotate Image with Segmentations
If you are running the segmentation model
[`sv.MaskAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.MaskAnnotator)
is a drop-in replacement for
[`sv.BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator)
that will allow you to draw masks instead of boxes.
If you are running the segmentation model [`sv.MaskAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.MaskAnnotator) is a drop-in replacement for [`sv.BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator) that will allow you to draw masks instead of boxes.
=== "Inference"
@ -349,7 +348,7 @@ that will allow you to draw masks instead of boxes.
from inference import get_model
model = get_model(model_id="yolov8n-seg-640")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
@ -364,6 +363,7 @@ that will allow you to draw masks instead of boxes.
scene=annotated_image,
detections=detections,
)
sv.plot_image(annotated_image)
```
=== "Ultralytics"
@ -374,7 +374,7 @@ that will allow you to draw masks instead of boxes.
from ultralytics import YOLO
model = YOLO("yolov8n-seg.pt")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
image = cv2.imread("dog.jpeg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
@ -402,7 +402,7 @@ that will allow you to draw masks instead of boxes.
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50-panoptic")
model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50-panoptic")
image = Image.open("<SOURCE_IMAGE_PATH>")
image = Image.open("dog.jpeg")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():

View File

@ -10,11 +10,7 @@ date_modified: 2026-04-22
# Detect Small Objects
This guide shows how to detect small objects
with the [Inference](https://github.com/roboflow/inference),
[Ultralytics](https://github.com/ultralytics/ultralytics) or
[Transformers](https://github.com/huggingface/transformers) packages using
[`InferenceSlicer`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer).
This guide shows how to detect small objects with the [Inference](https://github.com/roboflow/inference), [Ultralytics](https://github.com/ultralytics/ultralytics) or [Transformers](https://github.com/huggingface/transformers) packages using [`InferenceSlicer`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer).
<video controls>
<source src="https://media.roboflow.com/supervision_detect_small_objects_example.mp4" type="video/mp4">
@ -22,8 +18,7 @@ with the [Inference](https://github.com/roboflow/inference),
## Baseline Detection
Small object detection in high-resolution images presents challenges due to the objects'
size relative to the image resolution.
Small object detection in high-resolution images presents challenges due to the objects' size relative to the image resolution.
Running a standard detection model on the full image establishes a baseline for comparison. Load your chosen model, pass the image through it, and convert the results into a `Detections` object. This baseline reveals how many small objects the model misses at native resolution, motivating the sliced inference approach shown later.
@ -116,9 +111,7 @@ Running a standard detection model on the full image establishes a baseline for
## Input Resolution
Modifying the input resolution of images before detection can enhance small object
identification at the cost of processing speed and increased memory usage. This method
is less effective for ultra-high-resolution images (4K and above).
Modifying the input resolution of images before detection can enhance small object identification at the cost of processing speed and increased memory usage. This method is less effective for ultra-high-resolution images (4K and above).
=== "Inference"
@ -166,9 +159,7 @@ is less effective for ultra-high-resolution images (4K and above).
## Inference Slicer
[`InferenceSlicer`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer)
processes high-resolution images by dividing them into smaller segments, detecting
objects within each, and aggregating the results.
[`InferenceSlicer`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer) processes high-resolution images by dividing them into smaller segments, detecting objects within each, and aggregating the results.
<video controls>
<source src="https://media.roboflow.com/supervision_detect_small_objects_example_2.mp4" type="video/mp4">

View File

@ -10,11 +10,7 @@ date_modified: 2026-04-22
# Filter Detections
The advanced filtering capabilities of the `Detections` class offer users a versatile and efficient way to narrow down
and refine object detections. This section outlines various filtering methods, including filtering by specific class
or a set of classes, confidence, object area, bounding box area, relative area, box dimensions, and designated zones.
Each method is demonstrated with concise code examples to provide users with a clear understanding of how to implement
the filters in their applications.
The advanced filtering capabilities of the `Detections` class offer users a versatile and efficient way to narrow down and refine object detections. This section outlines various filtering methods, including filtering by specific class or a set of classes, confidence, object area, bounding box area, relative area, box dimensions, and designated zones. Each method is demonstrated with concise code examples to provide users with a clear understanding of how to implement the filters in their applications.
### by specific class
@ -124,8 +120,7 @@ Allows you to select detections with specific confidence value, for example high
### by area
Allows you to select detections based on their size. We define the area as the number of pixels occupied by the
detection in the image. In the example below, we have sifted out the detections that are too small.
Allows you to select detections based on their size. We define the area as the number of pixels occupied by the detection in the image. In the example below, we have sifted out the detections that are too small.
=== "After"
@ -159,10 +154,7 @@ detection in the image. In the example below, we have sifted out the detections
### by relative area
Allows you to select detections based on their size in relation to the size of whole image. Sometimes the concept of
detection size changes depending on the image. Detection occupying 10000 square px can be large on a 1280x720 image
but small on a 3840x2160 image. In such cases, we can filter out detections based on the percentage of the image area
occupied by them. In the example below, we remove too large detections.
Allows you to select detections based on their size in relation to the size of whole image. Sometimes the concept of detection size changes depending on the image. Detection occupying 10000 square px can be large on a 1280x720 image but small on a 3840x2160 image. In such cases, we can filter out detections based on the percentage of the image area occupied by them. In the example below, we remove too large detections.
=== "After"
@ -204,9 +196,7 @@ occupied by them. In the example below, we remove too large detections.
### by box dimensions
Allows you to select detections based on their dimensions. The size of the bounding box, as well as its coordinates,
can be criteria for rejecting detection. Implementing such filtering requires a bit of custom code but is relatively
simple and fast.
Allows you to select detections based on their dimensions. The size of the bounding box, as well as its coordinates, can be criteria for rejecting detection. Implementing such filtering requires a bit of custom code but is relatively simple and fast.
=== "After"
@ -244,8 +234,7 @@ simple and fast.
### by `PolygonZone`
Allows you to use `Detections` in combination with `PolygonZone` to weed out bounding boxes that are in and out of the
zone. In the example below you can see how to filter out all detections located in the lower part of the image.
Allows you to use `Detections` in combination with `PolygonZone` to weed out bounding boxes that are in and out of the zone. In the example below you can see how to filter out all detections located in the lower part of the image.
=== "After"
@ -331,7 +320,7 @@ Use NumPy-style boolean indexing: `detections[detections.class_id == 0]` for cla
### How do I filter by bounding box area?
`detections[detections.area > 1000]` filters by pixel area. If masks are present, `detections.area` uses mask area; otherwise it uses bounding box area from `xyxy`. Use `detections.box_area` when you specifically need bounding box area.
`detections[detections.area > 1000]` filters by pixel area. If masks are present, `detections.area` uses mask area; otherwise, if oriented-box coordinates are present, it uses oriented polygon area; all remaining detections use bounding box area from `xyxy`. Use `detections.box_area` when you specifically need axis-aligned bounding box area.
### Can I filter by box aspect ratio or dimensions?

View File

@ -0,0 +1,63 @@
---
comments: true
description: Migrate Supervision installations after OpenCV becomes an ambient optional backend: use the included fallback by default or select one compatible OpenCV wheel for your application.
date_modified: 2026-07-17
---
# Migrate to Supervision Without an OpenCV Dependency
Supervision no longer installs OpenCV or offers an OpenCV extra. A standard installation includes the NumPy, Pillow, SciPy, and PyAV fallback needed by Supervision's image, drawing, and file-video APIs. When a compatible `cv2` is already installed, Supervision selects it once when the process imports the package.
## Keep the default fallback
Install Supervision normally when your application does not otherwise require OpenCV:
```bash
pip install supervision
```
The fallback keeps Supervision's documented APIs operational. Some text and anti-aliased drawing pixels can differ from OpenCV, so use the same backend while validating image-level baselines.
## Prefer OpenCV behavior
Install exactly one OpenCV wheel family when your application relies on OpenCV outside Supervision or needs its native behavior:
```bash
# Servers and containers without OpenCV GUI modules
pip install opencv-python-headless supervision
# Desktop applications that need OpenCV GUI modules
pip install opencv-python supervision
```
Do not install both `opencv-python` and `opencv-python-headless`. If another dependency, such as a model runtime, already provides a compatible `cv2`, keep that installation instead of adding a second wheel family.
## Verify the selected backend
Backend selection happens at import time and lasts for the process lifetime. Run this command in a fresh Python process after changing dependencies:
```bash
python -c "from supervision import _cv2; print(_cv2.BACKEND_NAME)"
```
It prints `fallback` without OpenCV and the OpenCV backend name when `cv2` is available. `_cv2` is private; use this command only as an installation diagnostic, not as application API.
## Capture webcams yourself
Supervision's video helpers support file paths through either backend. Live camera capture remains application-owned, so install your chosen OpenCV wheel if you use `cv2.VideoCapture(0)`:
```python
import cv2
import supervision as sv
capture = cv2.VideoCapture(0)
annotator = sv.BoxAnnotator()
```
## Roll back an upgrade
If a downstream image baseline requires the pre-migration package behavior, pin Supervision below the first release that removes the OpenCV dependency, then plan a backend-specific migration separately:
```bash
pip install "supervision<0.30.0"
```

View File

@ -1,34 +1,24 @@
---
comments: true
description: Load, split, merge, and convert computer vision datasets between YOLO, COCO, and Pascal VOC formats using supervision's DetectionDataset.
description: Load, split, merge, and convert computer vision datasets between YOLO, COCO, Pascal VOC, CreateML, and LabelMe formats using supervision's DetectionDataset.
authors:
- name: Piotr Skalski
role: Computer Vision Engineer, Roboflow
github: https://github.com/SkalskiP
date_modified: 2026-04-22
date_modified: 2026-06-25
---
With Supervision, you can load and manipulate classification, object detection, and
segmentation datasets. This tutorial will walk you through how to load, split, merge,
visualize, and augment datasets in Supervision.
With Supervision, you can load and manipulate classification, object detection, and segmentation datasets. This tutorial will walk you through how to load, split, merge, visualize, and augment datasets in Supervision.
## Download Dataset
In this tutorial, we will use a dataset from
[Roboflow Universe](https://universe.roboflow.com/), a public repository of
thousands of computer vision datasets. If you already have your dataset in
[COCO](https://roboflow.com/formats/coco-json),
[YOLO](https://roboflow.com/formats/yolov8-pytorch-txt),
or [Pascal VOC](https://roboflow.com/formats/pascal-voc-xml) format, you can skip this
section.
In this tutorial, we will use a dataset from [Roboflow Universe](https://universe.roboflow.com/), a public repository of thousands of computer vision datasets. If you already have your dataset in [COCO](https://roboflow.com/formats/coco-json), [YOLO](https://roboflow.com/formats/yolov8-pytorch-txt), [Pascal VOC](https://roboflow.com/formats/pascal-voc-xml), [CreateML](https://roboflow.com/formats/createml-json), or [LabelMe](https://roboflow.com/formats/labelme-json) format, you can skip this section.
```bash
pip install roboflow
```
Next, log into your Roboflow account and download the dataset of your choice in the
COCO, YOLO, or Pascal VOC format. You can customize the following code snippet with
your workspace ID, project ID, and version number.
Next, log into your Roboflow account and download the dataset of your choice. The following snippets show common COCO, YOLO, Pascal VOC, and CreateML exports; LabelMe datasets can also be loaded directly from per-image JSON files in the next section. You can customize the code with your workspace ID, project ID, and version number.
=== "COCO"
@ -66,12 +56,21 @@ your workspace ID, project ID, and version number.
dataset = project.version("<PROJECT_VERSION>").download("voc")
```
=== "CreateML"
```python
import roboflow
roboflow.login()
rf = roboflow.Roboflow()
project = rf.workspace("<WORKSPACE_ID>").project("<PROJECT_ID>")
dataset = project.version("<PROJECT_VERSION>").download("createml")
```
## Load Dataset
The Supervision library provides convenient functions to load datasets in various
formats. If your dataset is already split into train, test, and valid subsets, you can
load each of those as separate [`sv.DetectionDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset)
instances.
The Supervision library provides convenient functions to load datasets in various formats. If your dataset is already split into train, test, and valid subsets, you can load each of those as separate [`sv.DetectionDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset) instances.
=== "COCO"
@ -157,11 +156,63 @@ instances.
# 800, 100, 100
```
=== "CreateML"
We can do so using the [`sv.DetectionDataset.from_createml`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.from_createml) to load annotations in [CreateML](https://roboflow.com/formats/createml-json) format.
```python
import supervision as sv
ds_train = sv.DetectionDataset.from_createml(
images_directory_path=f"{dataset.location}/train",
annotations_path=f"{dataset.location}/train/_annotations.createml.json",
)
ds_valid = sv.DetectionDataset.from_createml(
images_directory_path=f"{dataset.location}/valid",
annotations_path=f"{dataset.location}/valid/_annotations.createml.json",
)
ds_test = sv.DetectionDataset.from_createml(
images_directory_path=f"{dataset.location}/test",
annotations_path=f"{dataset.location}/test/_annotations.createml.json",
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
```
=== "LabelMe"
We can do so using the [`sv.DetectionDataset.from_labelme`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.from_labelme) to load annotations in [LabelMe](https://roboflow.com/formats/labelme-json) format. LabelMe `rectangle` shapes are loaded as bounding boxes and `polygon` shapes are loaded as masks with bounding boxes.
```python
import supervision as sv
ds_train = sv.DetectionDataset.from_labelme(
images_directory_path="<TRAIN_IMAGES_DIRECTORY_PATH>",
annotations_directory_path="<TRAIN_ANNOTATIONS_DIRECTORY_PATH>",
)
ds_valid = sv.DetectionDataset.from_labelme(
images_directory_path="<VALID_IMAGES_DIRECTORY_PATH>",
annotations_directory_path="<VALID_ANNOTATIONS_DIRECTORY_PATH>",
)
ds_test = sv.DetectionDataset.from_labelme(
images_directory_path="<TEST_IMAGES_DIRECTORY_PATH>",
annotations_directory_path="<TEST_ANNOTATIONS_DIRECTORY_PATH>",
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
```
## Split Dataset
If your dataset is not already split into train, test, and valid subsets, you can
easily do so using the [`sv.DetectionDataset.split`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.split)
method. We can split it as follows, ensuring a random shuffle of the data.
If your dataset is not already split into train, test, and valid subsets, you can easily do so using the [`sv.DetectionDataset.split`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.split) method. We can split it as follows, ensuring a random shuffle of the data.
```python
import supervision as sv
@ -180,9 +231,7 @@ len(ds_train), len(ds_valid), len(ds_test)
## Merge Dataset
If you have multiple datasets that you would like to merge, you can do so using the
[`sv.DetectionDataset.merge`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.merge)
method.
If you have multiple datasets that you would like to merge, you can do so using the [`sv.DetectionDataset.merge`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.merge) method.
=== "COCO"
@ -286,12 +335,75 @@ method.
# 1000
```
=== "CreateML"
```{ .py hl_lines="22-28" }
import supervision as sv
ds_train = sv.DetectionDataset.from_createml(
images_directory_path=f'{dataset.location}/train',
annotations_path=f'{dataset.location}/train/_annotations.createml.json',
)
ds_valid = sv.DetectionDataset.from_createml(
images_directory_path=f'{dataset.location}/valid',
annotations_path=f'{dataset.location}/valid/_annotations.createml.json',
)
ds_test = sv.DetectionDataset.from_createml(
images_directory_path=f'{dataset.location}/test',
annotations_path=f'{dataset.location}/test/_annotations.createml.json',
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
ds = sv.DetectionDataset.merge([ds_train, ds_valid, ds_test])
ds.classes
# ['person', 'bicycle', 'car', ...]
len(ds)
# 1000
```
=== "LabelMe"
```{ .py hl_lines="22-28" }
import supervision as sv
ds_train = sv.DetectionDataset.from_labelme(
images_directory_path="<TRAIN_IMAGES_DIRECTORY_PATH>",
annotations_directory_path="<TRAIN_ANNOTATIONS_DIRECTORY_PATH>",
)
ds_valid = sv.DetectionDataset.from_labelme(
images_directory_path="<VALID_IMAGES_DIRECTORY_PATH>",
annotations_directory_path="<VALID_ANNOTATIONS_DIRECTORY_PATH>",
)
ds_test = sv.DetectionDataset.from_labelme(
images_directory_path="<TEST_IMAGES_DIRECTORY_PATH>",
annotations_directory_path="<TEST_ANNOTATIONS_DIRECTORY_PATH>",
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
ds = sv.DetectionDataset.merge([ds_train, ds_valid, ds_test])
ds.classes
# ['person', 'bicycle', 'car', ...]
len(ds)
# 1000
```
## Iterate over Dataset
There are two ways to loop over a `sv.DetectionDataset`: using a direct
[for loop](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.__iter__)
called on the `sv.DetectionDataset` instance or loading `sv.DetectionDataset` entries
[by index](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.__getitem__).
There are two ways to loop over a `sv.DetectionDataset`: using a direct [for loop](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.__iter__) called on the `sv.DetectionDataset` instance or loading `sv.DetectionDataset` entries [by index](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.__getitem__).
```python
import supervision as sv
@ -310,13 +422,7 @@ for idx in range(len(ds)):
## Visualize Dataset
The Supervision library provides tools for easily visualizing your detection dataset.
You can create a grid of annotated images to quickly inspect your data and labels.
First, initialize the [`sv.BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator)
and [`sv.LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator).
Then, iterate through a subset of the dataset (e.g., the first 25 images), drawing
bounding boxes and class labels on each image. Finally, combine the annotated images
into a grid for display.
The Supervision library provides tools for easily visualizing your detection dataset. You can create a grid of annotated images to quickly inspect your data and labels. First, initialize the [`sv.BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator) and [`sv.LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator). Then, iterate through a subset of the dataset (e.g., the first 25 images), drawing bounding boxes and class labels on each image. Finally, combine the annotated images into a grid for display.
```python
import supervision as sv
@ -393,24 +499,45 @@ sv.plot_images_grid(
)
```
=== "CreateML"
We can do so using the [`sv.DetectionDataset.as_createml`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.as_createml) method to save annotations in [CreateML](https://roboflow.com/formats/createml-json) format.
```python
import supervision as sv
ds = sv.DetectionDataset(...)
ds.as_createml(
images_directory_path="<IMAGE_DIRECTORY_PATH>",
annotations_path="<ANNOTATIONS_PATH>",
)
```
=== "LabelMe"
We can do so using the [`sv.DetectionDataset.as_labelme`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.as_labelme) method to save annotations in [LabelMe](https://roboflow.com/formats/labelme-json) format. Detections with masks are exported as `polygon` shapes; box-only detections are exported as `rectangle` shapes.
```python
import supervision as sv
ds = sv.DetectionDataset(...)
ds.as_labelme(
images_directory_path="<IMAGE_DIRECTORY_PATH>",
annotations_directory_path="<ANNOTATIONS_DIRECTORY_PATH>",
)
```
## Augment Dataset
In this section, we'll explore using Supervision in combination with Albumentations to
augment our dataset. Data augmentation is a common technique in computer vision to
increase the size and diversity of training datasets, leading to improved model
performance and generalization.
In this section, we'll explore using Supervision in combination with Albumentations to augment our dataset. Data augmentation is a common technique in computer vision to increase the size and diversity of training datasets, leading to improved model performance and generalization.
```bash
pip install albumentations
```
Albumentations provides a flexible and powerful API for image augmentation. The core of
the library is the [`Compose`](https://albumentations.ai/docs/api-reference/albumentations/core/composition/#Compose)
class, which allows you to chain multiple image transformations together. Each
transformation is defined using a dedicated class, such as
[`HorizontalFlip`](https://albumentations.ai/docs/api-reference/albumentations/augmentations/geometric/flip/#HorizontalFlip),
[`RandomBrightnessContrast`](https://albumentations.ai/docs/api-reference/albumentations/augmentations/pixel/transforms/#RandomBrightnessContrast),
or [`Perspective`](https://albumentations.ai/docs/api-reference/albumentations/augmentations/geometric/transforms/#Perspective).
Albumentations provides a flexible and powerful API for image augmentation. The core of the library is the [`Compose`](https://albumentations.ai/docs/api-reference/albumentations/core/composition/#Compose) class, which allows you to chain multiple image transformations together. Each transformation is defined using a dedicated class, such as [`HorizontalFlip`](https://albumentations.ai/docs/api-reference/albumentations/augmentations/geometric/flip/#HorizontalFlip), [`RandomBrightnessContrast`](https://albumentations.ai/docs/api-reference/albumentations/augmentations/pixel/transforms/#RandomBrightnessContrast), or [`Perspective`](https://albumentations.ai/docs/api-reference/albumentations/augmentations/geometric/transforms/#Perspective).
```python
import albumentations as A
@ -428,8 +555,7 @@ augmentation = A.Compose(
)
```
The key is to set `format='pascal_voc'`, which corresponds to the
`[x_min, y_min, x_max, y_max]` bounding box format used in Supervision.
The key is to set `format='pascal_voc'`, which corresponds to the `[x_min, y_min, x_max, y_max]` bounding box format used in Supervision.
```python
import numpy as np
@ -460,7 +586,7 @@ augmented_annotations = replace(
### What dataset formats does supervision support?
For detection datasets, supervision supports YOLO, COCO JSON, and Pascal VOC. Use `DetectionDataset.from_yolo()`, `from_coco()`, or `from_pascal_voc()` to load, and `as_yolo()`, `as_coco()`, or `as_pascal_voc()` to save. Classification datasets use `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
For detection datasets, supervision supports YOLO, COCO JSON, Pascal VOC, CreateML, and LabelMe. Use `DetectionDataset.from_yolo()`, `from_coco()`, `from_pascal_voc()`, `from_createml()`, or `from_labelme()` to load, and `as_yolo()`, `as_coco()`, `as_pascal_voc()`, `as_createml()`, or `as_labelme()` to save. Classification datasets use `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
### Can I split a dataset into train/val/test sets?

View File

@ -10,19 +10,11 @@ date_modified: 2026-04-22
# Save Detections
Supervision enables an easy way to save detections in .CSV and .JSON files for offline
processing. This guide demonstrates how to perform video inference using the
[Inference](https://github.com/roboflow/inference),
[Ultralytics](https://github.com/ultralytics/ultralytics) or
[Transformers](https://github.com/huggingface/transformers) packages and save their results with
[`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) and
[`sv.JSONSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.json_sink.JSONSink).
Supervision enables an easy way to save detections in .CSV and .JSON files for offline processing. This guide demonstrates how to perform video inference using the [Inference](https://github.com/roboflow/inference), [Ultralytics](https://github.com/ultralytics/ultralytics) or [Transformers](https://github.com/huggingface/transformers) packages and save their results with [`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) and [`sv.JSONSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.json_sink.JSONSink).
## Run Detection
First, you'll need to obtain predictions from your object detection or segmentation
model. You can learn more on this topic in our
[How to Detect and Annotate](https://supervision.roboflow.com/latest/how_to/detect_and_annotate/) guide.
First, you'll need to obtain predictions from your object detection or segmentation model. You can learn more on this topic in our [How to Detect and Annotate](https://supervision.roboflow.com/latest/how_to/detect_and_annotate/) guide.
To generate predictions for saving, initialize your model and iterate over video frames using `sv.get_video_frames_generator`. Each frame is passed to the model, and the raw output is converted into a `sv.Detections` object. This detection loop forms the foundation for both CSV and JSON export workflows shown below.
@ -82,11 +74,7 @@ To generate predictions for saving, initialize your model and iterate over video
## Save Detections as CSV
To save detections to a `.CSV` file, open our
[`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink)
and then pass the
[`sv.Detections`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections)
object resulting from the inference to it. Its fields are parsed and saved on disk.
To save detections to a `.CSV` file, open our [`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) and then pass the [`sv.Detections`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections) object resulting from the inference to it. Its fields are parsed and saved on disk.
=== "Inference"
@ -158,12 +146,7 @@ object resulting from the inference to it. Its fields are parsed and saved on di
## Custom Fields
Besides regular fields in
[`sv.Detections`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections),
[`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink)
also allows you to add custom information to each row, which can be passed via the
`custom_data` dictionary. Let's utilize this feature to save information about the
frame index from which the detections originate.
Besides regular fields in [`sv.Detections`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections), [`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) also allows you to add custom information to each row, which can be passed via the `custom_data` dictionary. Let's utilize this feature to save information about the frame index from which the detections originate.
=== "Inference"
@ -235,11 +218,7 @@ frame index from which the detections originate.
## Save Detections as JSON
If you prefer to save the result in a `.JSON` file instead of a `.CSV` file, all you
need to do is replace
[`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink)
with
[`sv.JSONSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.json_sink.JSONSink).
If you prefer to save the result in a `.JSON` file instead of a `.CSV` file, all you need to do is replace [`sv.CSVSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) with [`sv.JSONSink`](https://supervision.roboflow.com/latest/detection/tools/save_detections/#supervision.detection.tools.json_sink.JSONSink).
=== "Inference"

View File

@ -13,20 +13,11 @@ date_modified: 2026-04-22
# Track Objects
Leverage Supervision's advanced capabilities for enhancing your video analysis by
seamlessly [tracking](https://supervision.roboflow.com/latest/trackers/) objects recognized by
a multitude of object detection, segmentation and keypoint models. This comprehensive guide will
take you through the steps to perform inference using the YOLOv8 model via either the
[Inference](https://github.com/roboflow/inference) or
[Ultralytics](https://github.com/ultralytics/ultralytics) packages. Following this,
you'll discover how to track these objects efficiently and annotate your video content
for a deeper analysis.
Leverage Supervision's advanced capabilities for enhancing your video analysis by seamlessly [tracking](https://supervision.roboflow.com/latest/trackers/) objects recognized by a multitude of object detection, segmentation and keypoint models. This comprehensive guide will take you through the steps to perform inference using the YOLOv8 model via either the [Inference](https://github.com/roboflow/inference) or [Ultralytics](https://github.com/ultralytics/ultralytics) packages. Following this, you'll discover how to track these objects efficiently and annotate your video content for a deeper analysis.
## Object Detection & Segmentation
To make it easier for you to follow our tutorial download the video we will use as an
example. You can do this using the
[`supervision.assets`](https://supervision.roboflow.com/latest/assets/) module included in the base package.
To make it easier for you to follow our tutorial download the video we will use as an example. You can do this using the [`supervision.assets`](https://supervision.roboflow.com/latest/assets/) module included in the base package.
This section demonstrates how to detect and segment objects in video frames using YOLOv8 with either the Inference or Ultralytics package. You will download a sample video, define a per-frame callback function that runs model prediction, and process the entire video to produce an annotated output file.
@ -42,16 +33,9 @@ download_assets(VideoAssets.PEOPLE_WALKING)
### Run Inference
First, you'll need to obtain predictions from your object detection or segmentation
model. In this tutorial, we are using the YOLOv8 model as an example. However,
Supervision is versatile and compatible with various models. Check this
[link](https://supervision.roboflow.com/latest/how_to/detect_and_annotate/#load-predictions-into-supervision)
for guidance on how to plug in other models.
First, you'll need to obtain predictions from your object detection or segmentation model. In this tutorial, we are using the YOLOv8 model as an example. However, Supervision is versatile and compatible with various models. Check this [link](https://supervision.roboflow.com/latest/how_to/detect_and_annotate/#load-predictions-into-supervision) for guidance on how to plug in other models.
We will define a `callback` function, which will process each frame of the video
by obtaining model predictions and then annotating the frame based on these predictions.
This `callback` function will be essential in the subsequent steps of the tutorial, as
it will be modified to include tracking, labeling, and trace annotations.
We will define a `callback` function, which will process each frame of the video by obtaining model predictions and then annotating the frame based on these predictions. This `callback` function will be essential in the subsequent steps of the tutorial, as it will be modified to include tracking, labeling, and trace annotations.
!!! tip
@ -107,11 +91,11 @@ it will be modified to include tracking, labeling, and trace annotations.
### Tracking
After running inference and obtaining predictions, the next step is to track the
detected objects throughout the video. Utilizing Supervisions
[`sv.ByteTrack`](https://supervision.roboflow.com/latest/trackers/#supervision.tracker.byte_tracker.core.ByteTrack)
functionality, each detected object is assigned a unique tracker ID,
enabling the continuous following of the object's motion path across different frames.
After running inference and obtaining predictions, the next step is to track the detected objects throughout the video. Utilizing Supervisions [`sv.ByteTrack`](https://supervision.roboflow.com/latest/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) functionality, each detected object is assigned a unique tracker ID, enabling the continuous following of the object's motion path across different frames.
!!! warning "Deprecated tracker wrapper"
`sv.ByteTrack` is deprecated in favor of `ByteTrackTracker` from the external `trackers` package. The external tracker uses `update()` instead of `update_with_detections()`.
=== "Ultralytics"
@ -163,11 +147,7 @@ enabling the continuous following of the object's motion path across different f
### Annotate Video with Tracking IDs
Annotating the video with tracking IDs helps in distinguishing and following each object
distinctly. With the
[`sv.LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator)
in Supervision, we can overlay the tracker IDs and class labels on the detected objects,
offering a clear visual representation of each object's class and unique identifier.
Annotating the video with tracking IDs helps in distinguishing and following each object distinctly. With the [`sv.LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator) in Supervision, we can overlay the tracker IDs and class labels on the detected objects, offering a clear visual representation of each object's class and unique identifier.
=== "Ultralytics"
@ -245,11 +225,7 @@ offering a clear visual representation of each object's class and unique identif
### Annotate Video with Traces
Adding traces to the video involves overlaying the historical paths of the detected
objects. This feature, powered by the
[`sv.TraceAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.TraceAnnotator),
allows for visualizing the trajectories of objects, helping in understanding the
movement patterns and interactions between objects in the video.
Adding traces to the video involves overlaying the historical paths of the detected objects. This feature, powered by the [`sv.TraceAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.TraceAnnotator), allows for visualizing the trajectories of objects, helping in understanding the movement patterns and interactions between objects in the video.
=== "Ultralytics"
@ -335,8 +311,7 @@ movement patterns and interactions between objects in the video.
Models aren't limited to object detection and segmentation. Keypoint detection allows for detailed analysis of body joints and connections, especially valuable for applications like human pose estimation. This section introduces keypoint tracking. We'll walk through the steps of annotating keypoints, converting them into bounding box detections compatible with `ByteTrack`, and applying detection smoothing for enhanced stability.
To make it easier for you to follow our tutorial, let's download the video we will use as an
example. You can do this using the [`supervision.assets`](https://supervision.roboflow.com/latest/assets/) module included in the base package.
To make it easier for you to follow our tutorial, let's download the video we will use as an example. You can do this using the [`supervision.assets`](https://supervision.roboflow.com/latest/assets/) module included in the base package.
```python
from supervision.assets import download_assets, VideoAssets
@ -350,8 +325,7 @@ download_assets(VideoAssets.SKIING)
### Keypoint Detection
First, you'll need to obtain predictions from your keypoint detection model. In this tutorial, we are using the YOLOv8 model as an example. However,
Supervision is versatile and compatible with various models. Check this [link](https://supervision.roboflow.com/latest/keypoint/core/) for guidance on how to plug in other models.
First, you'll need to obtain predictions from your keypoint detection model. In this tutorial, we are using the YOLOv8 model as an example. However, Supervision is versatile and compatible with various models. Check this [link](https://supervision.roboflow.com/latest/keypoint/core/) for guidance on how to plug in other models.
We will define a `callback` function, which will process each frame of the video by obtaining model predictions and then annotating the frame based on these predictions.

View File

@ -0,0 +1,193 @@
---
comments: true
description: Use CompactMask for memory-efficient instance segmentation in supervision — ingest COCO RLE payloads, skip mask materialisation, and merge mixed dense and compact detections without allocating a full pixel stack.
authors:
- name: Borda
role: Open Source Engineer, Roboflow
github: https://github.com/borda
date_modified: 2026-07-01
---
# Use Compact Masks for Memory-Efficient Segmentation
[CompactMask][supervision.detection.compact_mask.CompactMask] stores each instance mask as a run-length encoding of its bounding-box **crop** rather than a full `(H, W)` boolean frame. For high-resolution images with many sparse masks this can reduce memory from tens of gigabytes to tens of megabytes, and eliminates full-frame decode work in annotators that only need the cropped region.
!!! Note
`sv.mask_to_xyxy` keeps supervision's inclusive max-coordinate convention for compatibility with `CompactMask` and current box-based adapters. Use `sv.mask_to_roi` when you need exclusive slice bounds for NumPy indexing or crop extraction.
This guide covers the four main integration points:
1. [Ingesting COCO RLE payloads directly as CompactMask](#ingest-coco-rle-payloads)
2. [Parsing Roboflow Inference results without a dense stack](#parse-inference-results)
3. [Skipping mask materialisation for box/label annotators](#skip-unnecessary-materialisation)
4. [Merging mixed dense and compact detections](#merge-mixed-detections)
---
## Ingest COCO RLE Payloads
If your model or API returns masks in the COCO RLE format (`{"size": [H, W], "counts": "..."}`) you can convert them directly to `CompactMask` without allocating an `(N, H, W)` boolean array:
```python
import numpy as np
import supervision as sv
from supervision.detection.compact_mask import CompactMask
# Example: two COCO RLE masks for a 720×1280 frame.
# Replace the counts strings with actual compressed RLE payloads from your
# model or API — e.g., from pycocotools mask.encode() or an Inference response.
rles = [
{"size": [720, 1280], "counts": "YOUR_RLE_COUNTS_STRING_HERE"},
{"size": [720, 1280], "counts": "YOUR_RLE_COUNTS_STRING_HERE"},
]
xyxy = np.array(
[
[100.0, 50.0, 400.0, 300.0],
[500.0, 200.0, 900.0, 600.0],
]
)
compact = CompactMask.from_coco_rle(rles, xyxy, image_shape=(720, 1280))
detections = sv.Detections(
xyxy=xyxy,
mask=compact,
class_id=np.array([0, 1]),
)
```
`from_coco_rle` uses run-length arithmetic scoped to each bounding box so no dense pixel array is ever created. Uncompressed integer count lists are also accepted in place of compressed strings.
---
## Parse Inference Results
`Detections.from_inference` accepts a `compact_masks=True` flag that routes the Roboflow RLE payload through `CompactMask.from_coco_rle` instead of decoding to a dense stack:
```python
import supervision as sv
# result: a Roboflow Inference v2 response dict with instance masks.
detections = sv.Detections.from_inference(result, compact_masks=True)
from supervision.detection.compact_mask import CompactMask
assert isinstance(detections.mask, CompactMask)
```
!!! Warning
`compact_masks=True` crops each mask to its detector bounding box. Pixels outside the box are silently dropped. For masks that extend meaningfully beyond the reported bounding box, use the default `compact_masks=False` (dense decode) to preserve all pixels.
To convert an existing dense-mask `Detections` to compact at any point:
```python
detections_compact = detections.to_compact_masks()
```
---
## Skip Unnecessary Materialisation
Annotators that do not draw masks (box, label, circle, ellipse, trace, keypoint) expose `requires_mask = False`. Integrations can branch on this flag to avoid decoding compact or RLE masks before annotation:
```python
import supervision as sv
annotators = [
sv.BoxAnnotator(),
sv.LabelAnnotator(),
sv.MaskAnnotator(), # requires_mask = True
]
for ann in annotators:
if ann.requires_mask:
# Annotator reads mask pixels — CompactMask decodes lazily per crop.
scene = ann.annotate(scene, detections)
else:
# Annotator ignores masks — strip mask field to eliminate any decode cost.
det_no_mask = sv.Detections(
xyxy=detections.xyxy,
confidence=detections.confidence,
class_id=detections.class_id,
)
scene = ann.annotate(scene, det_no_mask)
```
Annotators that set `requires_mask = True`: [MaskAnnotator][supervision.annotators.core.MaskAnnotator], [PolygonAnnotator][supervision.annotators.core.PolygonAnnotator], [HaloAnnotator][supervision.annotators.core.HaloAnnotator].
All others default to `requires_mask = False`.
!!! Note
`PolygonAnnotator` and `MaskAnnotator` both operate directly on `CompactMask` without materialising the full `(N, H, W)` frame — passing compact detections to them is already efficient.
---
## Merge Mixed Detections
When merging `Detections` objects that mix dense `ndarray` masks and `CompactMask` instances, `Detections.merge` converts dense inputs to `CompactMask` automatically. No full `(N, H, W)` stack is allocated:
```python
import numpy as np
import supervision as sv
from supervision.detection.compact_mask import CompactMask
H, W = 720, 1280
# Compact detections from an RLE-based source.
# Replace the counts string with a real compressed RLE payload from your model or API.
rles = [{"size": [H, W], "counts": "YOUR_RLE_COUNTS_STRING_HERE"}]
xyxy_a = np.array([[100.0, 50.0, 400.0, 300.0]])
cm = CompactMask.from_coco_rle(rles, xyxy_a, image_shape=(H, W))
det_a = sv.Detections(xyxy=xyxy_a, mask=cm, class_id=np.array([0]))
# Dense detections from a different source.
masks_b = np.zeros((1, H, W), dtype=bool)
masks_b[0, 200:400, 500:800] = True
xyxy_b = np.array([[500.0, 200.0, 799.0, 399.0]])
det_b = sv.Detections(xyxy=xyxy_b, mask=masks_b, class_id=np.array([1]))
# Output is CompactMask regardless of input order.
merged = sv.Detections.merge([det_a, det_b])
assert isinstance(merged.mask, CompactMask)
assert len(merged) == 2
```
Merge rules:
| Inputs | Output mask type |
| ------------------------------------- | ------------------------------- |
| All `CompactMask` | `CompactMask` |
| Mixed `CompactMask` + dense `ndarray` | `CompactMask` |
| All dense `ndarray` | `ndarray` (backward compatible) |
All `CompactMask` inputs must share the same `image_shape`; mismatches raise `ValueError`.
---
## Performance Notes
These estimates apply to the **parsing and annotation stage**, not end-to-end pipeline FPS. Model inference typically dominates total runtime.
| Optimisation | Realistic gain | Applies when |
| ---------------------------- | -------------------------- | ------------------------------------------------------------- |
| `from_coco_rle` ingestion | 2560% faster parse | Full-frame COCO RLE payload; current dense decode path |
| `MaskAnnotator` ROI blending | 1035% faster annotation | Many small, sparse masks on high-res frames |
| `PolygonAnnotator` crop path | 1545% faster polygon draw | Many compact masks; full-frame materialise was the bottleneck |
| Mixed-mask merge | 520% faster merge | Mix of compact and dense sources (e.g. multi-camera stitch) |
Upper-end gains assume: ≥1080p frames, tens to hundreds of instances, masks covering less than ~20% of total pixels.
---
## API Reference
- [CompactMask][supervision.detection.compact_mask.CompactMask]
- [CompactMask.from_coco_rle][supervision.detection.compact_mask.CompactMask.from_coco_rle]
- [CompactMask.from_dense][supervision.detection.compact_mask.CompactMask.from_dense]
- [Detections.from_inference][supervision.detection.core.Detections.from_inference]
- [Detections.to_compact_masks][supervision.detection.core.Detections.to_compact_masks]
- [Detections.merge][supervision.detection.core.Detections.merge]
- [BaseAnnotator.requires_mask][supervision.annotators.base.BaseAnnotator]

View File

@ -45,17 +45,13 @@ We write your reusable computer vision tools. Whether you need to load your data
## 💻 Install
You can install `supervision` in a
[**Python>=3.9**](https://www.python.org/) environment.
You can install `supervision` in a [**Python>=3.10**](https://www.python.org/) environment.
!!! example "Installation"
=== "pip (recommended)"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision) [![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision) [![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md) [![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
pip install supervision
@ -63,10 +59,7 @@ You can install `supervision` in a
=== "poetry"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision) [![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision) [![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md) [![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
poetry add supervision
@ -74,10 +67,7 @@ You can install `supervision` in a
=== "uv"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision) [![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision) [![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md) [![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
uv pip install supervision
@ -91,10 +81,7 @@ You can install `supervision` in a
=== "rye"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision) [![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision) [![license](https://img.shields.io/pypi/l/supervision)](../LICENSE.md) [![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
rye add supervision

View File

@ -77,6 +77,63 @@ comments: true
</div>
=== "VertexEllipseAreaAnnotator"
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
area_annotator = sv.VertexEllipseAreaAnnotator(
color=sv.Color.GREEN,
sigma=2.0,
)
annotated_frame = area_annotator.annotate(
scene=image.copy(),
key_points=key_points,
)
```
`sv.VertexEllipseAnnotator` is a compatibility alias for `sv.VertexEllipseAreaAnnotator`.
=== "VertexEllipseOutlineAnnotator"
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
outline_annotator = sv.VertexEllipseOutlineAnnotator(
color=sv.Color.GREEN,
sigma=2.0,
thickness=2,
)
annotated_frame = outline_annotator.annotate(
scene=image.copy(),
key_points=key_points,
)
```
=== "VertexEllipseHaloAnnotator"
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
halo_annotator = sv.VertexEllipseHaloAnnotator(
color=sv.Color.GREEN,
sigma=2.0,
)
annotated_frame = halo_annotator.annotate(
scene=image.copy(),
key_points=key_points,
)
```
<div class="md-typeset">
<h2><a href="#supervision.key_points.annotators.VertexAnnotator">VertexAnnotator</a></h2>
</div>
@ -94,3 +151,21 @@ comments: true
</div>
:::supervision.key_points.annotators.VertexLabelAnnotator
<div class="md-typeset">
<h2><a href="#supervision.key_points.annotators.VertexEllipseAreaAnnotator">VertexEllipseAreaAnnotator</a></h2>
</div>
:::supervision.key_points.annotators.VertexEllipseAreaAnnotator
<div class="md-typeset">
<h2><a href="#supervision.key_points.annotators.VertexEllipseOutlineAnnotator">VertexEllipseOutlineAnnotator</a></h2>
</div>
:::supervision.key_points.annotators.VertexEllipseOutlineAnnotator
<div class="md-typeset">
<h2><a href="#supervision.key_points.annotators.VertexEllipseHaloAnnotator">VertexEllipseHaloAnnotator</a></h2>
</div>
:::supervision.key_points.annotators.VertexEllipseHaloAnnotator

View File

@ -76,7 +76,7 @@ The built-in `sv.ByteTrack` wrapper assigns persistent IDs across video frames t
### Datasets
`sv.DetectionDataset` loads, merges, splits, and converts object detection datasets. Supported formats include YOLO, COCO JSON, and Pascal VOC. `sv.ClassificationDataset` supports folder-structured classification datasets.
`sv.DetectionDataset` loads, merges, splits, and converts object detection datasets. Supported formats include YOLO, COCO JSON, Pascal VOC, and LabelMe. `sv.ClassificationDataset` supports folder-structured classification datasets.
### Metrics
@ -162,7 +162,7 @@ No. Supervision is model agnostic. It is designed to normalize model outputs int
### What dataset formats are supported?
For object detection datasets, Supervision supports YOLO, COCO JSON, and Pascal VOC import and export. For classification datasets, it supports folder-structure import and export.
For object detection datasets, Supervision supports YOLO, COCO JSON, Pascal VOC, and LabelMe import and export. For classification datasets, it supports folder-structure import and export.
### How do I detect small objects?

View File

@ -53,7 +53,7 @@ Zone-based counting. `PolygonZone.trigger(detections)` returns a boolean mask fo
### sv.DetectionDataset and sv.ClassificationDataset
For detection datasets, load, merge, split, and convert between YOLO, COCO JSON, and Pascal VOC formats. Classification datasets use folder-structure import and export via `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
For detection datasets, load, merge, split, and convert between YOLO, COCO JSON, Pascal VOC, and LabelMe formats. Classification datasets use folder-structure import and export via `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
### sv.InferenceSlicer
@ -129,11 +129,11 @@ Supervision is an open-source Python library by Roboflow for computer vision wor
### How do I install supervision?
Install with `pip install supervision`. For optional metric dependencies use `pip install supervision[metrics]`. Sample asset utilities are included in the base package under `supervision.assets`. The current package metadata requires Python 3.9+.
Install with `pip install supervision`. For optional metric dependencies use `pip install supervision[metrics]`. Sample asset utilities are included in the base package under `supervision.assets`. The current package metadata requires Python 3.10+.
### What can I do with supervision?
Annotate images and video with bounding boxes, masks, and labels; track objects across frames with persistent IDs; count detections inside polygon zones or line crossings; filter and query detection results; load, split, and convert detection datasets between YOLO, COCO, and Pascal VOC formats; manage classification datasets with folder structures; and benchmark model performance with mAP and confusion matrices.
Annotate images and video with bounding boxes, masks, and labels; track objects across frames with persistent IDs; count detections inside polygon zones or line crossings; filter and query detection results; load, split, and convert detection datasets between YOLO, COCO, Pascal VOC, and LabelMe formats; manage classification datasets with folder structures; and benchmark model performance with mAP and confusion matrices.
### Is supervision free to use?
@ -153,7 +153,7 @@ Use a tracker to assign persistent IDs. The built-in `sv.ByteTrack` wrapper acce
### What dataset formats does supervision support?
For detection datasets, supervision supports YOLO, COCO JSON, and Pascal VOC. Use `DetectionDataset.from_yolo()`, `from_coco()`, or `from_pascal_voc()` to load, and `as_yolo()`, `as_coco()`, or `as_pascal_voc()` to save. For classification datasets, use `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
For detection datasets, supervision supports YOLO, COCO JSON, Pascal VOC, and LabelMe. Use `DetectionDataset.from_yolo()`, `from_coco()`, `from_pascal_voc()`, or `from_labelme()` to load, and `as_yolo()`, `as_coco()`, `as_pascal_voc()`, or `as_labelme()` to save. For classification datasets, use `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
### How do I count objects in a zone?

View File

@ -47,7 +47,7 @@ Object tracker wrapper that assigns persistent IDs across video frames. The buil
Zone-based counting. `PolygonZone.trigger(detections)` returns a boolean mask for detections currently inside an arbitrary polygon. `LineZone.trigger(detections)` returns `(crossed_in, crossed_out)` arrays for line crossings and requires `detections.tracker_id` so objects can be matched across frames. Both are commonly paired with zone annotators for visualization.
### sv.DetectionDataset and sv.ClassificationDataset
For detection datasets, load, merge, split, and convert between YOLO, COCO JSON, and Pascal VOC formats. Classification datasets use folder-structure import and export via `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
For detection datasets, load, merge, split, and convert between YOLO, COCO JSON, Pascal VOC, CreateML, and LabelMe formats. Classification datasets use folder-structure import and export via `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
### sv.InferenceSlicer
SAHI-style inference slicing: split high-resolution images into overlapping tiles, run detection on each tile, merge results with non-maximum suppression or non-maximum merge. Configure tile overlap in pixels with `overlap_wh`.
@ -120,11 +120,11 @@ Supervision is an open-source Python library by Roboflow for computer vision wor
### How do I install supervision?
Install with `pip install supervision`. For optional metric dependencies use `pip install supervision[metrics]`. Sample asset utilities are included in the base package under `supervision.assets`. The current package metadata requires Python 3.9+.
Install with `pip install supervision`. For optional metric dependencies use `pip install supervision[metrics]`. Sample asset utilities are included in the base package under `supervision.assets`. The current package metadata requires Python 3.10+.
### What can I do with supervision?
Annotate images and video with bounding boxes, masks, and labels; track objects across frames with persistent IDs; count detections inside polygon zones or line crossings; filter and query detection results; load, split, and convert detection datasets between YOLO, COCO, and Pascal VOC formats; manage classification datasets with folder structures; and benchmark model performance with mAP and confusion matrices.
Annotate images and video with bounding boxes, masks, and labels; track objects across frames with persistent IDs; count detections inside polygon zones or line crossings; filter and query detection results; load, split, and convert detection datasets between YOLO, COCO, Pascal VOC, and LabelMe formats; manage classification datasets with folder structures; and benchmark model performance with mAP and confusion matrices.
### Is supervision free to use?
@ -144,7 +144,7 @@ Use a tracker to assign persistent IDs. The built-in `sv.ByteTrack` wrapper acce
### What dataset formats does supervision support?
For detection datasets, supervision supports YOLO, COCO JSON, and Pascal VOC. Use `DetectionDataset.from_yolo()`, `from_coco()`, or `from_pascal_voc()` to load, and `as_yolo()`, `as_coco()`, or `as_pascal_voc()` to save. For classification datasets, use `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
For detection datasets, supervision supports YOLO, COCO JSON, Pascal VOC, CreateML, and LabelMe. Use `DetectionDataset.from_yolo()`, `from_coco()`, `from_pascal_voc()`, `from_createml()`, or `from_labelme()` to load, and `as_yolo()`, `as_coco()`, `as_pascal_voc()`, `as_createml()`, or `as_labelme()` to save. For classification datasets, use `ClassificationDataset.from_folder_structure()` and `as_folder_structure()`.
### How do I count objects in a zone?

View File

@ -6,6 +6,12 @@ comments: true
This page contains supplementary values, types and enums that metrics use.
Install the metrics extra before using metrics APIs:
```bash
pip install "supervision[metrics]"
```
<div class="md-typeset">
<h2><a href="#supervision.metrics.core.MetricTarget">MetricTarget</a></h2>
</div>

View File

@ -4,6 +4,12 @@ comments: true
# F1 Score
Install the metrics extra before using this API:
```bash
pip install "supervision[metrics]"
```
<div class="md-typeset">
<h2><a href="#supervision.metrics.f1_score.F1Score">F1Score</a></h2>
</div>

View File

@ -1,10 +1,16 @@
---
comments: true
description: API reference for MeanAveragePrecision — compute mAP for object detection benchmarking with bounding boxes.
description: API reference for MeanAveragePrecision — compute mAP for object detection benchmarking with boxes, masks, and oriented boxes.
---
# Mean Average Precision
Install the metrics extra before using this API:
```bash
pip install "supervision[metrics]"
```
<div class="md-typeset">
<h2><a href="#supervision.metrics.mean_average_precision.MeanAveragePrecision">MeanAveragePrecision</a></h2>
</div>

View File

@ -4,6 +4,12 @@ comments: true
# Mean Average Recall
Install the metrics extra before using this API:
```bash
pip install "supervision[metrics]"
```
<div class="md-typeset">
<h2><a href="#supervision.metrics.mean_average_recall.MeanAverageRecall">MeanAverageRecall</a></h2>
</div>

View File

@ -4,6 +4,12 @@ comments: true
# Precision
Install the metrics extra before using this API:
```bash
pip install "supervision[metrics]"
```
<div class="md-typeset">
<h2><a href="#supervision.metrics.precision.Precision">Precision</a></h2>
</div>

View File

@ -4,6 +4,12 @@ comments: true
# Recall
Install the metrics extra before using this API:
```bash
pip install "supervision[metrics]"
```
<div class="md-typeset">
<h2><a href="#supervision.metrics.recall.Recall">Recall</a></h2>
</div>

176
docs/notebooks/blurring_faces.ipynb vendored Normal file
View File

@ -0,0 +1,176 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "jxcxFKy2hRnA"
},
"source": [
"# Blurring Faces\n",
"\n",
"---\n",
"\n",
"[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/develop/docs/notebooks/blurring_faces.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "FAxD-vAgkadG"
},
"source": [
"Click the `Open in Colab` button to run the cookbook on Google Colab.\n",
"\n",
"## Introduction\n",
"\n",
"In this cookbook we'll use a frame from a video of someone in a supermarket. We'll download this video via the `supervision` assets module. We'll then run inference on this frame using the hosted Roboflow API to fetch detections of faces utilizing an open source face detection model on Roboflow Universe. Finally, we'll use supervision to blur the detected faces."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "o_F-fJGskuz9"
},
"source": [
"## Install packages\n",
"\n",
"Let's quickly install the `supervision` package with the assets module, as well as the roboflow `inference_sdk` with pip. We'll also install `tqdm` to show a progress bar, but this is optional in production code."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c2X8k1opItiO"
},
"outputs": [],
"source": "!pip3 install -q supervision inference tqdm \"Pillow<12\""
},
{
"cell_type": "markdown",
"metadata": {
"id": "9xy1jXMrm5iU"
},
"source": [
"## Download Video and Extract Frame\n",
"\n",
"In order to blur a face in a frame, we'll need a frame with a face in it. Let's download a video, and grab a frame in the middle of the video. I played around a little, and found that the 800th frame is great frame for us to test, since the customer is facing the camera. In this code, we're also using tqdm to display a progress bar of our script."
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"import supervision as sv\n",
"\n",
"video = sv.download_assets(sv.VideoAssets.GROCERY_STORE)\n",
"\n",
"# Seek directly to frame 800 using the start parameter (O(1) seek)\n",
"frame = next(sv.get_video_frames_generator(video, start=800))\n",
"\n",
"sv.plot_image(frame)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "tgejF7Zlosyd"
},
"source": [
"## Detecting Faces\n",
"\n",
"Now that we've got our image we'll need a good face detecting model. For this task, there are already an impressive amount of open source models available on [Roboflow Universe](https://universe.roboflow.com/). After a little digging, this [face detection model](https://universe.roboflow.com/mohamed-traore-2ekkp/face-detection-mik1i) has over 1300 images. Some models, including this one, require a Roboflow API key. You can [create a free account here](https://app.roboflow.com/login). From there, you can find the key under Settings > Workspaces > Roboflow API. Let's give it a try."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "3MDjVxEzKWGn",
"outputId": "8fee3446-cc81-4e3c-c4a0-b531d286f1a4"
},
"outputs": [],
"source": [
"import os\n",
"from inference_sdk import InferenceHTTPClient\n",
"\n",
"try:\n",
" from google.colab import userdata\n",
" ROBOFLOW_API_KEY = userdata.get(\"ROBOFLOW_API_KEY\") or \"\"\n",
"except ImportError:\n",
" ROBOFLOW_API_KEY = os.environ.get(\"ROBOFLOW_API_KEY\", \"\")\n",
"\n",
"assert ROBOFLOW_API_KEY, \"Set ROBOFLOW_API_KEY in Colab secrets or as env var\"\n",
"\n",
"client = InferenceHTTPClient(\n",
" api_url=\"https://detect.roboflow.com\",\n",
" api_key=ROBOFLOW_API_KEY\n",
")\n",
"\n",
"results = client.infer(frame, model_id=\"face-detection-mik1i/18\")\n",
"\n",
"print(f\"Detected {len(results['predictions'])} face(s)\")\n",
"print(results)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "dkPMOHa_univ"
},
"source": [
"## Blurring the Face\n",
"\n",
"Now that we're detecting faces, bluring them is easy with supervision. Let's pass our results into a `Detections` object and annotate the frame with a `BlurAnnotator`."
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"blur = sv.BlurAnnotator(kernel_size=100)\n",
"\n",
"detections = sv.Detections.from_inference(results)\n",
"\n",
"annotated_frame = blur.annotate(scene=frame.copy(), detections=detections)\n",
"\n",
"sv.plot_image(annotated_frame)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "YVNe8oe4vY4N"
},
"source": [
"## Conclusion\n",
"\n",
"With supervision, inference, and Roboflow Universe we were able to blur faces in minutes with an open source model. There are many other impressive use cases out there, so feel free to share in your own cookbooks. Happy building!"
]
}
],
"metadata": {
"accelerator": "GPU",
"colab": {
"cell_execution_strategy": "setup",
"gpuType": "T4",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

715
docs/notebooks/compact-mask-sam3.ipynb vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,10 @@
<p class="card repo-card" data-name="Object Tracking" data-labels="TRACKING, ANNOTATOR" data-version="v0.18.0"
data-author="nickherrig"></p>
</a>
<a href="../notebooks/blurring-faces/">
<p class="card repo-card" data-name="Blurring Faces" data-labels="ANNOTATOR,API,UNIVERSE"
data-version="v0.18.0" data-author="nickherrig"></p>
</a>
<a href="../notebooks/occupancy_analytics/">
<p class="card repo-card" data-name="Analyzing Zone Occupancy" data-labels="ANNOTATOR,DETECTION,ZONES"
data-version="v0.26.0" data-author="stellasphere"></p>
@ -58,6 +62,14 @@
<p class="card repo-card" data-name="Understand Visitors with YOLO-World"
data-labels="ANNOTATORS,DETECTION,INFERENCE" data-version="v0.19.0" data-author="AdonaiVera"></p>
</a>
<a href="../notebooks/compact-mask-sam3/">
<p class="card repo-card" data-name="Memory-Efficient Instance Segmentation"
data-labels="COMPACT MASK,SAM3,SEGMENTATION" data-version="v0.28.0" data-author="Borda"></p>
</a>
<a href="../notebooks/oriented-bounding-boxes/">
<p class="card repo-card" data-name="Oriented Bounding Boxes for Densely Packed Objects"
data-labels="OBB,DETECTIONS,NMS,DATASET" data-version="v0.29.0" data-author="kounelisagis"></p>
</a>
</div>
</div>
</section>

View File

@ -120,7 +120,7 @@
"name": "How do I install supervision?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Install supervision with pip: pip install supervision. For optional metric dependencies use pip install supervision[metrics]. Sample asset utilities are included in the base package under supervision.assets. The current package metadata requires Python 3.9+."
"text": "Install supervision with pip: pip install supervision. For optional metric dependencies use pip install supervision[metrics]. Sample asset utilities are included in the base package under supervision.assets. The current package metadata requires Python 3.10+."
}
},
{

View File

@ -1,8 +1,12 @@
---
comments: true
description: API reference for supervision's object trackers — ByteTrack and SORT implementations that assign persistent IDs across video frames.
description: API reference for supervision's deprecated ByteTrack tracker wrapper.
---
# ByteTrack
!!! warning "Deprecated"
`sv.ByteTrack` is deprecated in `supervision-0.28.0` and will be removed in `supervision-0.31.0`. Install `trackers` and use `ByteTrackTracker` instead.
:::supervision.tracker.byte_tracker.core.ByteTrack

36
docs/utils/conversion.md Normal file
View File

@ -0,0 +1,36 @@
---
comments: true
status: new
---
# Conversion Utils
<div class="md-typeset">
<h2><a href="#supervision.utils.conversion.cv2_to_pillow">cv2_to_pillow</a></h2>
</div>
:::supervision.utils.conversion.cv2_to_pillow
<div class="md-typeset">
<h2><a href="#supervision.utils.conversion.pillow_to_cv2">pillow_to_cv2</a></h2>
</div>
:::supervision.utils.conversion.pillow_to_cv2
<div class="md-typeset">
<h2><a href="#supervision.utils.conversion.ensure_cv2_image_for_annotation">ensure_cv2_image_for_annotation</a></h2>
</div>
:::supervision.utils.conversion.ensure_cv2_image_for_annotation
<div class="md-typeset">
<h2><a href="#supervision.utils.conversion.ensure_pil_image_for_annotation">ensure_pil_image_for_annotation</a></h2>
</div>
:::supervision.utils.conversion.ensure_pil_image_for_annotation
<div class="md-typeset">
<h2><a href="#supervision.utils.conversion.images_to_cv2">images_to_cv2</a></h2>
</div>
:::supervision.utils.conversion.images_to_cv2

View File

@ -13,3 +13,21 @@ comments: true
</div>
:::supervision.geometry.core.Position
<div class="md-typeset">
<h2><a href="#supervision.geometry.core.Point">Point</a></h2>
</div>
:::supervision.geometry.core.Point
<div class="md-typeset">
<h2><a href="#supervision.geometry.core.Rect">Rect</a></h2>
</div>
:::supervision.geometry.core.Rect
<div class="md-typeset">
<h2><a href="#supervision.geometry.core.Vector">Vector</a></h2>
</div>
:::supervision.geometry.core.Vector

View File

@ -11,6 +11,12 @@ status: new
:::supervision.utils.image.crop_image
<div class="md-typeset">
<h2><a href="#supervision.utils.image.load_image_from_url">load_image_from_url</a></h2>
</div>
:::supervision.utils.image.load_image_from_url
<div class="md-typeset">
<h2><a href="#supervision.utils.image.scale_image">scale_image</a></h2>
</div>

View File

@ -0,0 +1,12 @@
---
comments: true
status: new
---
# Image Window
<div class="md-typeset">
<h2><a href="#supervision.utils.image_window.ImageWindow">ImageWindow</a></h2>
</div>
:::supervision.utils.image_window.ImageWindow

View File

@ -2,7 +2,7 @@
This example benchmarks `CompactMask`, a new mask representation introduced in `supervision` that replaces dense `(N, H, W)` boolean arrays with a crop-scoped Run-Length Encoding (RLE). The benchmark demonstrates full API compatibility, massive memory savings, and order-of-magnitude annotation speedups — with no change to your existing `Detections` code.
---
______________________________________________________________________
## The Problem
@ -16,7 +16,7 @@ For a 4K image with 1 000 detected objects:
At this scale, typical pipelines crash with `MemoryError` before a single frame is annotated. Aerial imagery, satellite tiles, and high-density crowd scenes all hit this wall.
---
______________________________________________________________________
## The Solution — Crop-RLE Storage
@ -95,7 +95,7 @@ Crop RLE's `.crop()` method powers the `MaskAnnotator` optimisation — it never
At N=1 000 with 1 % overlap, bbox pre-filter reduces 499 500 candidate pairs to ~5 000 overlapping pairs — a ~2 000x reduction in pixel-level work.
---
______________________________________________________________________
## Why Crop-RLE Was Chosen over Local Crop
@ -107,7 +107,7 @@ Both formats compress extremely well; the deciding factors for Crop-RLE are:
The main trade-off: crop-only decode is O(A) rather than O(1). For the common solid-fill segmentation mask this is negligible (\<0.1 ms per mask).
---
______________________________________________________________________
## Operation-by-Operation Speedup Analysis
@ -115,7 +115,7 @@ This section walks through every `Detections` operation that touches masks and s
At 50% fill on an FHD image each mask's bounding box covers a large portion of the frame, producing many RLE runs per row.
---
______________________________________________________________________
### Memory
@ -146,7 +146,7 @@ Scaled to N=200: 200 x 4.7 KB = ~933 KB of RLE data, plus `_crop_shapes` (1.6 KB
At 5% fill with 8-vertex polygons, the ratio reaches 10 000x20 000x because crops are tiny and RLEs are extremely short. The benchmark's 4K-200-5%-v8 scenario measures 21 786x (theory) / ~6 000x (malloc). The SAT-200-5%-v8 scenario reaches 62 968x theoretical.
---
______________________________________________________________________
### `.area`
@ -179,7 +179,7 @@ At FHD-200-50%-v600, dense `.area` takes 84.66 ms; compact takes 0.48 ms — a *
| No (H, W) allocation per mask | latency |
| **Combined** | **~1 000x** |
---
______________________________________________________________________
### `filter` / `__getitem__` (boolean index)
@ -212,7 +212,7 @@ At FHD-200-50%-v600, dense `filter` takes 14.56 ms; compact takes 0.03 ms — a
| Allocation | new `(K, H, W)` array | new `CompactMask` shell (~trivial) |
| **Speedup** | | **hundreds to tens of thousands x** |
---
______________________________________________________________________
### `annotate` (`MaskAnnotator`)
@ -246,7 +246,7 @@ colored_mask[y1 : y1 + crop_h, x1 : x1 + crop_w][crop_m] = color.as_bgr()
| x N masks | compounds |
| **Combined** | **~26 400x** |
---
______________________________________________________________________
### IoU (`mask_iou_batch` / `compact_mask_iou_batch`)
@ -315,7 +315,7 @@ At FHD-200-50%-v600, dense IoU takes 23 915 ms; compact takes 51.58 ms — a **4
At 20% fill the gaps close — more pairs overlap, larger crops — speedup drops toward the lower end of the range.
---
______________________________________________________________________
### NMS (`mask_non_max_suppression`)
@ -339,7 +339,7 @@ All three IoU optimisations apply to the compact path:
At FHD-200-50%-v600, dense NMS takes 5 231 ms; compact takes 48.15 ms — a **481x speedup**. Dense IoU/NMS is skipped for scenarios above 1 GB (4K-200 and SAT-200 tiers); compact NMS still runs on those.
---
______________________________________________________________________
### `merge` (`Detections.merge`)
@ -387,7 +387,7 @@ if len(self.xyxy) > 0:
This O(1) check avoids the O(N x H x W) dense materialisation that previously dominated compact merge time.
---
______________________________________________________________________
### `offset` / `with_offset` (`InferenceSlicer` tile stitching)
@ -425,7 +425,7 @@ At FHD-200-50%-v600, dense offset takes 42.30 ms; compact takes 0.02 ms — a **
In the `InferenceSlicer` pipeline the canvas is always expanded by the tile offset, so no crop ever overflows — the fast path is always taken. Clipping only activates for objects that genuinely straddle the image boundary.
---
______________________________________________________________________
### `centroids` (`calculate_masks_centroids`)
@ -460,7 +460,7 @@ At FHD-200-50%-v600, dense centroids takes 1 133.68 ms; compact takes 60.39 ms
| No global `np.indices((H, W))` allocation | saves large float64 |
| **Combined (N=200)** | **~19 1 000x** |
---
______________________________________________________________________
### Summary
@ -480,7 +480,7 @@ Measured speedups at the **FHD-200-50%-v600** operating point (dense fill, compl
All speedups are larger at sparser fill fractions and larger resolutions. At SAT-200-20%-v128, `.area` reaches 1 204x and `merge` reaches 89 046x. At the sparsest scenarios (5% fill, 8-vertex polygons), memory ratios exceed 60 000x.
---
______________________________________________________________________
## Drop-In Compatibility
@ -517,7 +517,7 @@ Supported indexing patterns:
| `mask[slice]` | New `CompactMask` |
| `np.asarray(mask)` | Dense `(N, H, W)` bool array |
---
______________________________________________________________________
## Benchmark
@ -527,6 +527,69 @@ Run on any machine — no GPU or real model required:
uv run python examples/compact_mask/benchmark.py
```
For a focused benchmark of the Roboflow inference-result parser API, run:
```bash
uv run python examples/compact_mask/bench_inference_api.py
```
This script downloads all supervision image assets plus the middle frame from every supervision video asset by default, runs one real segmentation inference per source image, requests native RLE masks from Inference, freezes that result, and then compares parser performance:
```python
sv.Detections.from_inference(result)
sv.Detections.from_inference(result, compact_masks=True)
```
Timing repetitions, warmups, confidence, IoU, response mask format, and the default model live as constants in `bench_inference_api.py`.
Inference runs and segmentation-derived box fields are outside the timed benchmark loop. By default the script uses `rfdetr-seg-large` with `response_mask_format="rle"`; set `BENCH_INFERENCE_MODEL_ID` to override the model. Set `ROBOFLOW_API_KEY` when your model requires authentication. Sources where the model returns no native RLE segmentation masks are skipped because there is no RLE parser work to benchmark. `rfdetr-large` is a valid local Inference model id, but it is object detection only; use an `rfdetr-seg-*` model for instance segmentation.
Run one specific supervision image or video asset with `--asset`:
```bash
uv run python examples/compact_mask/bench_inference_api.py --asset people-walking
uv run python examples/compact_mask/bench_inference_api.py --asset soccer
uv run python examples/compact_mask/bench_inference_api.py --asset vehicles
uv run python examples/compact_mask/bench_inference_api.py --asset people-walking-video
```
The output reports image size, segmented objects, median parser time, peak traced allocations, mask storage, and parser speedup (`dense parser time / compact parser time`).
**Speedup column:** The `speedup` value reflects allocation savings — how much time is saved by skipping the dense `(N, H, W)` bool-stack allocation — not a faster RLE decode. Compact RLE arithmetic is typically slower than the dense NumPy path. The net result:
- **Compact is faster** only when the dense `(N, H, W)` bool-stack allocation dominates — large images with many sparse masks where avoiding that allocation outweighs the RLE arithmetic cost.
- **Compact is slower** for small images or dense/overlapping masks, where Python RLE arithmetic dominates and the allocation cost is negligible.
- **The primary guaranteed benefit is memory**: compact masks use roughly 99% less memory than dense stacks for typical segmentation output, regardless of which parse direction is faster.
The default run includes a `synthetic-dense-64` row (64×64 image, 4 fully-filled masks) to demonstrate the adversarial regime where compact is slower than dense. For each real source with segmentation masks, the script also writes a validation overlay to `examples/compact_mask/outputs/*_segmentations.jpg`.
### Sample results — inference API
Measured on macOS Apple M4 Max, 50 reps after 3 warmups, using `rfdetr-seg-large` via Roboflow Inference.
| src | res | seg | dense ms | CM ms | speedup | peak MB (dense/compact) | mask MB (dense/compact) | ok |
| -------------------------- | --------- | --- | -------- | ----- | ------- | ----------------------- | ----------------------- | --- |
| synthetic-dense-64 | 64×64 | 4 | 0.03 | 0.11 | 0.31× | 0.04 / 0.05 | 0.02 / 0.00 | ✓ |
| people-walking.jpg | 1920×1080 | 53 | 85.56 | 12.55 | 6.82× | 219.86 / 0.11 | 109.90 / 0.02 | ✓ |
| soccer.jpg | 398×224 | 21 | 1.36 | 1.07 | 1.27× | 3.77 / 0.05 | 1.87 / 0.00 | ✓ |
| vehicles.mp4#269 | 3840×2160 | 7 | 46.03 | 2.60 | 18× | 116.13 / 0.07 | 58.06 / 0.00 | ✓ |
| milk-bottling-plant.mp4#94 | 1920×1080 | 9 | 15.61 | 11.57 | 1.35× | 37.34 / 0.53 | 18.66 / 0.03 | ✓ |
| vehicles-2.mp4#637 | 1920×1080 | 47 | 76.87 | 13.59 | 5.66× | 194.97 / 0.13 | 97.46 / 0.03 | ✓ |
| grocery-store.mp4#501 | 3840×2160 | 4 | 27.20 | 4.36 | 6.24× | 66.36 / 0.22 | 33.18 / 0.01 | ✓ |
| subway.mp4#649 | 2160×3840 | 42 | 325.71 | 32.21 | 10× | 696.78 / 0.80 | 348.36 / 0.09 | ✓ |
| market-square.mp4#237 | 2160×3840 | 96 | 732.98 | 27.24 | 27× | 1592.61 / 0.22 | 796.26 / 0.05 | ✓ |
| people-walking.mp4#170 | 1920×1080 | 60 | 100.99 | 12.69 | 7.96× | 248.89 / 0.12 | 124.42 / 0.02 | ✓ |
| beach-1.mp4#223 | 3840×2160 | 33 | 223.50 | 13.39 | 17× | 547.47 / 0.12 | 273.72 / 0.02 | ✓ |
| basketball-1.mp4#238 | 1920×1080 | 2 | 3.61 | 2.05 | 1.76× | 8.30 / 0.15 | 4.15 / 0.01 | ✓ |
| skiing.mp4#176 | 1920×1080 | 11 | 16.47 | 3.07 | 5.37× | 45.63 / 0.08 | 22.81 / 0.01 | ✓ |
- **seg** — number of instance segmentations returned by the model
- **dense ms / CM ms** — median parse time for `from_inference()` vs `from_inference(compact_masks=True)`
- **speedup** — dense / compact parse time; values below 1× (e.g., synthetic-dense-64) indicate the adversarial regime where RLE arithmetic cost exceeds allocation savings
- **peak MB** — peak traced allocations during parsing (dense / compact)
- **mask MB** — mask storage only (dense / compact); compact is typically 1005 000× smaller
- **ok**`compact.to_dense()` pixel-exactly matches dense masks
Six image tiers x three fill fractions (5 / 20 / 50 %) x three vertex counts (8 / 128 / 600):
| Tier | Resolution | Objects | Dense array | Notes |
@ -563,7 +626,7 @@ Dense timing is skipped automatically when the dense IoU/NMS array would exceed
All non-skipped scenarios pass: pixel-perfect annotation, exact area, lossless `to_dense()` roundtrip.
---
______________________________________________________________________
## Use-Cases
@ -573,7 +636,7 @@ All non-skipped scenarios pass: pixel-perfect annotation, exact area, lossless `
- **Long-running tracking** — accumulated `Detections` across many frames stay in kilobytes rather than gigabytes.
- **`InferenceSlicer`** — `with_offset()` adjusts crop origins directly when stitching tile results; no dense materialisation needed.
---
______________________________________________________________________
## Limitations
@ -581,11 +644,12 @@ All non-skipped scenarios pass: pixel-perfect annotation, exact area, lossless `
- RLE format is **column-major (F-order), crop-scoped** — pixel-scan order matches COCO / pycocotools, but crop scope differs from full-image scope. Use `.to_dense()` to materialize a full-image dense mask, then encode that mask to COCO RLE before passing it to pycocotools.
- `from_dense()` requires the input `(N, H, W)` array to fit in memory. For truly OOM-scale data, build `CompactMask` per-detection directly from model output crops rather than from a pre-allocated dense stack.
---
______________________________________________________________________
## Files
| File | Description |
| -------------- | ------------------------------------------------ |
| `benchmark.py` | Full benchmark across FHD / 4K / satellite tiers |
| `README.md` | This file |
| File | Description |
| ------------------------ | --------------------------------------------------- |
| `benchmark.py` | Full benchmark across FHD / 4K / satellite tiers |
| `bench_inference_api.py` | Focused dense vs compact `from_inference` benchmark |
| `README.md` | This file |

View File

@ -0,0 +1,505 @@
"""Benchmark dense vs compact Roboflow RLE ingestion.
Run with:
uv run python examples/compact_mask/bench_inference_api.py
The benchmark downloads supervision assets, runs one segmentation inference per
source image, then times dense vs compact parsing of that fixed inference result.
"""
from __future__ import annotations
import argparse
import gc
import os
import statistics
import time
import tracemalloc
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import cv2
import numpy as np
from rich import box
from rich.console import Console
from rich.table import Table
import supervision as sv
from supervision.assets import ImageAssets, VideoAssets, download_assets
from supervision.config import CLASS_NAME_DATA_FIELD
from supervision.detection.compact_mask import CompactMask
console = Console(width=120, force_terminal=True)
# Default segmentation model; use an rfdetr-seg-* id so masks are returned.
MODEL_ID = "rfdetr-seg-large"
# Environment variable that can override MODEL_ID without adding CLI noise.
MODEL_ID_ENV = "BENCH_INFERENCE_MODEL_ID"
# Optional Roboflow API key for models that require authentication.
API_KEY_ENV = "ROBOFLOW_API_KEY"
# Model confidence threshold used only for the one inference call per source.
CONFIDENCE = 0.2
# Model IoU threshold used only for the one inference call per source.
IOU = 0.5
# Request native RLE masks so the benchmark measures RLE parser ingestion.
RESPONSE_MASK_FORMAT = "rle"
# Parser timing repetitions; inference itself is not repeated.
REPETITIONS = 50
# Untimed parser warmup calls before measurements.
WARMUP = 3
# Visual segmentation overlays for manual validation.
ARTIFACT_DIR = Path("examples/compact_mask/outputs")
ASSETS = {Path(asset.filename).stem: asset for asset in ImageAssets}
for video_asset in VideoAssets:
key = Path(video_asset.filename).stem
ASSETS[key if key not in ASSETS else f"{key}-video"] = video_asset
@dataclass
class ApiBenchmarkResult:
"""Result for one dense-vs-compact parser benchmark run."""
source: str
resolution: str
segmented_objects: int
dense_s: float
compact_s: float
dense_peak_bytes: int
compact_peak_bytes: int
dense_mask_bytes: int
compact_mask_bytes: int
pixel_perfect: bool
def load_image_from_asset(path: Path | None, asset: str) -> tuple[np.ndarray, str]:
"""Return ``(image, label)`` for an image or video middle frame."""
if path is not None:
image = cv2.imread(str(path))
if image is None:
raise FileNotFoundError(f"Could not read image: {path}")
return image, str(path)
asset_obj = ASSETS[asset]
asset_path = Path(download_assets(asset_obj))
if isinstance(asset_obj, ImageAssets):
image = cv2.imread(str(asset_path))
if image is None:
raise FileNotFoundError(f"Could not read image: {asset_path}")
return image, str(asset_path)
video = cv2.VideoCapture(str(asset_path))
if not video.isOpened():
raise FileNotFoundError(f"Could not read video: {asset_path}")
frame_count = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
frame_index = max(0, frame_count // 2)
if frame_index:
video.set(cv2.CAP_PROP_POS_FRAMES, frame_index)
ok, frame = video.read()
video.release()
if not ok or frame is None:
raise FileNotFoundError(f"Could not read middle frame: {asset_path}")
return frame, f"{asset_path}#{frame_index}"
def freeze_result(inference_result: Any) -> dict[str, Any]:
"""Convert one Inference result to a reusable dictionary."""
if isinstance(inference_result, dict):
return inference_result
if hasattr(inference_result, "model_dump"):
return inference_result.model_dump(exclude_none=True, by_alias=True)
if hasattr(inference_result, "dict"):
return inference_result.dict(exclude_none=True, by_alias=True)
raise TypeError(
f"Expected dict-like Inference result, got {type(inference_result).__name__}"
)
def count_rle_predictions(result: dict[str, Any]) -> int:
"""Return the number of predictions carrying Roboflow RLE masks."""
return sum(
isinstance(prediction.get("rle") or prediction.get("rle_mask"), dict)
for prediction in result.get("predictions", [])
)
def synthetic_dense_small_result() -> tuple[np.ndarray, str, dict[str, Any]]:
"""Return a small dense-mask adversarial payload where compact parsing is slower.
Uses a 64x64 image with 4 fully-filled masks. At this scale the dense
``(N, H, W)`` allocation cost is negligible; Python RLE arithmetic dominates,
making compact ingestion slower than the dense NumPy path. Included as a
clearly labeled adversarial row in the default benchmark run to show that
the ``speedup`` column reflects allocation savings, not decode speed.
"""
height, width = 64, 64
image = np.zeros((height, width, 3), dtype=np.uint8)
predictions = [
{
"x": width / 2,
"y": height / 2,
"width": width,
"height": height,
"confidence": 0.9,
"class_id": index,
"class": f"dense-{index}",
"rle": {"size": [height, width], "counts": [0, height * width]},
}
for index in range(4)
]
return (
image,
"synthetic-dense-64",
{
"predictions": predictions,
"image": {"width": width, "height": height},
},
)
def derive_boxes_from_rle_masks(result: dict[str, Any]) -> dict[str, Any]:
"""Set prediction boxes from native RLE segmentation masks."""
predictions = []
for prediction in result.get("predictions", []):
rle = prediction.get("rle") or prediction.get("rle_mask")
if not isinstance(rle, dict):
predictions.append(prediction)
continue
height, width = rle["size"]
mask = sv.rle_to_mask(rle["counts"], resolution_wh=(int(width), int(height)))
if not mask.any():
predictions.append(prediction)
continue
x1, y1, x2, y2 = sv.mask_to_xyxy(mask[np.newaxis, ...])[0]
predictions.append(
{
**prediction,
"x": float((x1 + x2) / 2),
"y": float((y1 + y2) / 2),
"width": float(x2 - x1),
"height": float(y2 - y1),
}
)
return {**result, "predictions": predictions}
def artifact_path(source: str) -> Path:
"""Return the segmentation validation artifact path for a source."""
source_path, separator, frame = source.partition("#")
stem = Path(source_path).stem
suffix = f"_frame_{frame}" if separator else ""
return ARTIFACT_DIR / f"{stem}{suffix}_segmentations.jpg"
def detection_labels(detections: sv.Detections) -> list[str]:
"""Return compact class/confidence labels for validation artifacts."""
raw_class_names = detections.get_data(CLASS_NAME_DATA_FIELD)
class_names = (
raw_class_names.astype(str).tolist()
if isinstance(raw_class_names, np.ndarray)
else [""] * len(detections)
)
labels = []
for index in range(len(detections)):
class_name = class_names[index] if index < len(class_names) else ""
confidence = (
""
if detections.confidence is None
else f" {detections.confidence[index]:.2f}"
)
labels.append(f"{class_name}{confidence}".strip() or str(index))
return labels
def save_segmentation_artifact(
image: np.ndarray,
result: dict[str, Any],
source: str,
) -> Path | None:
"""Draw parsed segmentation masks and save a validation artifact."""
detections = sv.Detections.from_inference(result)
if detections.mask is None:
return None
annotated = image.copy()
annotated = sv.MaskAnnotator(
color_lookup=sv.ColorLookup.INDEX,
opacity=0.45,
).annotate(scene=annotated, detections=detections)
annotated = sv.LabelAnnotator(
color_lookup=sv.ColorLookup.INDEX,
text_scale=0.35,
text_padding=4,
).annotate(
scene=annotated,
detections=detections,
labels=detection_labels(detections),
)
path = artifact_path(source)
path.parent.mkdir(parents=True, exist_ok=True)
if not cv2.imwrite(str(path), annotated):
raise OSError(f"Could not write segmentation artifact: {path}")
return path
def load_inference_model(model_id: str, api_key: str | None) -> Any:
"""Load the requested Inference model."""
try:
from inference import get_model
except ImportError as exc:
raise ImportError(
"Install the `inference` package to run this benchmark."
) from exc
model_kwargs = {"api_key": api_key} if api_key is not None else {}
return get_model(model_id=model_id, **model_kwargs)
def run_inference_once(
image: np.ndarray,
model: Any,
model_id: str,
confidence: float,
iou: float,
) -> dict[str, Any] | None:
"""Run one real segmentation inference and return a frozen result."""
# Inference still serializes instance segmentations with x/y/width/height.
# Derive those fields from the RLE masks so the benchmark uses segmentations,
# not the model-reported detector boxes, as the source of truth.
result = derive_boxes_from_rle_masks(
freeze_result(
model.infer(
image,
confidence=confidence,
iou=iou,
response_mask_format=RESPONSE_MASK_FORMAT,
)[0]
)
)
rle_count = count_rle_predictions(result)
if rle_count == 0:
console.print(
f"[yellow]skipped[/yellow] {model_id}: no native RLE segmentation "
f"predictions for response_mask_format={RESPONSE_MASK_FORMAT!r}"
)
return None
return result
def median_seconds(fn: Callable[[], object], reps: int, warmup: int) -> float:
"""Return median runtime for ``fn``."""
for _ in range(warmup):
fn()
gc.collect()
timings = []
for _ in range(reps):
start = time.perf_counter()
fn()
timings.append(time.perf_counter() - start)
return statistics.median(timings)
def peak_bytes(fn: Callable[[], object]) -> int:
"""Return peak traced allocations for one call."""
gc.collect()
tracemalloc.start()
fn()
_, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
return int(peak)
def dense_mask_bytes(detections: sv.Detections) -> int:
"""Return dense mask storage bytes."""
return 0 if detections.mask is None else int(np.asarray(detections.mask).nbytes)
def compact_mask_bytes(detections: sv.Detections) -> int:
"""Return compact mask storage bytes."""
if not isinstance(detections.mask, CompactMask):
return 0
return sum(rle.nbytes for rle in detections.mask._rles)
def _fmt_ratio(ratio: float) -> str:
"""Format a speedup/compression ratio with colour coding."""
fmt = f"{ratio:.0f}x" if ratio >= 10 else f"{ratio:.2f}x"
if ratio >= 10:
return f"[green]{fmt}[/green]"
elif ratio >= 1:
return f"[yellow]{fmt}[/yellow]"
else:
return f"[red]{fmt}[/red]"
def _fmt_mb(num_bytes: int) -> str:
"""Format bytes as compact megabytes."""
return f"{num_bytes / 1e6:.2f}"
def run_benchmark(
source: str,
image: np.ndarray,
result: dict[str, Any],
reps: int,
warmup: int,
) -> ApiBenchmarkResult:
"""Run one dense-vs-compact parser benchmark."""
# Benchmark the public Roboflow/Inference adapter; RLE masks enter through
# the result payload and should stay compact when compact_masks=True.
def dense() -> sv.Detections:
return sv.Detections.from_inference(result)
def compact() -> sv.Detections:
return sv.Detections.from_inference(result, compact_masks=True)
dense_once = dense()
compact_once = compact()
if not isinstance(dense_once.mask, np.ndarray):
raise TypeError(f"Expected dense ndarray mask, got {type(dense_once.mask)}")
if not isinstance(compact_once.mask, CompactMask):
raise TypeError(f"Expected CompactMask, got {type(compact_once.mask)}")
np.testing.assert_array_equal(compact_once.mask.to_dense(), dense_once.mask)
dense_s = median_seconds(dense, reps, warmup)
compact_s = median_seconds(compact, reps, warmup)
dense_peak = peak_bytes(dense)
compact_peak = peak_bytes(compact)
return ApiBenchmarkResult(
source=source,
resolution=f"{image.shape[1]}x{image.shape[0]}",
segmented_objects=len(dense_once),
dense_s=dense_s,
compact_s=compact_s,
dense_peak_bytes=dense_peak,
compact_peak_bytes=compact_peak,
dense_mask_bytes=dense_mask_bytes(dense_once),
compact_mask_bytes=compact_mask_bytes(compact_once),
pixel_perfect=True,
)
def print_summary(results: list[ApiBenchmarkResult], reps: int, warmup: int) -> None:
"""Print a Rich summary table matching the compact mask benchmark style."""
table = Table(
title="CompactMask from_inference",
box=box.ROUNDED,
show_lines=False,
header_style="bold cyan",
)
table.add_column("src", style="bold", no_wrap=True)
table.add_column("res", no_wrap=True)
table.add_column("seg", justify="right")
table.add_column("dense ms", justify="right")
table.add_column("CM ms", justify="right", style="green")
table.add_column("speedup", justify="right")
table.add_column("peak MB", justify="right", style="cyan")
table.add_column("mask MB", justify="right")
table.add_column("ok", justify="center")
for result in results:
speedup = result.dense_s / max(result.compact_s, 1e-9)
table.add_row(
result.source,
result.resolution,
str(result.segmented_objects),
f"{result.dense_s * 1e3:.2f}",
f"{result.compact_s * 1e3:.2f}",
_fmt_ratio(speedup),
f"{_fmt_mb(result.dense_peak_bytes)}/{_fmt_mb(result.compact_peak_bytes)}",
f"{_fmt_mb(result.dense_mask_bytes)}/{_fmt_mb(result.compact_mask_bytes)}",
"[green]✓[/green]" if result.pixel_perfect else "[red]✗[/red]",
)
console.print(table)
console.print(
"[dim]"
+ " · ".join(
[
f"timings are median of {reps} reps after {warmup} warmups",
"peak MB and mask MB are dense/compact",
"speedup = dense / compact parse time; gains are allocation-driven"
" (avoiding the dense (N,H,W) bool-stack), not faster RLE decode",
"compact RLE arithmetic is typically slower than the dense NumPy path"
" — synthetic-dense-64 shows this adversarial regime (speedup < 1x)",
"OK means compact.to_dense() exactly matches dense masks",
]
)
+ "[/dim]"
)
def main() -> None:
"""Run the benchmark."""
parser = argparse.ArgumentParser()
parser.add_argument("--asset", choices=ASSETS.keys(), default=None)
parser.add_argument("--image", type=Path, default=None)
args = parser.parse_args()
assets = [args.asset] if args.asset is not None else list(ASSETS)
if args.image is not None:
assets = ["custom"]
results = []
if args.asset is None and args.image is None:
image, source, inference_result = synthetic_dense_small_result()
console.rule(f"[bold]{source}[/bold] | {image.shape[1]}x{image.shape[0]}")
results.append(
run_benchmark(
source=source,
image=image,
result=inference_result,
reps=REPETITIONS,
warmup=WARMUP,
)
)
model_id = os.getenv(MODEL_ID_ENV, MODEL_ID)
model = load_inference_model(model_id=model_id, api_key=os.getenv(API_KEY_ENV))
for asset in assets:
image, source = load_image_from_asset(args.image, asset)
console.rule(f"[bold]{source}[/bold] | {image.shape[1]}x{image.shape[0]}")
inference_result = run_inference_once(
image=image,
model=model,
model_id=model_id,
confidence=CONFIDENCE,
iou=IOU,
)
if inference_result is None:
continue
console.print(
f"[dim]captured {count_rle_predictions(inference_result)} RLE masks "
f"from {model_id}[/dim]"
)
artifact = save_segmentation_artifact(
image=image,
result=inference_result,
source=source,
)
if artifact is not None:
console.print(f"[dim]saved segmentation artifact: {artifact}[/dim]")
results.append(
run_benchmark(
source=source,
image=image,
result=inference_result,
reps=REPETITIONS,
warmup=WARMUP,
)
)
if not results:
raise ValueError(f"Model {model_id!r} returned no segmentation masks.")
print_summary(results, reps=REPETITIONS, warmup=WARMUP)
if __name__ == "__main__":
main()

View File

@ -2,7 +2,9 @@
Demonstrates that ``CompactMask`` is a drop-in replacement for dense
``(N, H, W)`` bool arrays in ``supervision.Detections``, while using
significantly less memory and enabling faster annotation.
significantly less memory and enabling faster annotation. The annotation
timing reports frame size, detection count, mask area ratio, and
``MaskAnnotator`` speedup from ROI-only blending.
Run with:
uv run python examples/compact_mask/benchmark.py
@ -12,19 +14,17 @@ Mask complexity is controlled by ``num_vertices``: random polygons with more
vertices produce jaggier boundaries and more RLE runs per row.
"""
from __future__ import annotations
import dataclasses
import gc
import json
import math
import time
import tracemalloc
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Callable
import cv2
import numpy as np
@ -74,7 +74,7 @@ class ScenarioResult:
name: str
resolution: str # e.g. "1920x1080"
num_objects: int
fill_name: str # e.g. "5%"
fill_name: str # mask area ratio, e.g. "5%"
num_vertices: int # polygon vertex count — complexity proxy
# memory (theoretical: raw numpy nbytes)
dense_bytes: int
@ -956,7 +956,7 @@ def print_summary(results: list[ScenarioResult]) -> None:
table.add_column("Scenario", style="bold", min_width=22)
table.add_column("Objects", justify="right", min_width=7)
table.add_column("Resolution", min_width=12, no_wrap=True)
table.add_column("Fill", justify="right", min_width=5, no_wrap=True)
table.add_column("Mask\narea", justify="right", min_width=5, no_wrap=True)
table.add_column("Vertices", justify="right", min_width=8, no_wrap=True)
table.add_column("Dense\ntheory", justify="right", min_width=10)
table.add_column("Compact\ntheory", justify="right", style="green", min_width=9)
@ -1037,7 +1037,8 @@ def print_summary(results: list[ScenarioResult]) -> None:
"Decode ms/mask — to_dense() / N (compact→dense overhead per mask)",
"Area x — .area speedup (RLE sum, no materialisation)",
"Filter x — boolean-index speedup",
"Annot x — MaskAnnotator speedup (crop-paint vs full-frame alloc)",
"Annot x — MaskAnnotator speedup "
"(ROI-only blend vs full-frame overlay)",
f"IoU x — pairwise self-IoU speedup "
f"(dense skipped >{IOU_DENSE_SKIP_GB:.0f} GB)",
"NMS x — mask_non_max_suppression speedup",

View File

@ -1,14 +1,10 @@
# count people in zone
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-detect-and-count-objects-in-polygon-zone.ipynb)
[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://www.youtube.com/watch?v=l_kf9CfZ_8M)
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-detect-and-count-objects-in-polygon-zone.ipynb) [![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://www.youtube.com/watch?v=l_kf9CfZ_8M)
## 👋 hello
This demo is a video analysis tool that counts and highlights objects in specific zones
of a video. Each zone and the objects within it are marked in different colors, making
it easy to see and count the objects in each area. The tool can save this enhanced
video or display it live on the screen.
This demo is a video analysis tool that counts and highlights objects in specific zones of a video. Each zone and the objects within it are marked in different colors, making it easy to see and count the objects in each area. The tool can save this enhanced video or display it live on the screen.
https://github.com/roboflow/supervision/assets/26109316/f84db7b5-79e2-4142-a1da-64daa43ce667
@ -16,76 +12,61 @@ https://github.com/roboflow/supervision/assets/26109316/f84db7b5-79e2-4142-a1da-
- clone repository and navigate to example directory
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/count_people_in_zone
```
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/count_people_in_zone
```
- setup python environment and activate it [optional]
```bash
uv venv
source .venv/bin/activate
```
```bash
uv venv
source .venv/bin/activate
```
- install required dependencies
```bash
uv pip install -r requirements.txt
```
```bash
uv pip install -r requirements.txt
```
- download `traffic_analysis.pt` and `traffic_analysis.mov` files
```bash
./setup.sh
```
```bash
./setup.sh
```
## 🛠️ script arguments
- ultralytics
- `--source_weights_path` (optional): The path to the YOLO model's weights file.
Defaults to `"yolov8x.pt"` if not specified.
- `--source_weights_path` (optional): The path to the YOLO model's weights file. Defaults to `"yolov8x.pt"` if not specified.
- `--zone_configuration_path`: Specifies the path to the JSON file containing zone
configurations. This file defines the polygonal areas in the video where objects will
be counted.
- `--zone_configuration_path`: Specifies the path to the JSON file containing zone configurations. This file defines the polygonal areas in the video where objects will be counted.
- `--source_video_path`: The path to the source video file that will be analyzed.
- `--source_video_path`: The path to the source video file that will be analyzed.
- `--target_video_path` (optional): The path to save the output video with annotations.
If not provided, the processed video will be displayed in real-time.
- `--target_video_path` (optional): The path to save the output video with annotations. If not provided, the processed video will be displayed in real-time.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model
to filter detections. Default is `0.3`.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model to filter detections. Default is `0.3`.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model. Default is `0.7`.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model. Default is `0.7`.
- inference
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided
directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment
variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key)
to acquire your `API KEY`.
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key) to acquire your `API KEY`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default
value is `"yolov8x-1280"`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default value is `"yolov8x-1280"`.
- `--zone_configuration_path`: Specifies the path to the JSON file containing zone
configurations. This file defines the polygonal areas in the video where objects will
be counted.
- `--zone_configuration_path`: Specifies the path to the JSON file containing zone configurations. This file defines the polygonal areas in the video where objects will be counted.
- `--source_video_path`: The path to the source video file that will be analyzed.
- `--source_video_path`: The path to the source video file that will be analyzed.
- `--target_video_path` (optional): The path to save the output video with annotations.
If not provided, the processed video will be displayed in real-time.
- `--target_video_path` (optional): The path to save the output video with annotations. If not provided, the processed video will be displayed in real-time.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model
to filter detections. Default is `0.3`.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model to filter detections. Default is `0.3`.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model. Default is `0.7`.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model. Default is `0.7`.
## 📌 zone configuration
@ -98,35 +79,29 @@ https://github.com/roboflow/supervision/assets/26109316/f84db7b5-79e2-4142-a1da-
- ultralytics
```bash
python ultralytics_example.py \
--zone_configuration_path data/multi-zone-config.json \
--source_video_path data/market-square.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
```bash
python ultralytics_example.py \
--zone_configuration_path data/multi-zone-config.json \
--source_video_path data/market-square.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
- inference
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--zone_configuration_path data/multi-zone-config.json \
--source_video_path data/market-square.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--zone_configuration_path data/multi-zone-config.json \
--source_video_path data/market-square.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
## © license
This demo integrates two main components, each with its own licensing:
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed
under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE).
You can find more details about this license here.
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE). You can find more details about this license here.
- supervision: The analytics code that powers the zone-based analysis in this demo is
based on the Supervision library, which is licensed under the
[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This
makes the Supervision part of the code fully open source and freely usable in your
projects.
- supervision: The analytics code that powers the zone-based analysis in this demo is based on the Supervision library, which is licensed under the [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This makes the Supervision part of the code fully open source and freely usable in your projects.

View File

@ -1,7 +1,6 @@
import json
import os
import cv2
import numpy as np
from inference.core.models.roboflow import RoboflowInferenceModel
from inference.models.utils import get_roboflow_model
@ -117,7 +116,7 @@ def annotate(
"""
annotated_frame = frame.copy()
for zone, zone_annotator, box_annotator in zip(
zones, zone_annotators, box_annotators
zones, zone_annotators, box_annotators, strict=True
):
detections_in_zone = detections[zone.trigger(detections=detections)]
annotated_frame = zone_annotator.annotate(scene=annotated_frame)
@ -135,7 +134,7 @@ def main(
target_video_path: str | None = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
):
) -> None:
"""
Counting people in zones with Inference and Supervision.
@ -179,6 +178,7 @@ def main(
)
sink.write_frame(annotated_frame)
else:
window = sv.ImageWindow("Processed Video")
for frame in tqdm(frames_generator, total=video_info.total_frames):
detections = detect(frame, model, confidence_threshold, iou_threshold)
annotated_frame = annotate(
@ -188,11 +188,12 @@ def main(
box_annotators=box_annotators,
detections=detections,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -1,6 +1,5 @@
import json
import cv2
import numpy as np
from tqdm import tqdm
from ultralytics import YOLO
@ -116,7 +115,7 @@ def annotate(
"""
annotated_frame = frame.copy()
for zone, zone_annotator, box_annotator in zip(
zones, zone_annotators, box_annotators
zones, zone_annotators, box_annotators, strict=True
):
detections_in_zone = detections[zone.trigger(detections=detections)]
annotated_frame = zone_annotator.annotate(scene=annotated_frame)
@ -133,7 +132,7 @@ def main(
target_video_path: str | None = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
):
) -> None:
"""
Counting people in zones with YOLO and Supervision.
@ -167,6 +166,7 @@ def main(
)
sink.write_frame(annotated_frame)
else:
window = sv.ImageWindow("Processed Video")
for frame in tqdm(frames_generator, total=video_info.total_frames):
detections = detect(frame, model, confidence_threshold, iou_threshold)
annotated_frame = annotate(
@ -176,11 +176,12 @@ def main(
box_annotators=box_annotators,
detections=detections,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -2,51 +2,42 @@
## 👋 hello
This script performs heatmap and tracking analysis using YOLOv8, an object-detection method and
ByteTrack, a simple yet effective online multi-object tracking method. It uses the
supervision package for multiple tasks such as drawing heatmap annotations, tracking objects, etc.
This script performs heatmap and tracking analysis using YOLOv8, an object-detection method and ByteTrack, a simple yet effective online multi-object tracking method. It uses the supervision package for multiple tasks such as drawing heatmap annotations, tracking objects, etc.
## 💻 install
- clone repository and navigate to example directory
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/heatmap_and_track
```
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/heatmap_and_track
```
- setup python environment and activate it [optional]
```bash
uv venv
source .venv/bin/activate
```
```bash
uv venv
source .venv/bin/activate
```
- install required dependencies
```bash
uv pip install -r requirements.txt
```
```bash
uv pip install -r requirements.txt
```
## 🛠️ script arguments
- `--source_weights_path`: Required. Specifies the path to the weights file for the
YOLO model. This file contains the trained model data necessary for object detection.
- `--source_video_path` (optional): The path to the source video file that will be
analyzed. This is the input video on which crowd analysis will be performed.
If not specified default is `people-walking.mp4` from supervision assets
- `--source_weights_path`: Required. Specifies the path to the weights file for the YOLO model. This file contains the trained model data necessary for object detection.
- `--source_video_path` (optional): The path to the source video file that will be analyzed. This is the input video on which crowd analysis will be performed. If not specified default is `people-walking.mp4` from supervision assets
- `--target_video_path` (optional): The path to save the output.mp4 video with annotations.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model
to filter detections. Default is `0.3`. This determines how confident the model should
be to recognize an object in the video.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model. Default is 0.7. This value is used to manage object detection accuracy,
particularly in distinguishing between different objects.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model to filter detections. Default is `0.3`. This determines how confident the model should be to recognize an object in the video.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model. Default is 0.7. This value is used to manage object detection accuracy, particularly in distinguishing between different objects.
- `--heatmap_alpha` (optional): Opacity of the overlay mask, between 0 and 1.
- `--radius` (optional): Radius of the heat circle.
- `--track_threshold` (optional): Detection confidence threshold for track activation.
- `--track_activation_threshold` (optional): Detection confidence threshold for track activation.
- `--track_seconds` (optional): Number of seconds to buffer when a track is lost.
- `--match_threshold` (optional): Threshold for matching tracks with detections.
- `--minimum_matching_threshold` (optional): Threshold for matching tracks with detections.
## ⚙️ run
@ -63,12 +54,6 @@ python script.py \
This demo integrates two main components, each with its own licensing:
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed
under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE).
You can find more details about this license here.
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE). You can find more details about this license here.
- supervision: The analytics code that powers the zone-based analysis in this demo is
based on the Supervision library, which is licensed under the
[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This
makes the Supervision part of the code fully open source and freely usable in your
projects.
- supervision: The analytics code that powers the zone-based analysis in this demo is based on the Supervision library, which is licensed under the [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This makes the Supervision part of the code fully open source and freely usable in your projects.

View File

@ -1,123 +1,121 @@
from typing import Optional
import cv2
from ultralytics import YOLO
import supervision as sv
from supervision.assets import VideoAssets, download_assets
def download_video() -> str:
download_assets(VideoAssets.PEOPLE_WALKING)
return VideoAssets.PEOPLE_WALKING.value
def main(
source_weights_path: str,
source_video_path: Optional[str] = None,
target_video_path: str = "output.mp4",
confidence_threshold: float = 0.35,
iou_threshold: float = 0.5,
heatmap_alpha: float = 0.5,
radius: int = 25,
track_activation_threshold: float = 0.35,
track_seconds: int = 5,
minimum_matching_threshold: float = 0.99,
) -> None:
"""
Heatmap and Tracking with Supervision.
Args:
source_weights_path: Path to the source weights file
source_video_path: Path to the source video file
target_video_path: Path to the target video file
confidence_threshold: Confidence threshold for the model
iou_threshold: IOU threshold for the model
heatmap_alpha: Opacity of the overlay mask, between 0 and 1
radius: Radius of the heat circle
track_activation_threshold: Detection confidence threshold for track activation
track_seconds: Number of seconds to buffer when a track is lost
minimum_matching_threshold: Threshold for matching tracks with detections
"""
### instantiate model
model = YOLO(source_weights_path)
source_video_path = source_video_path or download_video()
### heatmap config
heat_map_annotator = sv.HeatMapAnnotator(
position=sv.Position.BOTTOM_CENTER,
opacity=heatmap_alpha,
radius=radius,
kernel_size=25,
top_hue=0,
low_hue=125,
)
### annotation config
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
### get the video fps
cap = cv2.VideoCapture(source_video_path)
fps = int(cap.get(cv2.CAP_PROP_FPS))
cap.release()
### tracker config
byte_tracker = sv.ByteTrack(
track_activation_threshold=track_activation_threshold,
lost_track_buffer=track_seconds * fps,
minimum_matching_threshold=minimum_matching_threshold,
frame_rate=fps,
)
### video config
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
frames_generator = sv.get_video_frames_generator(
source_path=source_video_path, stride=1
)
### Detect, track, annotate, save
with sv.VideoSink(target_path=target_video_path, video_info=video_info) as sink:
for frame in frames_generator:
result = model(
source=frame,
classes=[0], # only person class
conf=confidence_threshold,
iou=iou_threshold,
# show_conf = True,
# save_txt = True,
# save_conf = True,
# save = True,
device=None, # use None = CPU, 0 = single GPU, or [0,1] = dual GPU
)[0]
detections = sv.Detections.from_ultralytics(result) # get detections
detections = byte_tracker.update_with_detections(
detections
) # update tracker
### draw heatmap
annotated_frame = heat_map_annotator.annotate(
scene=frame.copy(), detections=detections
)
### draw other attributes from `detections` object
labels = [
f"#{tracker_id}"
for class_id, tracker_id in zip(
detections.class_id, detections.tracker_id
)
]
label_annotator.annotate(
scene=annotated_frame, detections=detections, labels=labels
)
sink.write_frame(frame=annotated_frame)
if __name__ == "__main__":
from jsonargparse import auto_cli, set_parsing_settings
set_parsing_settings(parse_optionals_as_positionals=True)
auto_cli(main, as_positional=False)
import cv2
from ultralytics import YOLO
import supervision as sv
from supervision.assets import VideoAssets, download_assets
def download_video() -> str:
download_assets(VideoAssets.PEOPLE_WALKING)
return VideoAssets.PEOPLE_WALKING.value
def main(
source_weights_path: str,
source_video_path: str | None = None,
target_video_path: str = "output.mp4",
confidence_threshold: float = 0.35,
iou_threshold: float = 0.5,
heatmap_alpha: float = 0.5,
radius: int = 25,
track_activation_threshold: float = 0.35,
track_seconds: int = 5,
minimum_matching_threshold: float = 0.99,
) -> None:
"""
Heatmap and Tracking with Supervision.
Args:
source_weights_path: Path to the source weights file
source_video_path: Path to the source video file
target_video_path: Path to the target video file
confidence_threshold: Confidence threshold for the model
iou_threshold: IOU threshold for the model
heatmap_alpha: Opacity of the overlay mask, between 0 and 1
radius: Radius of the heat circle
track_activation_threshold: Detection confidence threshold for track activation
track_seconds: Number of seconds to buffer when a track is lost
minimum_matching_threshold: Threshold for matching tracks with detections
"""
### instantiate model
model = YOLO(source_weights_path)
source_video_path = source_video_path or download_video()
### heatmap config
heat_map_annotator = sv.HeatMapAnnotator(
position=sv.Position.BOTTOM_CENTER,
opacity=heatmap_alpha,
radius=radius,
kernel_size=25,
top_hue=0,
low_hue=125,
)
### annotation config
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
### get the video fps
cap = cv2.VideoCapture(source_video_path)
fps = int(cap.get(cv2.CAP_PROP_FPS))
cap.release()
### tracker config
byte_tracker = sv.ByteTrack(
track_activation_threshold=track_activation_threshold,
lost_track_buffer=track_seconds * fps,
minimum_matching_threshold=minimum_matching_threshold,
frame_rate=fps,
)
### video config
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
frames_generator = sv.get_video_frames_generator(
source_path=source_video_path, stride=1
)
### Detect, track, annotate, save
with sv.VideoSink(target_path=target_video_path, video_info=video_info) as sink:
for frame in frames_generator:
result = model(
source=frame,
classes=[0], # only person class
conf=confidence_threshold,
iou=iou_threshold,
# show_conf = True,
# save_txt = True,
# save_conf = True,
# save = True,
device=None, # use None = CPU, 0 = single GPU, or [0,1] = dual GPU
)[0]
detections = sv.Detections.from_ultralytics(result) # get detections
detections = byte_tracker.update_with_detections(
detections
) # update tracker
### draw heatmap
annotated_frame = heat_map_annotator.annotate(
scene=frame.copy(), detections=detections
)
### draw other attributes from `detections` object
labels = [
f"#{tracker_id}"
for class_id, tracker_id in zip(
detections.class_id, detections.tracker_id
)
]
label_annotator.annotate(
scene=annotated_frame, detections=detections, labels=labels
)
sink.write_frame(frame=annotated_frame)
if __name__ == "__main__":
from jsonargparse import auto_cli, set_parsing_settings
set_parsing_settings(parse_optionals_as_positionals=True)
auto_cli(main, as_positional=False)

View File

@ -1,122 +1,96 @@
# speed estimation
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-estimate-vehicle-speed-with-computer-vision.ipynb)
[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/uWP6UjDeZvY)
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-estimate-vehicle-speed-with-computer-vision.ipynb) [![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://youtu.be/uWP6UjDeZvY)
## 👋 hello
This example performs speed estimation analysis using various object-detection models
and ByteTrack - a simple yet effective online multi-object tracking method. It uses the
supervision package for multiple tasks such as tracking, annotations, etc.
This example performs speed estimation analysis using various object-detection models and ByteTrack - a simple yet effective online multi-object tracking method. It uses the supervision package for multiple tasks such as tracking, annotations, etc.
https://github.com/roboflow/supervision/assets/26109316/d50118c1-2ae4-458d-915a-5d860fd36f71
> [!IMPORTANT]
> Adjust the [`SOURCE`](https://github.com/roboflow/supervision/blob/e32b05a636dab2ea1f39299e529c4b22b8baa8da/examples/speed_estimation/ultralytics_example.py#L10)
> and [`TARGET`](https://github.com/roboflow/supervision/blob/e32b05a636dab2ea1f39299e529c4b22b8baa8da/examples/speed_estimation/ultralytics_example.py#L15)
> configuration if you plan to run a speed estimation script on your video file. Those must be adjusted separately for each camera view. You can learn more
> from our YouTube [tutorial](https://youtu.be/uWP6UjDeZvY).
> [!IMPORTANT] Adjust the [`SOURCE`](https://github.com/roboflow/supervision/blob/e32b05a636dab2ea1f39299e529c4b22b8baa8da/examples/speed_estimation/ultralytics_example.py#L10) and [`TARGET`](https://github.com/roboflow/supervision/blob/e32b05a636dab2ea1f39299e529c4b22b8baa8da/examples/speed_estimation/ultralytics_example.py#L15) configuration if you plan to run a speed estimation script on your video file. Those must be adjusted separately for each camera view. You can learn more from our YouTube [tutorial](https://youtu.be/uWP6UjDeZvY).
## 💻 install
- clone repository and navigate to example directory
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/speed_estimation
```
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/speed_estimation
```
- setup python environment and activate it [optional]
```bash
uv venv
source .venv/bin/activate
```
```bash
uv venv
source .venv/bin/activate
```
- install required dependencies
```bash
uv pip install -r requirements.txt
```
```bash
uv pip install -r requirements.txt
```
- download `vehicles.mp4` file
```bash
python video_downloader.py
```
```bash
python video_downloader.py
```
## 🛠️ script arguments
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided
directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment
variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key)
to acquire your `API KEY`.
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key) to acquire your `API KEY`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default
value is `"yolov8x-1280"`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default value is `"yolov8x-1280"`.
- `--source_weights_path`: Required. Specifies the path to the YOLO model's weights
file, which is essential for the object detection process. This file contains the
data that the model uses to identify objects in the video.
- `--source_weights_path`: Required. Specifies the path to the YOLO model's weights file, which is essential for the object detection process. This file contains the data that the model uses to identify objects in the video.
- `--source_video_path`: Required. The path to the source video file that will be
analyzed. This is the input video on which traffic flow analysis will be performed.
- `--source_video_path`: Required. The path to the source video file that will be analyzed. This is the input video on which traffic flow analysis will be performed.
- `--target_video_path`: The path to save the output video with
annotations. If not specified, the processed video will be displayed in real-time
without being saved.
- `--target_video_path`: The path to save the output video with annotations. If not specified, the processed video will be displayed in real-time without being saved.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO
model to filter detections. Default is `0.3`. This determines how confident the
model should be to recognize an object in the video.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model to filter detections. Default is `0.3`. This determines how confident the model should be to recognize an object in the video.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model. Default is 0.7. This value is used to manage object detection
accuracy, particularly in distinguishing between different objects.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model. Default is 0.7. This value is used to manage object detection accuracy, particularly in distinguishing between different objects.
## ⚙️ run
- yolo-nas
```bash
python yolo_nas_example.py \
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
```bash
python yolo_nas_example.py \
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
- inference
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
- ultralytics
```bash
python ultralytics_example.py \
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
```bash
python ultralytics_example.py \
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
## © license
This demo integrates two main components, each with its own licensing:
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed
under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE).
You can find more details about this license here.
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE). You can find more details about this license here.
- supervision: The analytics code that powers the zone-based analysis in this demo is
based on the Supervision library, which is licensed under the
[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This
makes the Supervision part of the code fully open source and freely usable in your
projects.
- supervision: The analytics code that powers the zone-based analysis in this demo is based on the Supervision library, which is licensed under the [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This makes the Supervision part of the code fully open source and freely usable in your projects.

View File

@ -44,7 +44,7 @@ def main(
roboflow_api_key: str | None = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
):
) -> None:
"""
Vehicle Speed Estimation using Inference and Supervision.
@ -96,6 +96,7 @@ def main(
coordinates = defaultdict(lambda: deque(maxlen=int(video_info.fps)))
with sv.VideoSink(target_video_path, video_info) as sink:
window = sv.ImageWindow("frame")
for frame in frame_generator:
results = model.infer(
frame, confidence=confidence_threshold, iou=iou_threshold
@ -109,7 +110,7 @@ def main(
)
points = view_transformer.transform_points(points=points).astype(int)
for tracker_id, [_, y] in zip(detections.tracker_id, points):
for tracker_id, [_, y] in zip(detections.tracker_id, points, strict=True):
coordinates[tracker_id].append(y)
labels = []
@ -136,10 +137,11 @@ def main(
)
sink.write_frame(annotated_frame)
cv2.imshow("frame", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -41,7 +41,7 @@ def main(
target_video_path: str,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
):
) -> None:
"""
Vehicle Speed Estimation using Ultralytics and Supervision.
@ -82,6 +82,7 @@ def main(
coordinates = defaultdict(lambda: deque(maxlen=int(video_info.fps)))
with sv.VideoSink(target_video_path, video_info) as sink:
window = sv.ImageWindow("frame")
for frame in frame_generator:
result = model(frame, conf=confidence_threshold, iou=iou_threshold)[0]
detections = sv.Detections.from_ultralytics(result)
@ -93,7 +94,7 @@ def main(
)
points = view_transformer.transform_points(points=points).astype(int)
for tracker_id, [_, y] in zip(detections.tracker_id, points):
for tracker_id, [_, y] in zip(detections.tracker_id, points, strict=True):
coordinates[tracker_id].append(y)
labels = []
@ -120,10 +121,11 @@ def main(
)
sink.write_frame(annotated_frame)
cv2.imshow("frame", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -42,7 +42,7 @@ def main(
target_video_path: str,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
):
) -> None:
"""
Vehicle Speed Estimation using YOLO-NAS and Supervision.
@ -83,6 +83,7 @@ def main(
coordinates = defaultdict(lambda: deque(maxlen=int(video_info.fps)))
with sv.VideoSink(target_video_path, video_info) as sink:
window = sv.ImageWindow("frame")
for frame in frame_generator:
result = model.predict(frame, conf=confidence_threshold, iou=iou_threshold)[
0
@ -123,10 +124,11 @@ def main(
)
sink.write_frame(annotated_frame)
cv2.imshow("frame", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -4,10 +4,7 @@
## 👋 hello
Practical demonstration on leveraging computer vision for analyzing wait times and
monitoring the duration that objects or individuals spend in predefined areas of video
frames. This example project, perfect for retail analytics or traffic management
applications.
Practical demonstration on leveraging computer vision for analyzing wait times and monitoring the duration that objects or individuals spend in predefined areas of video frames. This example project, perfect for retail analytics or traffic management applications.
https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-d38b86334c39
@ -15,23 +12,25 @@ https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-
- clone repository and navigate to example directory
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/time_in_zone
```
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/time_in_zone
```
- setup python environment and activate it [optional]
```bash
uv venv
source .venv/bin/activate
```
```bash
uv venv
source .venv/bin/activate
```
- install required dependencies
```bash
uv pip install -r requirements.txt
```
```bash
uv pip install -r requirements.txt
```
The three RTSP `*_stream_example.py` scripts display frames from an `InferencePipeline` callback running on a worker thread, so they use OpenCV HighGUI instead of `sv.ImageWindow`. Install `opencv-python` and keep only one OpenCV wheel installed to run those scripts. The file and naive-stream examples use `sv.ImageWindow`, which works regardless of which OpenCV wheel (or none) is installed.
## 🛠 scripts
@ -59,9 +58,7 @@ python scripts/download_from_youtube.py \
### `stream_from_file`
This script allows you to stream video files from a directory. It's an awesome way to
mock a live video stream for local testing. Video will be streamed in a loop under
`rtsp://localhost:8554/live0.stream` URL. This script requires docker to be installed.
This script allows you to stream video files from a directory. It's an awesome way to mock a live video stream for local testing. Video will be streamed in a loop under `rtsp://localhost:8554/live0.stream` URL. This script requires docker to be installed.
- `--video_directory`: Directory containing video files to stream.
- `--number_of_streams`: Number of video files to stream.
@ -80,10 +77,7 @@ python scripts/stream_from_file.py \
### `draw_zones`
If you want to test zone time in zone analysis on your own video, you can use this
script to design custom zones and save results as a JSON file. The script will open a
window where you can draw polygons on the source image or video file. The polygons will
be saved as a JSON file.
If you want to test zone time in zone analysis on your own video, you can use this script to design custom zones and save results as a JSON file. The script will open a window where you can draw polygons on the source image or video file. The polygons will be saved as a JSON file.
- `--source_path`: Path to the source image or video file for drawing polygons.
- `--zone_configuration_path`: Path where the polygon annotations will be saved as a JSON file.
@ -324,12 +318,6 @@ python ultralytics_stream_example.py \
This demo integrates two main components, each with its own licensing:
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed
under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE).
You can find more details about this license here.
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE). You can find more details about this license here.
- supervision: The analytics code that powers the zone-based analysis in this demo is
based on the Supervision library, which is licensed under the
[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This
makes the Supervision part of the code fully open source and freely usable in your
projects.
- supervision: The analytics code that powers the zone-based analysis in this demo is based on the Supervision library, which is licensed under the [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This makes the Supervision part of the code fully open source and freely usable in your projects.

View File

@ -1,4 +1,3 @@
import cv2
import numpy as np
from inference import get_model
from utils.general import find_in_list, load_zones_config
@ -49,6 +48,7 @@ def main(
]
timers = [FPSBasedTimer(video_info.fps) for _ in zones]
window = sv.ImageWindow("Processed Video")
for frame in frames_generator:
results = model.infer(
frame, confidence=confidence_threshold, iou_threshold=iou_threshold
@ -84,10 +84,11 @@ def main(
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -1,4 +1,3 @@
import cv2
import numpy as np
from inference import get_model
from utils.general import find_in_list, get_stream_frames_generator, load_zones_config
@ -49,6 +48,7 @@ def main(
]
timers = [ClockBasedTimer() for _ in zones]
window = sv.ImageWindow("Processed Video")
for frame in frames_generator:
fps_monitor.tick()
fps = fps_monitor.fps
@ -94,10 +94,11 @@ def main(
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -15,7 +15,7 @@ LABEL_ANNOTATOR = sv.LabelAnnotator(
class CustomSink:
def __init__(self, zone_configuration_path: str, classes: list[int]):
def __init__(self, zone_configuration_path: str, classes: list[int]) -> None:
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
self.fps_monitor = sv.FPSMonitor()

View File

@ -2,7 +2,6 @@ from __future__ import annotations
from enum import Enum
import cv2
import numpy as np
from rfdetr import RFDETRBase, RFDETRLarge, RFDETRMedium, RFDETRNano, RFDETRSmall
from utils.general import find_in_list, load_zones_config
@ -25,7 +24,7 @@ class ModelSize(Enum):
LARGE = "large"
@classmethod
def list(cls):
def list(cls) -> list[str]:
return list(map(lambda c: c.value, cls))
@classmethod
@ -44,7 +43,9 @@ class ModelSize(Enum):
)
def load_model(checkpoint: ModelSize | str, device: str, resolution: int):
def load_model(
checkpoint: ModelSize | str, device: str, resolution: int
) -> RFDETRBase | RFDETRLarge | RFDETRMedium | RFDETRNano | RFDETRSmall:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint == ModelSize.NANO:
@ -126,6 +127,7 @@ def main(
]
timers = [FPSBasedTimer(video_info.fps) for _ in zones]
window = sv.ImageWindow("Processed Video")
for frame in frames_generator:
detections = model.predict(frame, threshold=confidence_threshold)
detections = detections[find_in_list(detections.class_id, classes)]
@ -159,10 +161,11 @@ def main(
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -2,7 +2,6 @@ from __future__ import annotations
from enum import Enum
import cv2
import numpy as np
from rfdetr import RFDETRBase, RFDETRLarge, RFDETRMedium, RFDETRNano, RFDETRSmall
from utils.general import find_in_list, get_stream_frames_generator, load_zones_config
@ -25,7 +24,7 @@ class ModelSize(Enum):
LARGE = "large"
@classmethod
def list(cls):
def list(cls) -> list[str]:
return list(map(lambda c: c.value, cls))
@classmethod
@ -44,7 +43,9 @@ class ModelSize(Enum):
)
def load_model(checkpoint: ModelSize | str, device: str, resolution: int):
def load_model(
checkpoint: ModelSize | str, device: str, resolution: int
) -> RFDETRBase | RFDETRLarge | RFDETRMedium | RFDETRNano | RFDETRSmall:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint == ModelSize.NANO:
@ -126,6 +127,7 @@ def main(
]
timers = [ClockBasedTimer() for _ in zones]
window = sv.ImageWindow("Processed Video")
for frame in frames_generator:
fps_monitor.tick()
fps = fps_monitor.fps
@ -169,11 +171,12 @@ def main(
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -21,7 +21,7 @@ class ModelSize(Enum):
LARGE = "large"
@classmethod
def list(cls):
def list(cls) -> list[str]:
return [c.value for c in cls]
@classmethod
@ -41,7 +41,9 @@ class ModelSize(Enum):
)
def load_model(checkpoint: ModelSize | str, device: str, resolution: int):
def load_model(
checkpoint: ModelSize | str, device: str, resolution: int
) -> RFDETRBase | RFDETRLarge | RFDETRMedium | RFDETRNano | RFDETRSmall:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint == ModelSize.NANO:
return RFDETRNano(device=device, resolution=resolution)
@ -77,7 +79,7 @@ LABEL_ANNOTATOR = sv.LabelAnnotator(
class CustomSink:
def __init__(self, zone_configuration_path: str, classes: list[int]):
def __init__(self, zone_configuration_path: str, classes: list[int]) -> None:
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.8)
self.fps_monitor = sv.FPSMonitor()

View File

@ -1,5 +1,3 @@
from __future__ import annotations
import os
import sys
from typing import Any

View File

@ -1,8 +1,5 @@
from __future__ import annotations
import json
import os
from typing import Any
import cv2
import numpy as np
@ -10,11 +7,10 @@ from jsonargparse import auto_cli
import supervision as sv
KEY_ENTER = 13
KEY_NEWLINE = 10
KEY_ESCAPE = 27
KEY_QUIT = ord("q")
KEY_SAVE = ord("s")
KEY_ENTER = {"Return", "KP_Enter"}
KEY_ESCAPE = "Escape"
KEY_QUIT = "q"
KEY_SAVE = "s"
THICKNESS = 2
COLORS = sv.ColorPalette.DEFAULT
@ -37,15 +33,17 @@ def resolve_source(source_path: str) -> np.ndarray | None:
return frame
def mouse_event(event: int, x: int, y: int, flags: int, param: Any) -> None:
def mouse_event(x: int, y: int, event_type: str) -> None:
global current_mouse_position
if event == cv2.EVENT_MOUSEMOVE:
if event_type == "move":
current_mouse_position = (x, y)
elif event == cv2.EVENT_LBUTTONDOWN:
elif event_type == "down":
POLYGONS[-1].append((x, y))
def redraw(image: np.ndarray, original_image: np.ndarray) -> None:
def redraw(
image: np.ndarray, original_image: np.ndarray, window: sv.ImageWindow
) -> None:
global POLYGONS, current_mouse_position
image[:] = original_image.copy()
for idx, polygon in enumerate(POLYGONS):
@ -80,10 +78,12 @@ def redraw(image: np.ndarray, original_image: np.ndarray) -> None:
color=color,
thickness=THICKNESS,
)
cv2.imshow(WINDOW_NAME, image)
window.show(image)
def close_and_finalize_polygon(image: np.ndarray, original_image: np.ndarray) -> None:
def close_and_finalize_polygon(
image: np.ndarray, original_image: np.ndarray, window: sv.ImageWindow
) -> None:
if len(POLYGONS[-1]) > 2:
cv2.line(
img=image,
@ -95,7 +95,7 @@ def close_and_finalize_polygon(image: np.ndarray, original_image: np.ndarray) ->
POLYGONS.append([])
image[:] = original_image.copy()
redraw_polygons(image)
cv2.imshow(WINDOW_NAME, image)
window.show(image)
def redraw_polygons(image: np.ndarray) -> None:
@ -119,7 +119,9 @@ def redraw_polygons(image: np.ndarray) -> None:
)
def save_polygons_to_json(polygons, target_path):
def save_polygons_to_json(
polygons: list[list[tuple[int, int]]], target_path: str | os.PathLike[str]
) -> None:
data_to_save = polygons if polygons[-1] else polygons[:-1]
with open(target_path, "w") as f:
json.dump(data_to_save, f)
@ -140,13 +142,16 @@ def main(source_path: str, zone_configuration_path: str) -> None:
return
image = original_image.copy()
cv2.imshow(WINDOW_NAME, image)
cv2.setMouseCallback(WINDOW_NAME, mouse_event, image)
window = sv.ImageWindow(WINDOW_NAME)
window.set_mouse_callback(mouse_event)
window.show(image)
while True:
key = cv2.waitKey(1) & 0xFF
if key == KEY_ENTER or key == KEY_NEWLINE:
close_and_finalize_polygon(image, original_image)
key = window.wait_key(1)
if not window.is_open:
break
if key in KEY_ENTER:
close_and_finalize_polygon(image, original_image, window)
elif key == KEY_ESCAPE:
POLYGONS[-1] = []
current_mouse_position = None
@ -154,11 +159,11 @@ def main(source_path: str, zone_configuration_path: str) -> None:
save_polygons_to_json(POLYGONS, zone_configuration_path)
print(f"Polygons saved to {zone_configuration_path}")
break
redraw(image, original_image)
redraw(image, original_image, window)
if key == KEY_QUIT:
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -1,4 +1,3 @@
import cv2
import numpy as np
from ultralytics import YOLO
from utils.general import find_in_list, load_zones_config
@ -49,6 +48,7 @@ def main(
]
timers = [FPSBasedTimer(video_info.fps) for _ in zones]
window = sv.ImageWindow("Processed Video")
for frame in frames_generator:
results = model(
frame,
@ -88,10 +88,11 @@ def main(
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -1,4 +1,3 @@
import cv2
import numpy as np
from ultralytics import YOLO
from utils.general import find_in_list, get_stream_frames_generator, load_zones_config
@ -49,6 +48,7 @@ def main(
]
timers = [ClockBasedTimer() for _ in zones]
window = sv.ImageWindow("Processed Video")
for frame in frames_generator:
fps_monitor.tick()
fps = fps_monitor.fps
@ -98,10 +98,11 @@ def main(
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
if __name__ == "__main__":

View File

@ -1,5 +1,3 @@
from __future__ import annotations
import cv2
import numpy as np
from inference import InferencePipeline
@ -18,7 +16,7 @@ LABEL_ANNOTATOR = sv.LabelAnnotator(
class CustomSink:
def __init__(self, zone_configuration_path: str, classes: list[int]):
def __init__(self, zone_configuration_path: str, classes: list[int]) -> None:
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.8)
self.fps_monitor = sv.FPSMonitor()

View File

@ -2,107 +2,82 @@
## 👋 hello
This script provides functionality for processing videos using YOLOv8 for object
detection and Supervision for tracking and annotation.
This script provides functionality for processing videos using YOLOv8 for object detection and Supervision for tracking and annotation.
## 💻 install
- clone repository and navigate to example directory
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/tracking
```
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/tracking
```
- setup python environment and activate it [optional]
```bash
uv venv
source .venv/bin/activate
```
```bash
uv venv
source .venv/bin/activate
```
- install required dependencies
```bash
uv pip install -r requirements.txt
```
```bash
uv pip install -r requirements.txt
```
## 🛠️ script arguments
- ultralytics
- `--source_weights_path`: Required. Specifies the path to the YOLO model's weights
file, which is essential for the object detection process. This file contains the data
that the model uses to identify objects in the video.
- `--source_weights_path`: Required. Specifies the path to the YOLO model's weights file, which is essential for the object detection process. This file contains the data that the model uses to identify objects in the video.
- `--source_video_path`: Required. The path to the source video file to be processed.
This is the video on which object detection and annotation will be performed.
- `--source_video_path`: Required. The path to the source video file to be processed. This is the video on which object detection and annotation will be performed.
- `--target_video_path`: Required. The path where the processed video, with annotations
added, will be saved. This is your output video file.
- `--target_video_path`: Required. The path where the processed video, with annotations added, will be saved. This is your output video file.
- `--confidence_threshold` (optional): Sets the confidence level at which the model
identifies objects in the video. Default is `0.3`. A higher threshold makes the model
more selective, while a lower threshold makes it more inclusive in identifying objects.
- `--confidence_threshold` (optional): Sets the confidence level at which the model identifies objects in the video. Default is `0.3`. A higher threshold makes the model more selective, while a lower threshold makes it more inclusive in identifying objects.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model, defaulting to `0.7`. This parameter helps in differentiating between
distinct objects, especially in crowded scenes.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model, defaulting to `0.7`. This parameter helps in differentiating between distinct objects, especially in crowded scenes.
- inference
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided
directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment
variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key)
to acquire your `API KEY`.
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key) to acquire your `API KEY`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default
value is `"yolov8x-1280"`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default value is `"yolov8x-1280"`.
- `--source_video_path`: Required. The path to the source video file to be processed.
This is the video on which object detection and annotation will be performed.
- `--source_video_path`: Required. The path to the source video file to be processed. This is the video on which object detection and annotation will be performed.
- `--target_video_path`: Required. The path where the processed video, with annotations
added, will be saved. This is your output video file.
- `--target_video_path`: Required. The path where the processed video, with annotations added, will be saved. This is your output video file.
- `--confidence_threshold` (optional): Sets the confidence level at which the model
identifies objects in the video. Default is `0.3`. A higher threshold makes the model
more selective, while a lower threshold makes it more inclusive in identifying objects.
- `--confidence_threshold` (optional): Sets the confidence level at which the model identifies objects in the video. Default is `0.3`. A higher threshold makes the model more selective, while a lower threshold makes it more inclusive in identifying objects.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model, defaulting to `0.7`. This parameter helps in differentiating between
distinct objects, especially in crowded scenes.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model, defaulting to `0.7`. This parameter helps in differentiating between distinct objects, especially in crowded scenes.
## ⚙️ run
- inference
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path input.mp4 \
--target_video_path tracking_result.mp4
```
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path input.mp4 \
--target_video_path tracking_result.mp4
```
- ultralytics
```bash
python ultralytics_example.py \
--source_weights_path yolov8s.pt \
--source_video_path input.mp4 \
--target_video_path tracking_result.mp4
```
```bash
python ultralytics_example.py \
--source_weights_path yolov8s.pt \
--source_video_path input.mp4 \
--target_video_path tracking_result.mp4
```
## © license
This demo integrates two main components, each with its own licensing:
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed
under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE).
You can find more details about this license here.
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE). You can find more details about this license here.
- supervision: The analytics code that powers the zone-based analysis in this demo is
based on the Supervision library, which is licensed under the
[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This
makes the Supervision part of the code fully open source and freely usable in your
projects.
- supervision: The analytics code that powers the zone-based analysis in this demo is based on the Supervision library, which is licensed under the [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This makes the Supervision part of the code fully open source and freely usable in your projects.

View File

@ -2,9 +2,7 @@
## 👋 hello
This script performs traffic flow analysis using YOLOv8, an object-detection method and
ByteTrack, a simple yet effective online multi-object tracking method. It uses the
supervision package for multiple tasks such as tracking, annotations, etc.
This script performs traffic flow analysis using YOLOv8, an object-detection method and ByteTrack, a simple yet effective online multi-object tracking method. It uses the supervision package for multiple tasks such as tracking, annotations, etc.
https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-60e9c81b3900
@ -12,112 +10,86 @@ https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-
- clone repository and navigate to example directory
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/traffic_analysis
```
```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/traffic_analysis
```
- setup python environment and activate it [optional]
```bash
uv venv
source .venv/bin/activate
```
```bash
uv venv
source .venv/bin/activate
```
- install required dependencies
```bash
uv pip install -r requirements.txt
```
```bash
uv pip install -r requirements.txt
```
- download `traffic_analysis.pt` and `traffic_analysis.mov` files
```bash
./setup.sh
```
```bash
./setup.sh
```
## 🛠️ script arguments
- ultralytics
- `--source_weights_path`: Required. Specifies the path to the YOLO model's weights
file, which is essential for the object detection process. This file contains the
data that the model uses to identify objects in the video.
- `--source_weights_path`: Required. Specifies the path to the YOLO model's weights file, which is essential for the object detection process. This file contains the data that the model uses to identify objects in the video.
- `--source_video_path`: Required. The path to the source video file that will be
analyzed. This is the input video on which traffic flow analysis will be performed.
- `--source_video_path`: Required. The path to the source video file that will be analyzed. This is the input video on which traffic flow analysis will be performed.
- `--target_video_path` (optional): The path to save the output video with
annotations. If not specified, the processed video will be displayed in real-time
without being saved.
- `--target_video_path` (optional): The path to save the output video with annotations. If not specified, the processed video will be displayed in real-time without being saved.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO
model to filter detections. Default is `0.3`. This determines how confident the
model should be to recognize an object in the video.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model to filter detections. Default is `0.3`. This determines how confident the model should be to recognize an object in the video.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model. Default is 0.7. This value is used to manage object detection
accuracy, particularly in distinguishing between different objects.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model. Default is 0.7. This value is used to manage object detection accuracy, particularly in distinguishing between different objects.
- inference
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided
directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment
variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key)
to acquire your `API KEY`.
- `--roboflow_api_key` (optional): The API key for Roboflow services. If not provided directly, the script tries to fetch it from the `ROBOFLOW_API_KEY` environment variable. Follow [this guide](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key) to acquire your `API KEY`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default
value is `"vehicle-count-in-drone-video/6"`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default value is `"vehicle-count-in-drone-video/6"`.
- `--source_video_path`: Required. The path to the source video file that will be
analyzed. This is the input video on which traffic flow analysis will be performed.
- `--source_video_path`: Required. The path to the source video file that will be analyzed. This is the input video on which traffic flow analysis will be performed.
- `--target_video_path` (optional): The path to save the output video with
annotations. If not specified, the processed video will be displayed in real-time
without being saved.
- `--target_video_path` (optional): The path to save the output video with annotations. If not specified, the processed video will be displayed in real-time without being saved.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO
model to filter detections. Default is `0.3`. This determines how confident the
model should be to recognize an object in the video.
- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model to filter detections. Default is `0.3`. This determines how confident the model should be to recognize an object in the video.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model. Default is 0.7. This value is used to manage object detection
accuracy, particularly in distinguishing between different objects.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model. Default is 0.7. This value is used to manage object detection accuracy, particularly in distinguishing between different objects.
## ⚙️ run
- ultralytics
```bash
python ultralytics_example.py \
--source_weights_path data/traffic_analysis.pt \
--source_video_path data/traffic_analysis.mov \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
--target_video_path data/traffic_analysis_result.mov
```
```bash
python ultralytics_example.py \
--source_weights_path data/traffic_analysis.pt \
--source_video_path data/traffic_analysis.mov \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
--target_video_path data/traffic_analysis_result.mov
```
- inference
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path data/traffic_analysis.mov \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
--target_video_path data/traffic_analysis_result.mov
```
```bash
python inference_example.py \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path data/traffic_analysis.mov \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
--target_video_path data/traffic_analysis_result.mov
```
## © license
This demo integrates two main components, each with its own licensing:
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed
under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE).
You can find more details about this license here.
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE). You can find more details about this license here.
- supervision: The analytics code that powers the zone-based analysis in this demo is
based on the Supervision library, which is licensed under the
[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This
makes the Supervision part of the code fully open source and freely usable in your
projects.
- supervision: The analytics code that powers the zone-based analysis in this demo is based on the Supervision library, which is licensed under the [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This makes the Supervision part of the code fully open source and freely usable in your projects.

View File

@ -1,9 +1,6 @@
from __future__ import annotations
import os
from collections.abc import Iterable
import cv2
import numpy as np
from inference.models.utils import get_roboflow_model
from tqdm import tqdm
@ -103,7 +100,7 @@ class VideoProcessor:
)
self.detections_manager = DetectionsManager()
def process_video(self):
def process_video(self) -> None:
frame_generator = sv.get_video_frames_generator(
source_path=self.source_video_path
)
@ -114,12 +111,14 @@ class VideoProcessor:
annotated_frame = self.process_frame(frame)
sink.write_frame(annotated_frame)
else:
window = sv.ImageWindow("Processed Video")
for frame in tqdm(frame_generator, total=self.video_info.total_frames):
annotated_frame = self.process_frame(frame)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
def annotate_frame(
self, frame: np.ndarray, detections: sv.Detections

View File

@ -1,8 +1,5 @@
from __future__ import annotations
from collections.abc import Iterable
import cv2
import numpy as np
from tqdm import tqdm
from ultralytics import YOLO
@ -100,7 +97,7 @@ class VideoProcessor:
)
self.detections_manager = DetectionsManager()
def process_video(self):
def process_video(self) -> None:
frame_generator = sv.get_video_frames_generator(
source_path=self.source_video_path
)
@ -111,12 +108,14 @@ class VideoProcessor:
annotated_frame = self.process_frame(frame)
sink.write_frame(annotated_frame)
else:
window = sv.ImageWindow("Processed Video")
for frame in tqdm(frame_generator, total=self.video_info.total_frames):
annotated_frame = self.process_frame(frame)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
window.show(annotated_frame)
key = window.wait_key(1)
if not window.is_open or key == "q":
break
cv2.destroyAllWindows()
window.close()
def annotate_frame(
self, frame: np.ndarray, detections: sv.Detections

View File

@ -20,7 +20,7 @@ extra:
link: https://discord.gg/GbfgXGJ8Bk
analytics:
provider: google
property: G-P7ZG0Y19G5
property: G-SEKT4K1EWR
version:
provider: mike
@ -42,6 +42,8 @@ nav:
- Process Datasets: how_to/process_datasets.md
- Benchmark a Model: how_to/benchmark_a_model.md
- Count in Zone: how_to/count_in_zone.md
- Use Compact Masks: how_to/use_compact_masks.md
- OpenCV Migration: how_to/opencv_migration.md
- Reference:
- Detection and Segmentation:
- Core: detection/core.md
@ -52,7 +54,7 @@ nav:
- Boxes: detection/utils/boxes.md
- Masks: detection/utils/masks.md
- Polygons: detection/utils/polygons.md
- VLMs: detection/utils/vlms.md
- VLM Utils: detection/utils/vlms.md
- Keypoint Detection:
- Core: keypoint/core.md
- Annotators: keypoint/annotators.md
@ -76,8 +78,10 @@ nav:
- Common Values: metrics/common_values.md
- Legacy Metrics: detection/metrics.md
- Utils:
- Conversion: utils/conversion.md
- Video: utils/video.md
- Image: utils/image.md
- Image Window: utils/image_window.md
- Iterables: utils/iterables.md
- Notebook: utils/notebook.md
- File: utils/file.md
@ -85,6 +89,9 @@ nav:
- Geometry: utils/geometry.md
- Assets: assets.md
- Cookbooks: cookbooks.md
- Contributing: contributing.md
- Code of Conduct: code_of_conduct.md
- License: license.md
- Changelog:
- Changelog: changelog.md
- Deprecated: deprecated.md
@ -132,10 +139,10 @@ plugins:
default_handler: python
handlers:
python:
paths: [supervision]
load_external_modules: true
options:
parameter_headings: true
paths: [supervision]
load_external_modules: true
allow_inspection: true
show_bases: true
group_by_category: true

View File

@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for py in "$SCRIPT_DIR"/*.py; do
echo "Converting: $(basename "$py")"
jupytext --to ipynb "$py"
done

View File

@ -1,450 +0,0 @@
# ---
# jupyter:
# jupytext:
# cell_metadata_filter: -all
# formats: ipynb,py:percent
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.19.1
# ---
# ruff: noqa: E402
# %% [markdown]
# # supervision 0.28.0: Memory-Efficient Instance Segmentation
#
# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/develop/notebooks/release-demo_0-28.ipynb)
#
# **supervision** is a set of reusable tools for computer vision.
# Two headlining changes in 0.28.0:
#
# 1. **`sv.Detections.from_sam3`** -- first-class support for SAM3 (Segment
# Anything Model 3) inference responses. supervision now parses both the
# PCS (prompt-controlled segmentation) and PVS (point-video segmentation)
# output formats directly into a `sv.Detections` object.
#
# 2. **`sv.CompactMask`** -- instance masks stored as RLE-encoded bounding-box
# crops instead of full-resolution bitmaps. Any segmentation model --
# RF-DETR Seg, SAM3, YOLO-Seg -- can feed into CompactMask. Memory drops
# 10-100x without changing the API anywhere in supervision.
#
# **Story**: run RF-DETR Seg on a real image, visualise the masks, then convert
# to CompactMask and watch the memory footprint collapse.
#
# **Sections:**
# 1. [Install](#1-install)
# 2. [Download sample image](#2-download-sample-image)
# 3. [RF-DETR Seg -- instance segmentation](#3-rf-detr-seg)
# 4. [CompactMask -- memory-efficient storage](#4-compactmask)
# 5. [SAM3 -- text-prompted segmentation](#5-sam3)
# 6. [Other notable changes in 0.28.0](#6-other-notable-changes)
# 7. [Next steps](#7-next-steps)
# %% [markdown]
# ## 1. Install
# %%
# !pip install -q 'supervision==0.28.0' 'rfdetr' 'inference-sdk>=0.9' numpy matplotlib
# %% [markdown]
# ## 2. Download sample image
#
# `sv.ImageAssets` is new in 0.28.0 -- a counterpart to the existing
# `sv.VideoAssets`. `download_assets` caches locally and returns the path.
# %%
# %matplotlib inline
import cv2
import matplotlib.pyplot as plt
import numpy as np
import supervision as sv
from supervision.assets import ImageAssets, download_assets
image_path = download_assets(ImageAssets.PEOPLE_WALKING)
print(f"Image: {image_path}")
image_bgr = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
H, W = image_bgr.shape[:2]
print(f"Resolution: {W} x {H}")
plt.figure(figsize=(12, 7))
plt.imshow(image_rgb)
plt.axis("off")
plt.title("people-walking.jpg")
plt.tight_layout()
plt.show()
# %% [markdown]
# ## 3. RF-DETR Seg
#
# **RF-DETR** is a real-time transformer-based object detection model from Roboflow.
# The `RFDETRSegSmall` variant adds an instance segmentation head -- it produces
# one binary mask per detected instance alongside the bounding box.
#
# Key facts for this demo:
#
# - Pretrained on **COCO** (80 object categories) -- detects people, bags, cars, etc.
# - Weights download automatically on first `RFDETRSegSmall()` call (~100 MB).
# - `model.predict()` returns **`sv.Detections`** directly -- no converter needed.
# Masks are a `(N, H, W)` bool array attached as `detections.mask`.
# %%
from rfdetr.detr import RFDETRSegSmall
model = RFDETRSegSmall()
model.optimize_for_inference()
# predict accepts a file path, PIL Image, or RGB numpy array
detections = model.predict(image_path, threshold=0.3)
if not isinstance(detections, sv.Detections):
raise TypeError(f"Expected sv.Detections, got {type(detections).__name__}")
n_masks = 0 if detections.mask is None else len(detections.mask)
print(f"Detections: {len(detections)} (with masks: {n_masks})")
# %% [markdown]
# ### 3.1 COCO class names
#
# COCO has 90 numeric class IDs; map them to readable names for annotation.
# %%
# Subset of COCO class names (IDs 0-based after RF-DETR's remapping).
COCO_NAMES: dict[int, str] = {
0: "person",
1: "bicycle",
2: "car",
3: "motorcycle",
4: "airplane",
5: "bus",
6: "train",
7: "truck",
8: "boat",
24: "backpack",
25: "umbrella",
26: "handbag",
28: "suitcase",
56: "chair",
57: "couch",
58: "potted plant",
59: "bed",
60: "dining table",
62: "tv",
63: "laptop",
67: "cell phone",
72: "refrigerator",
74: "clock",
76: "scissors",
}
labels = []
assert detections.class_id is not None
for cid, conf in zip(
detections.class_id,
detections.confidence
if detections.confidence is not None
else [None] * len(detections),
):
name = COCO_NAMES.get(int(cid), f"cls_{cid}")
labels.append(f"{name} {conf:.2f}" if conf is not None else name)
# %% [markdown]
# ### 3.2 Visualise RF-DETR Seg output
# %%
PALETTE = sv.ColorPalette.DEFAULT
annotated = image_bgr.copy()
annotated = sv.MaskAnnotator(color=PALETTE, opacity=0.45).annotate(
annotated, detections
)
annotated = sv.BoxAnnotator(color=PALETTE, thickness=2).annotate(annotated, detections)
annotated = sv.LabelAnnotator(color=PALETTE, text_scale=0.5, text_thickness=1).annotate(
annotated, detections, labels=labels
)
plt.figure(figsize=(12, 7))
plt.imshow(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.title(f"RF-DETR Seg -- {len(detections)} instance(s)")
plt.tight_layout()
plt.show()
# %% [markdown]
# ## 4. CompactMask
#
# RF-DETR Seg returns one full-resolution binary mask per detected instance.
# On a 1280 x 720 image with 12 people that is:
#
# `12 x 720 x 1280 x 1 byte = 11 MB`
#
# Most of those pixels are background. The actual person silhouette fits in a
# tight bounding box. `sv.CompactMask` stores **only the bounding-box crop**,
# RLE-encoded:
#
# - A 200 x 100 person crop: `~2.5 KB` instead of `900 KB`
# - Drop-in replacement -- all annotators, filters, and `area` keep working
# %% [markdown]
# ### 4.1 Measure dense mask footprint
# %%
from typing import Any
dense_bytes: int = 0
dense_mask: "np.ndarray[Any, np.dtype[np.bool_]] | None" = None
assert detections.mask is not None and isinstance(detections.mask, np.ndarray)
dense_mask = detections.mask
dense_bytes = dense_mask.nbytes
n_inst = len(dense_mask)
print(f"Instances: {n_inst}")
print(f"Mask shape: {dense_mask.shape} (N x H x W, bool)")
print(f"Dense footprint: {dense_bytes / 1024:.1f} KB")
print(f" = {n_inst} masks x {H} x {W} x 1 byte")
# %% [markdown]
# ### 4.2 Convert to CompactMask
# %%
compact: "sv.CompactMask | None" = None
crop_bytes: int = 0
assert dense_mask is not None
compact = sv.CompactMask.from_dense(
masks=dense_mask,
xyxy=detections.xyxy,
image_shape=(H, W),
)
# Measure compact size via uncompressed crop booleans (upper bound; RLE < this).
crop_bytes = sum(compact.crop(i).nbytes for i in range(len(compact)))
print(f"Crop size (est.): {crop_bytes / 1024:.1f} KB (uncompressed crops)")
if crop_bytes > 0 and dense_bytes > 0:
ratio = dense_bytes / crop_bytes
print(f"Reduction factor: {ratio:.1f}x (before RLE compression)")
# Swap in CompactMask -- supervision uses it transparently from here on.
detections.mask = compact
print(f"\ndetections.mask type: {type(detections.mask).__name__}")
# %% [markdown]
# ### 4.3 Filtering by mask area
#
# `compact.area` returns the true pixel count of each instance mask.
# Filter out tiny detections (partial occlusions, image-edge artefacts).
# %%
large: sv.Detections = detections
large_labels: list[str] = labels
assert isinstance(detections.mask, sv.CompactMask)
areas = detections.mask.area
print(
f"Mask areas (px): min={areas.min():.0f} "
f"mean={areas.mean():.0f} max={areas.max():.0f}"
)
# Keep instances larger than 0.1% of the image.
min_area = 0.001 * H * W
keep_idx = np.where(areas > min_area)[0]
_filtered = detections[keep_idx]
if isinstance(_filtered, sv.Detections):
large = _filtered
large_labels = [labels[i] for i in keep_idx] if labels else []
print(f"\nInstances > {min_area:.0f} px: {len(large)}")
# %% [markdown]
# ### 4.4 Annotate with CompactMask
#
# Annotators call `.to_dense()` internally -- CompactMask is invisible to them.
# %%
assert isinstance(detections.mask, sv.CompactMask) and dense_bytes > 0
annotated_compact = image_bgr.copy()
annotated_compact = sv.MaskAnnotator(color=PALETTE, opacity=0.45).annotate(
annotated_compact, large
)
annotated_compact = sv.BoxAnnotator(color=PALETTE, thickness=2).annotate(
annotated_compact, large
)
annotated_compact = sv.LabelAnnotator(
color=PALETTE, text_scale=0.5, text_thickness=1
).annotate(annotated_compact, large, labels=large_labels)
plt.figure(figsize=(12, 7))
plt.imshow(cv2.cvtColor(annotated_compact, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.title(
f"CompactMask (filtered) -- {len(large)} instance(s) "
f"| {dense_bytes / 1024:.0f} KB dense -> {crop_bytes / 1024:.0f} KB crops"
)
plt.tight_layout()
plt.show()
# %% [markdown]
# ### 4.5 Per-instance crop
#
# `compact.crop(i)` decodes only the bounding-box crop for instance `i` as a
# `(H_crop, W_crop)` bool array -- no full mask materialised.
# %%
assert isinstance(detections.mask, sv.CompactMask) and len(detections) > 0
crop = detections.mask.crop(0)
bbox = detections.mask.bbox_xyxy[0].astype(int)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].imshow(image_rgb[bbox[1] : bbox[3], bbox[0] : bbox[2]])
axes[0].set_title("Image crop (instance 0)")
axes[0].axis("off")
axes[1].imshow(crop, cmap="gray")
axes[1].set_title(f"Mask crop ({crop.shape[1]} x {crop.shape[0]} px)")
axes[1].axis("off")
plt.tight_layout()
plt.show()
full_px = H * W
crop_kb = crop.nbytes / 1024
print(f"Full-res mask slot: {H} x {W} = {full_px / 1024:.0f} KB")
print(f"Compact crop: {crop.shape[0]} x {crop.shape[1]} = {crop_kb:.1f} KB")
# %% [markdown]
# ## 5. SAM3
#
# `sv.Detections.from_sam3()` is the other headline in 0.28.0.
# SAM3 segments objects by free-text prompts -- `"person"`, `"bag"`, any phrase.
# supervision parses both the PCS and PVS response formats into a standard
# `sv.Detections`, with `class_id` set to the prompt index.
#
# This section runs only when `ROBOFLOW_API_KEY` is available.
# %%
import base64
import os
from typing import Optional
import requests
try:
from google.colab import userdata # type: ignore[import, unused-ignore]
ROBOFLOW_API_KEY: str = userdata.get("ROBOFLOW_API_KEY") or ""
except Exception:
ROBOFLOW_API_KEY = os.environ.get("ROBOFLOW_API_KEY", "")
PROMPTS = ["person", "bag"]
sam3_detections: Optional[sv.Detections] = None
assert ROBOFLOW_API_KEY
with open(image_path, "rb") as _f:
_img_b64 = base64.b64encode(_f.read()).decode("utf-8")
_response = requests.post(
f"https://api.roboflow.com/inferenceproxy/seg-preview?api_key={ROBOFLOW_API_KEY}",
json={
"image": {"type": "base64", "value": _img_b64},
"prompts": [{"type": "text", "text": p} for p in PROMPTS],
"output_prob_thresh": 0.3,
},
headers={"Content-Type": "application/json"},
timeout=60,
)
_response.raise_for_status()
sam3_result: dict[str, Any] = _response.json()
sam3_detections = sv.Detections.from_sam3(sam3_result=sam3_result, resolution_wh=(W, H))
print(f"SAM3 detections: {len(sam3_detections)}")
if sam3_detections.class_id is not None:
for idx, prompt in enumerate(PROMPTS):
count = int((sam3_detections.class_id == idx).sum())
print(f" [{idx}] '{prompt}': {count} instance(s)")
# %%
assert sam3_detections is not None and len(sam3_detections) > 0
sam3_labels = (
[PROMPTS[c] for c in sam3_detections.class_id]
if sam3_detections.class_id is not None
else []
)
SAM3_PALETTE = sv.ColorPalette.from_hex(["#ff6b6b", "#4ecdc4"])
annotated_sam3 = image_bgr.copy()
annotated_sam3 = sv.MaskAnnotator(color=SAM3_PALETTE, opacity=0.45).annotate(
annotated_sam3, sam3_detections
)
annotated_sam3 = sv.BoxAnnotator(color=SAM3_PALETTE, thickness=2).annotate(
annotated_sam3, sam3_detections
)
annotated_sam3 = sv.LabelAnnotator(
color=SAM3_PALETTE, text_scale=0.5, text_thickness=1
).annotate(annotated_sam3, sam3_detections, labels=sam3_labels)
plt.figure(figsize=(12, 7))
plt.imshow(cv2.cvtColor(annotated_sam3, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.title(f"SAM3 -- from_sam3() -- {len(sam3_detections)} instance(s)")
plt.tight_layout()
plt.show()
# %% [markdown]
# ## 6. Other notable changes in 0.28.0
#
# ### `VideoInfo.fps` is now `float`
#
# NTSC frame rates (23.976, 29.97, 59.94) were silently truncated to `int`.
# Wrap with `int()` at call sites that require an integer.
# %%
import collections
from supervision.assets import VideoAssets
video_path = download_assets(VideoAssets.PEOPLE_WALKING)
info = sv.VideoInfo.from_video_path(video_path)
print(f"fps: {info.fps} ({type(info.fps).__name__}) -- was int before 0.28.0")
fps_int = int(info.fps)
buf: collections.deque[sv.Detections] = collections.deque(maxlen=fps_int)
trace = sv.TraceAnnotator(trace_length=fps_int)
print(f"deque maxlen: {buf.maxlen} (= int({info.fps}))")
# %% [markdown]
# ### `sv.ByteTrack` deprecated
#
# `sv.ByteTrack` still works in 0.28.0 and 0.29.0 but emits a
# `DeprecationWarning`. Migrate to `ByteTrackTracker` from the external
# [`trackers`](https://pypi.org/project/trackers/) package before 0.30.0.
#
# ```python
# # Before
# tracker = sv.ByteTrack()
# detections = tracker.update_with_detections(detections)
#
# # After (pip install trackers)
# from trackers import ByteTrackTracker
# tracker = ByteTrackTracker()
# detections = tracker.update(detections)
# ```
# %% [markdown]
# ## 7. Next steps
#
# - [`sv.CompactMask` docs](https://supervision.roboflow.com/develop/detection/compact_mask/)
# -- full API reference: `resize`, `merge`, `with_offset`
# - [`sv.Detections.from_sam3` docs](https://supervision.roboflow.com/develop/detection/core/)
# -- PCS and PVS format reference
# - [RF-DETR docs](https://github.com/roboflow/rf-detr)
# -- training, export, and deployment
# - [Full changelog](https://supervision.roboflow.com/develop/changelog/)
# -- every change in 0.28.0

View File

@ -4,7 +4,7 @@ requires = [ "setuptools>=61" ]
[project]
name = "supervision"
version = "0.28.0"
version = "0.31.0.dev0"
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
readme = "README.md"
keywords = [
@ -23,7 +23,7 @@ maintainers = [
authors = [
{ name = "Roboflow et al.", email = "develop@roboflow.com" },
]
requires-python = ">=3.9"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
@ -33,7 +33,6 @@ classifiers = [
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
@ -48,17 +47,20 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"av>=14.2",
"defusedxml>=0.7.1",
"matplotlib>=3.6",
"numpy>=1.21.2",
"opencv-python>=4.5.5.64",
"pillow>=9.4",
"pydeprecate>=0.7,<0.8",
"pydeprecate>=0.9,<0.12",
"pyyaml>=5.3",
"requests>=2.26",
"scipy>=1.10",
"tqdm>=4.62.3"
]
optional-dependencies.geotiff = [
"rasterio>=1.3", # 1.3 introduced stable window-read API and CRS.is_projected
]
optional-dependencies.metrics = [
"pandas>=2",
]
@ -74,34 +76,36 @@ dev = [
"nbconvert>=7.14.2",
"notebook>=6.5.3,<8",
"pre-commit>=3.8",
"pytest>=7.2.2,<9",
"pytest>=7.2.2,<10",
"pytest-cov>=4,<8",
"scikit-learn>=1.7",
"tox>=4.11.4",
"types-tqdm",
]
docs = [
"mike>=2",
"mkdocs-git-committers-plugin-2>=2.4.1; python_version>='3.9' and python_version<'4'",
"mkdocs-git-committers-plugin-2>=2.4.1; python_version>='3.10' and python_version<'4'",
"mkdocs-git-revision-date-localized-plugin>=1.2.4",
"mkdocs-jupyter>=0.24.3",
"mkdocs-material[imaging]>=9.7",
"mkdocstrings>=0.25.2,<0.31",
"mkdocstrings-python>=1.10.9,<2", # todo: breaking changes in 2.x
"mkdocstrings>=1,<1.1",
"mkdocstrings-python>=2,<3",
]
build = [
"build>=0.10,<1.5",
"build>=1,<1.6",
"twine>=5.1.1,<7",
"wheel>=0.40,<0.48",
]
[tool.setuptools]
include-package-data = false
package-data.supervision = [ "py.typed" ]
packages.find.where = [ "src" ]
packages.find.include = [ "supervision*" ]
include-package-data = false
package-data.supervision = [ "py.typed" ]
# exclude = [ "docs*", "tests*", "examples*" ]
[tool.ruff]
target-version = "py39"
target-version = "py310"
line-length = 88
indent-width = 4
# Exclude a variety of commonly ignored directories.
@ -163,6 +167,7 @@ lint.per-file-ignores."src/**" = [
]
lint.per-file-ignores."tests/**" = [
"S101", # Use of `assert` detected
"S603", # `subprocess` call: subprocess with hardcoded args in test utilities is safe
]
lint.unfixable = []
# Allow unused variables when underscore-prefixed.
@ -178,35 +183,33 @@ lint.pydocstyle.convention = "google"
lint.pylint.max-args = 20
[tool.codespell]
ignore-words-list = "STrack,sTrack,strack"
skip = "*.ipynb"
count = true
quiet-level = 3
ignore-words-list = "STrack,sTrack,strack"
[tool.mypy]
python_version = "3.9"
ignore_missing_imports = false
explicit_package_bases = true
strict = true
mypy_path = "src"
explicit_package_bases = true
ignore_missing_imports = false
python_version = "3.10"
warn_unused_ignores = true
strict = true
overrides = [
# exclude = [
# "docs",
# "test",
# "examples",
# "setup.py",
# ]
{ module = [
"tests.*",
"examples.*",
], ignore_errors = true },
{ module = [ "examples.*", "tests.*" ], ignore_errors = true },
{ module = [ "supervision._cv2" ], warn_unused_ignores = false },
]
[tool.pytest]
ini_options.testpaths = [ "src", "tests" ]
ini_options.norecursedirs = [ ".git", ".venv", "build", "dist", "docs", "examples", "notebooks" ]
ini_options.addopts = [
"--doctest-modules",
"--color=yes",
]
ini_options.filterwarnings = [
"error::DeprecationWarning",
]
ini_options.doctest_optionflags = "ELLIPSIS NORMALIZE_WHITESPACE"
[tool.autoflake]

View File

@ -1,24 +0,0 @@
# Release Process
This doc outlines how supervision is released into production.
It assumes you already have the code changes, as well as a draft of the release notes.
1. Make sure you have all required changes were merged into `develop`.
2. Create and merge a PR, merging `develop` into `main`, containing:
- A commit that updates the project version in `pyproject.toml`.
- All changes made during the release.
3. Tag the commit with the new supervision version.
- make sure to pull from `main` !
- Verify that the latest merge commits exists. `git log`.
- Run `git tag x.y.z`, with your version
- Check with `git log`.
- Run `git push origin --tags`
- Upon pushing the Github release, the [PyPi](https://pypi.org/project/supervision/) should update to the new version. Check this!
4. Open and merge a PR, merging `main` into `develop`.
5. Update the docs by running the [Supervision Release Documentation Workflow 📚](https://github.com/roboflow/supervision/actions/workflows/publish-release-docs.yml) workflow from GitHub.
- Select the `main` branch from the dropdown.
6. Create a release on GitHub.
- Go to releases
- Assign the release notes to the tag created in step 3.
- Publish the release.

View File

@ -1,4 +1,5 @@
import importlib.metadata as importlib_metadata
from typing import TYPE_CHECKING, Any
try:
# This will read version from pyproject.toml
@ -52,7 +53,10 @@ from supervision.detection.line_zone import (
LineZoneAnnotatorMulticlass,
)
from supervision.detection.tools.csv_sink import CSVSink
from supervision.detection.tools.inference_slicer import InferenceSlicer
from supervision.detection.tools.inference_slicer import (
InferenceSlicer,
WindowedRasterDataset,
)
from supervision.detection.tools.json_sink import JSONSink
from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator
from supervision.detection.tools.smoother import DetectionsSmoother
@ -62,6 +66,7 @@ from supervision.detection.utils.boxes import (
move_boxes,
pad_boxes,
scale_boxes,
xyxyxyxy_to_xyxy,
)
from supervision.detection.utils.converters import (
is_compressed_rle,
@ -86,16 +91,21 @@ from supervision.detection.utils.iou_and_nms import (
box_iou_batch_with_jaccard,
box_non_max_merge,
box_non_max_suppression,
box_soft_non_max_suppression,
mask_iou_batch,
mask_non_max_merge,
mask_non_max_suppression,
mask_soft_non_max_suppression,
oriented_box_iou_batch,
oriented_box_non_max_merge,
oriented_box_non_max_suppression,
)
from supervision.detection.utils.masks import (
calculate_masks_centroids,
contains_holes,
contains_multiple_segments,
filter_segments_by_distance,
mask_to_roi,
move_masks,
)
from supervision.detection.utils.polygons import (
@ -121,11 +131,14 @@ from supervision.geometry.utils import get_polygon_center
from supervision.key_points.annotators import (
EdgeAnnotator,
VertexAnnotator,
VertexEllipseAnnotator,
VertexEllipseAreaAnnotator,
VertexEllipseHaloAnnotator,
VertexEllipseOutlineAnnotator,
VertexLabelAnnotator,
)
from supervision.key_points.core import KeyPoints
from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision
from supervision.tracker.byte_tracker.core import ByteTrack
from supervision.utils.conversion import cv2_to_pillow, pillow_to_cv2
from supervision.utils.file import list_files_with_extensions
from supervision.utils.image import (
@ -134,11 +147,13 @@ from supervision.utils.image import (
get_image_resolution_wh,
grayscale_image,
letterbox_image,
load_image_from_url,
overlay_image,
resize_image,
scale_image,
tint_image,
)
from supervision.utils.image_window import ImageWindow
from supervision.utils.notebook import plot_image, plot_images_grid
from supervision.utils.video import (
FPSMonitor,
@ -148,8 +163,12 @@ from supervision.utils.video import (
process_video,
)
if TYPE_CHECKING:
from supervision.tracker.byte_tracker.core import ByteTrack
__all__ = [
"LMM",
"VLM",
"BackgroundOverlayAnnotator",
"BaseDataset",
"BlurAnnotator",
@ -179,6 +198,7 @@ __all__ = [
"HeatMapAnnotator",
"IconAnnotator",
"ImageSink",
"ImageWindow",
"InferenceSlicer",
"JSONSink",
"KeyPoints",
@ -204,15 +224,21 @@ __all__ = [
"TraceAnnotator",
"TriangleAnnotator",
"VertexAnnotator",
"VertexEllipseAnnotator",
"VertexEllipseAreaAnnotator",
"VertexEllipseHaloAnnotator",
"VertexEllipseOutlineAnnotator",
"VertexLabelAnnotator",
"VideoInfo",
"VideoSink",
"WindowedRasterDataset",
"approximate_polygon",
"box_iou",
"box_iou_batch",
"box_iou_batch_with_jaccard",
"box_non_max_merge",
"box_non_max_suppression",
"box_soft_non_max_suppression",
"calculate_masks_centroids",
"calculate_optimal_line_thickness",
"calculate_optimal_text_scale",
@ -221,6 +247,7 @@ __all__ = [
"contains_multiple_segments",
"crop_image",
"cv2_to_pillow",
"denormalize_boxes",
"draw_filled_polygon",
"draw_filled_rectangle",
"draw_image",
@ -242,15 +269,20 @@ __all__ = [
"is_valid_hex",
"letterbox_image",
"list_files_with_extensions",
"load_image_from_url",
"mask_iou_batch",
"mask_non_max_merge",
"mask_non_max_suppression",
"mask_soft_non_max_suppression",
"mask_to_polygons",
"mask_to_rle",
"mask_to_roi",
"mask_to_xyxy",
"move_boxes",
"move_masks",
"oriented_box_iou_batch",
"oriented_box_non_max_merge",
"oriented_box_non_max_suppression",
"overlay_image",
"pad_boxes",
"pillow_to_cv2",
@ -271,4 +303,15 @@ __all__ = [
"xyxy_to_polygons",
"xyxy_to_xcycarh",
"xyxy_to_xywh",
"xyxyxyxy_to_xyxy",
]
def __getattr__(name: str) -> Any:
"""Lazily resolve deprecated compatibility exports."""
if name == "ByteTrack":
from supervision.tracker.byte_tracker.core import ByteTrack as byte_track
globals()[name] = byte_track
return byte_track
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@ -0,0 +1,302 @@
"""Private OpenCV compatibility surface used by Supervision."""
from __future__ import annotations
import warnings
from typing import Any
import numpy.typing as npt
from supervision._cv2._color import _cvt_color, _merge, _split
from supervision._cv2._common import BackendUnavailableError
from supervision._cv2._components import (
_connected_components,
_connected_components_with_stats,
)
from supervision._cv2._contours import _find_contours
from supervision._cv2._drawing import (
_circle,
_draw_contours,
_ellipse,
_fill_poly,
_line,
_polylines,
_rectangle,
)
from supervision._cv2._geometry import (
_approx_poly_dp,
_contour_area,
_intersect_convex_convex,
)
from supervision._cv2._image import (
_add_weighted,
_convert_scale_abs,
_copy_make_border,
_flip,
_imdecode,
_imencode,
_imread,
_imwrite,
_mean,
_resize,
)
from supervision._cv2._text import _get_text_size, _put_text
from supervision._cv2._transform import _blur
from supervision._cv2._video import (
_video_writer_fourcc,
_VideoCapture,
_VideoWriter,
)
from supervision._cv2.constants import (
_BORDER_CONSTANT,
_CAP_PROP_FPS,
_CAP_PROP_FRAME_COUNT,
_CAP_PROP_FRAME_HEIGHT,
_CAP_PROP_FRAME_WIDTH,
_CAP_PROP_POS_FRAMES,
_CC_STAT_AREA,
_CHAIN_APPROX_SIMPLE,
_COLOR_BGR2GRAY,
_COLOR_BGR2RGB,
_COLOR_GRAY2BGR,
_COLOR_HSV2BGR,
_COLOR_RGB2BGR,
_FONT_HERSHEY_COMPLEX,
_FONT_HERSHEY_COMPLEX_SMALL,
_FONT_HERSHEY_DUPLEX,
_FONT_HERSHEY_PLAIN,
_FONT_HERSHEY_SCRIPT_COMPLEX,
_FONT_HERSHEY_SCRIPT_SIMPLEX,
_FONT_HERSHEY_SIMPLEX,
_FONT_HERSHEY_TRIPLEX,
_FONT_ITALIC,
_IMREAD_COLOR,
_IMREAD_UNCHANGED,
_INTER_LINEAR,
_INTER_NEAREST,
_LINE_4,
_LINE_8,
_LINE_AA,
_RETR_TREE,
)
try:
import cv2
except (ImportError, OSError):
_IS_CV2_AVAILABLE = False
else:
_IS_CV2_AVAILABLE = True
if _IS_CV2_AVAILABLE:
from cv2 import ( # type: ignore[attr-defined]
BORDER_CONSTANT,
CAP_PROP_FPS,
CAP_PROP_FRAME_COUNT,
CAP_PROP_FRAME_HEIGHT,
CAP_PROP_FRAME_WIDTH,
CAP_PROP_POS_FRAMES,
CC_STAT_AREA,
COLOR_BGR2GRAY,
COLOR_BGR2RGB,
COLOR_GRAY2BGR,
COLOR_HSV2BGR,
COLOR_RGB2BGR,
FONT_HERSHEY_COMPLEX,
FONT_HERSHEY_COMPLEX_SMALL,
FONT_HERSHEY_DUPLEX,
FONT_HERSHEY_PLAIN,
FONT_HERSHEY_SCRIPT_COMPLEX,
FONT_HERSHEY_SCRIPT_SIMPLEX,
FONT_HERSHEY_SIMPLEX,
FONT_HERSHEY_TRIPLEX,
FONT_ITALIC,
IMREAD_COLOR,
IMREAD_UNCHANGED,
INTER_LINEAR,
INTER_NEAREST,
LINE_4,
LINE_8,
LINE_AA,
VideoCapture,
VideoWriter,
VideoWriter_fourcc, # type: ignore[attr-defined]
addWeighted,
approxPolyDP,
blur,
circle,
connectedComponents,
connectedComponentsWithStats,
contourArea,
convertScaleAbs,
copyMakeBorder,
cvtColor,
drawContours,
ellipse,
fillPoly,
flip,
getTextSize,
imdecode,
imencode,
imread,
imwrite,
intersectConvexConvex,
line,
mean,
merge,
polylines,
putText,
rectangle,
resize,
split,
)
from cv2 import (
findContours as _find_contours_impl,
)
BACKEND_NAME = "opencv"
else:
BACKEND_NAME = "fallback"
warnings.warn(
"OpenCV (`opencv-python`) is not installed; supervision is using its "
"pure NumPy fallback backend instead. Some operations may be slower "
"or behave slightly differently. Install `opencv-python` for full "
"performance and compatibility.",
stacklevel=2,
)
BORDER_CONSTANT = _BORDER_CONSTANT
CAP_PROP_FPS = _CAP_PROP_FPS
CAP_PROP_FRAME_COUNT = _CAP_PROP_FRAME_COUNT
CAP_PROP_FRAME_HEIGHT = _CAP_PROP_FRAME_HEIGHT
CAP_PROP_FRAME_WIDTH = _CAP_PROP_FRAME_WIDTH
CAP_PROP_POS_FRAMES = _CAP_PROP_POS_FRAMES
CC_STAT_AREA = _CC_STAT_AREA
COLOR_BGR2GRAY = _COLOR_BGR2GRAY
COLOR_BGR2RGB = _COLOR_BGR2RGB
COLOR_GRAY2BGR = _COLOR_GRAY2BGR
COLOR_HSV2BGR = _COLOR_HSV2BGR
COLOR_RGB2BGR = _COLOR_RGB2BGR
FONT_HERSHEY_COMPLEX = _FONT_HERSHEY_COMPLEX
FONT_HERSHEY_COMPLEX_SMALL = _FONT_HERSHEY_COMPLEX_SMALL
FONT_HERSHEY_DUPLEX = _FONT_HERSHEY_DUPLEX
FONT_HERSHEY_PLAIN = _FONT_HERSHEY_PLAIN
FONT_HERSHEY_SCRIPT_COMPLEX = _FONT_HERSHEY_SCRIPT_COMPLEX
FONT_HERSHEY_SCRIPT_SIMPLEX = _FONT_HERSHEY_SCRIPT_SIMPLEX
FONT_HERSHEY_SIMPLEX = _FONT_HERSHEY_SIMPLEX
FONT_HERSHEY_TRIPLEX = _FONT_HERSHEY_TRIPLEX
FONT_ITALIC = _FONT_ITALIC
IMREAD_COLOR = _IMREAD_COLOR
IMREAD_UNCHANGED = _IMREAD_UNCHANGED
INTER_LINEAR = _INTER_LINEAR
INTER_NEAREST = _INTER_NEAREST
LINE_4 = _LINE_4
LINE_8 = _LINE_8
LINE_AA = _LINE_AA
# Fallback implementations when cv2 is not available. Suppress type errors because
# fallback types differ from cv2 types, but are functionally equivalent.
VideoCapture = _VideoCapture # type: ignore[assignment,misc]
VideoWriter = _VideoWriter # type: ignore[assignment,misc]
VideoWriter_fourcc = _video_writer_fourcc # type: ignore[assignment]
addWeighted = _add_weighted # type: ignore[assignment]
approxPolyDP = _approx_poly_dp # type: ignore[assignment]
blur = _blur # type: ignore[assignment]
circle = _circle # type: ignore[assignment]
connectedComponents = _connected_components # type: ignore[assignment]
connectedComponentsWithStats = _connected_components_with_stats # type: ignore[assignment]
contourArea = _contour_area # type: ignore[assignment]
convertScaleAbs = _convert_scale_abs # type: ignore[assignment]
copyMakeBorder = _copy_make_border # type: ignore[assignment]
cvtColor = _cvt_color # type: ignore[assignment]
drawContours = _draw_contours # type: ignore[assignment]
ellipse = _ellipse # type: ignore[assignment]
fillPoly = _fill_poly # type: ignore[assignment]
_find_contours_impl = _find_contours
flip = _flip # type: ignore[assignment]
getTextSize = _get_text_size # type: ignore[assignment]
imdecode = _imdecode # type: ignore[assignment]
imencode = _imencode # type: ignore[assignment]
imread = _imread # type: ignore[assignment]
imwrite = _imwrite # type: ignore[assignment]
intersectConvexConvex = _intersect_convex_convex # type: ignore[assignment]
line = _line # type: ignore[assignment]
mean = _mean # type: ignore[assignment]
merge = _merge # type: ignore[assignment]
polylines = _polylines # type: ignore[assignment]
putText = _put_text # type: ignore[assignment]
rectangle = _rectangle # type: ignore[assignment]
resize = _resize # type: ignore[assignment]
split = _split # type: ignore[assignment]
def find_contours(image: npt.NDArray[Any]) -> list[npt.NDArray[Any]]:
"""Return the contour geometry required by mask-to-polygon conversion."""
contours, _ = _find_contours_impl(image, _RETR_TREE, _CHAIN_APPROX_SIMPLE)
return list(contours)
__all__ = [
"BACKEND_NAME",
"BORDER_CONSTANT",
"CAP_PROP_FPS",
"CAP_PROP_FRAME_COUNT",
"CAP_PROP_FRAME_HEIGHT",
"CAP_PROP_FRAME_WIDTH",
"CAP_PROP_POS_FRAMES",
"CC_STAT_AREA",
"COLOR_BGR2GRAY",
"COLOR_BGR2RGB",
"COLOR_GRAY2BGR",
"COLOR_HSV2BGR",
"COLOR_RGB2BGR",
"FONT_HERSHEY_COMPLEX",
"FONT_HERSHEY_COMPLEX_SMALL",
"FONT_HERSHEY_DUPLEX",
"FONT_HERSHEY_PLAIN",
"FONT_HERSHEY_SCRIPT_COMPLEX",
"FONT_HERSHEY_SCRIPT_SIMPLEX",
"FONT_HERSHEY_SIMPLEX",
"FONT_HERSHEY_TRIPLEX",
"FONT_ITALIC",
"IMREAD_COLOR",
"IMREAD_UNCHANGED",
"INTER_LINEAR",
"INTER_NEAREST",
"LINE_4",
"LINE_8",
"LINE_AA",
"BackendUnavailableError",
"VideoCapture",
"VideoWriter",
"VideoWriter_fourcc",
"addWeighted",
"approxPolyDP",
"blur",
"circle",
"connectedComponents",
"connectedComponentsWithStats",
"contourArea",
"convertScaleAbs",
"copyMakeBorder",
"cvtColor",
"drawContours",
"ellipse",
"fillPoly",
"find_contours",
"flip",
"getTextSize",
"imdecode",
"imencode",
"imread",
"imwrite",
"intersectConvexConvex",
"line",
"mean",
"merge",
"polylines",
"putText",
"rectangle",
"resize",
"split",
]

View File

@ -0,0 +1,94 @@
"""Private color and channel-operation fallbacks."""
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
import numpy as np
import numpy.typing as npt
from supervision._cv2._common import _cast_array_like_opencv
from supervision._cv2.constants import (
_COLOR_BGR2GRAY,
_COLOR_BGR2RGB,
_COLOR_GRAY2BGR,
_COLOR_HSV2BGR,
_COLOR_RGB2BGR,
)
def _cvt_color(image: npt.NDArray[Any], code: int) -> npt.NDArray[Any]:
"""Convert the BGR, RGB, grayscale, and 8-bit HSV formats used by Supervision."""
if code in (_COLOR_BGR2RGB, _COLOR_RGB2BGR):
if image.ndim != 3 or image.shape[2] != 3:
raise ValueError("BGR/RGB conversion requires a three-channel image")
return np.ascontiguousarray(image[..., ::-1])
if code == _COLOR_GRAY2BGR:
if image.ndim != 2:
raise ValueError("GRAY2BGR conversion requires a two-dimensional image")
return np.repeat(image[..., np.newaxis], 3, axis=2)
if code == _COLOR_BGR2GRAY:
if image.ndim != 3 or image.shape[2] != 3:
raise ValueError("BGR2GRAY conversion requires a three-channel image")
if image.dtype == np.uint8:
values = image.astype(np.uint32)
weighted = (
values[..., 0] * 3735
+ values[..., 1] * 19235
+ values[..., 2] * 9798
+ (1 << 14)
) >> 15
return weighted.astype(np.uint8)
float_values = (
image[..., 0].astype(np.float64) * 0.114
+ image[..., 1].astype(np.float64) * 0.587
+ image[..., 2].astype(np.float64) * 0.299
)
return _cast_array_like_opencv(float_values, image.dtype)
if code == _COLOR_HSV2BGR:
if image.ndim != 3 or image.shape[2] != 3:
raise ValueError("HSV2BGR conversion requires a three-channel image")
return _hsv_to_bgr(image)
raise ValueError(f"Unsupported color conversion code: {code}")
def _hsv_to_bgr(image: npt.NDArray[Any]) -> npt.NDArray[Any]:
"""Convert OpenCV's 8-bit HSV representation to BGR."""
values = image.astype(np.float64)
hue = values[..., 0] / 30.0
saturation = values[..., 1] / 255.0
value = values[..., 2] / 255.0
chroma = value * saturation
sector_index = np.floor(hue).astype(np.int64) % 6
sector = hue - np.floor(hue)
x = chroma * (1 - np.abs(((sector_index + sector) % 2) - 1))
match = value - chroma
zeros = np.zeros_like(chroma)
red = np.choose(sector_index, (chroma, x, zeros, zeros, x, chroma))
green = np.choose(sector_index, (x, chroma, chroma, x, zeros, zeros))
blue = np.choose(sector_index, (zeros, zeros, x, chroma, chroma, x))
bgr = np.stack((blue + match, green + match, red + match), axis=-1) * 255
return _cast_array_like_opencv(bgr, image.dtype)
def _split(image: npt.NDArray[Any]) -> tuple[npt.NDArray[Any], ...]:
"""Split an image into contiguous single-channel arrays."""
if image.ndim == 2:
return (np.ascontiguousarray(image),)
return tuple(
np.ascontiguousarray(image[..., index]) for index in range(image.shape[2])
)
def _merge(channels: Sequence[npt.NDArray[Any]]) -> npt.NDArray[Any]:
"""Merge single-channel arrays along their final axis."""
if not channels:
raise ValueError("At least one channel is required")
return np.ascontiguousarray(np.stack(channels, axis=-1))

Some files were not shown because too many files have changed in this diff Show More