Compare commits

...
227 Commits
Author SHA1 Message Date
LinasKo 58e27f44f1 Merge pull request #1573 from roboflow/develop
supervision-0.24.0 release
2024-10-04 22:57:42 +03:00
LinasKo 953ecba283 Bump supervision to 0.24.0 2024-10-04 22:55:27 +03:00
LinasKo e1e00ffc52 pathch: minor typo in changelog 2024-10-04 22:47:27 +03:00
LinasKo b01b321eca Merge pull request #1572 from roboflow/feat/changelog-0.24.0
Feat/changelog 0.24.0
2024-10-04 22:39:33 +03:00
LinasKo c9dfdbe301 Fixed: Admonitions formatting reformatted incorrectly
* Manually saving docs files elsewhere reformats admonitions there
2024-10-04 22:34:13 +03:00
LinasKo 38a1d3ad90 Docs: missing Return docstring for LineZoneAnnotatorMulticlass.annotate 2024-10-04 22:25:44 +03:00
LinasKo 68d668a1f2 Update "New" tags in documentation 2024-10-04 22:22:10 +03:00
LinasKo 8f61898964 Point changelog links to 0.24.0 2024-10-04 22:13:26 +03:00
LinasKo 02ed59b27b Add changelog, deprecations 2024-10-04 22:08:56 +03:00
LinasKo 72d0b9b914 Merge pull request #1571 from roboflow/revert-1570-package/git-commiter-plugin-downgrade
Revert "fix: 🔥 downgrade mkdocs-git-committers-plugin-2 to version 2.3.0"
2024-10-04 21:37:33 +03:00
LinasKo db0a078280 Bugfix: missing LineZoneAnnotatorMulticlass.annotate docs 2024-10-04 21:29:15 +03:00
Onuralp SEZER 63f5bfd4f3 chore: poetry update and start version 2.4.1 for prevent broken version
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-10-04 21:24:23 +03:00
Onuralp SEZER 61ababd0b5 Revert "fix: 🔥 downgrade mkdocs-git-committers-plugin-2 to version 2.3.0" 2024-10-04 21:17:35 +03:00
LinasKo f8b6d3cf9a Merge pull request #1570 from roboflow/package/git-commiter-plugin-downgrade
fix: 🔥 downgrade mkdocs-git-committers-plugin-2 to version 2.3.0
2024-10-04 19:30:12 +03:00
Onuralp SEZER 73ed766e97 fix: 🔥 downgrade mkdocs-git-committers-plugin-2 to version 2.3.0
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-10-04 19:25:48 +03:00
Onuralp SEZER d3d1bfc86c Merge pull request #1568 from roboflow/dependabot/pip/tox-4.21.2
⬆️ Bump tox from 4.21.1 to 4.21.2
2024-10-04 19:09:25 +03:00
dependabot[bot] 55d7c8daf3 ⬆️ Bump tox from 4.21.1 to 4.21.2
Bumps [tox](https://github.com/tox-dev/tox) from 4.21.1 to 4.21.2.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.21.1...4.21.2)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-04 00:29:50 +00:00
LinasKo 66067cc0ac Remove deprecated frame_resolution_wh from PolygonZone 2024-10-04 00:53:28 +03:00
LinasKo 448a0ab7c0 Merge pull request #1565 from josephofiowa/develop
Update annotators.md
2024-10-03 23:31:38 +03:00
Joseph Nelson f207eaca31 Update annotators.md
being concise is a virtue
2024-10-03 13:29:14 -07:00
Joseph Nelson 51e2f9e462 Update annotators.md
included description of what annotators are; clarified how to use the visualization widget
2024-10-03 11:45:41 -07:00
LinasKo 8755bc8391 Merge pull request #1563 from roboflow/hacktoberfest-2024
Update hacktoberfest banner
2024-10-03 15:33:00 +03:00
LinasKo eb0e50a2f9 Update hacktoberfest banner 2024-10-03 15:30:20 +03:00
Onuralp SEZER d6aa72c0f2 Merge pull request #1561 from roboflow/dependabot/pip/tox-4.21.1
⬆️ Bump tox from 4.21.0 to 4.21.1
2024-10-03 13:04:48 +03:00
LinasKo 2be5008b3c Minors docs edit: Tell what classes can be annotated by the embedded annotator 2024-10-03 12:49:40 +03:00
LinasKo b09559a900 Merge pull request #1560 from roboflow/joao/fix/new-embedded-docs-layout
Moves embedded annotators widget to below the tabs and adds query params to display new layout
2024-10-03 12:34:30 +03:00
LinasKo ef89d8ceff Remove the Workflows introduction.
* Info is available upon mousing-over the '?' icon on the embed.
2024-10-03 12:22:42 +03:00
dependabot[bot] 0b931e11cd ⬆️ Bump tox from 4.21.0 to 4.21.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.21.0 to 4.21.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.21.0...4.21.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-03 00:36:54 +00:00
João 0bc52bf6fe moves embedded widget to below the tabs and passes necessary query params to render new layout 2024-10-02 21:09:58 -03:00
Onuralp SEZER 5af3eab3d0 Merge pull request #1559 from roboflow/dependabot/pip/tox-4.21.0
⬆️ Bump tox from 4.20.0 to 4.21.0
2024-10-02 03:41:11 +03:00
dependabot[bot] 56c269a1ba ⬆️ Bump tox from 4.20.0 to 4.21.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.20.0 to 4.21.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.20.0...4.21.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-02 00:35:02 +00:00
LinasKo 52c9ec0def Merge pull request #1547 from tibeoh/fix/inference-slicer-overlap
fix: InferenceSlicer overlap_ratio_wh argument changed to None by default
2024-10-01 13:37:34 +03:00
LinasKo 892bd87b6c overlap_ratio_wh: remove deprecated param decorator
* We want users to set it to None manually, and the decorator would produce a warning every time.
2024-10-01 13:32:11 +03:00
LinasKo 23023e9764 Revert default overlap_ratio_wh value, update docs 2024-10-01 13:22:55 +03:00
LinasKo 3789f694c4 Merge pull request #1555 from roboflow/feat/line-zone-per-class-counting
Add per-class counting to LineZone, create LineZoneAnnotatorMulticlass
2024-10-01 12:23:45 +03:00
LinasKo 865cdd3bd1 Minor spelling change, code of conduct 2024-10-01 12:10:29 +03:00
LinasKo 121190be50 minor code refactor, if mot -> continue 2024-10-01 11:50:50 +03:00
pre-commit-ci[bot] f01126fabb fix(pre_commit): 🎨 auto format pre-commit hooks 2024-10-01 08:44:54 +00:00
Onuralp SEZER 125a858654 Apply Onuralps review suggestions
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-10-01 11:42:53 +03:00
Onuralp SEZER 1d4b61f20c Merge pull request #1557 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-10-01 05:57:01 +03:00
pre-commit-ci[bot] edda8dea0b chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/PyCQA/bandit: 1.7.9 → 1.7.10](https://github.com/PyCQA/bandit/compare/1.7.9...1.7.10)
- [github.com/astral-sh/ruff-pre-commit: v0.6.7 → v0.6.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.7...v0.6.8)
- [github.com/codespell-project/codespell: v2.2.6 → v2.3.0](https://github.com/codespell-project/codespell/compare/v2.2.6...v2.3.0)
2024-09-30 17:53:37 +00:00
Onuralp SEZER 3b95c1d8bf Merge pull request #1556 from roboflow/dependabot/pip/mkdocs-material-9.5.39
⬆️ Bump mkdocs-material from 9.5.38 to 9.5.39
2024-09-30 03:40:55 +03:00
dependabot[bot] 1d77868080 ⬆️ Bump mkdocs-material from 9.5.38 to 9.5.39
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.38 to 9.5.39.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.38...9.5.39)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 00:08:42 +00:00
LinasKo eebd57a731 Add per-class counting to LineZone, create LineZoneAnnotatorMulticlass 2024-09-29 12:47:07 +03:00
LinasKo 1860fdb0a4 Merge pull request #1542 from roboflow/ci/pypi-configs
ci: 👷 pypi username - password swap with token
2024-09-27 15:51:24 +03:00
Onuralp SEZER 31a3cca5d5 ci: 👷 trusted publisher configuration added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-27 15:49:55 +03:00
Onuralp SEZER b45bee0761 Merge pull request #1549 from roboflow/docs/mkdocs-cleanup
Resolve mkdocs issues
2024-09-27 15:23:58 +03:00
Onuralp SEZER 103669bf0f docs: 📝 minor adjustments on readme and changelog
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-27 15:21:30 +03:00
Onuralp SEZER 5b39c81035 Merge branch 'develop' into docs/mkdocs-cleanup 2024-09-27 14:42:11 +03:00
Onuralp SEZER 78f17cba2e chore: 🧹 clean up documentation and improve formatting
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-27 14:18:26 +03:00
LinasKo 36f0755767 Merge pull request #1533 from roboflow/demo-annotators-with-workflows
Update docs: adds basic workflow to try supervision annotators on annotators doc page
2024-09-27 13:15:04 +03:00
Onuralp SEZER 9ddbad2300 Merge pull request #1552 from roboflow/dependabot/pip/mkdocs-material-9.5.38
⬆️ Bump mkdocs-material from 9.5.37 to 9.5.38
2024-09-27 11:38:28 +03:00
Onuralp SEZER c3b9db0258 Merge pull request #1551 from roboflow/dependabot/pip/ruff-0.6.8
⬆️ Bump ruff from 0.6.7 to 0.6.8
2024-09-27 11:38:11 +03:00
LinasKo dec29200a9 Add repo of the day badge to readme 2024-09-27 11:29:16 +03:00
dependabot[bot] 20c5f8da3d ⬆️ Bump mkdocs-material from 9.5.37 to 9.5.38
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.37 to 9.5.38.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.37...9.5.38)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 01:01:49 +00:00
dependabot[bot] 39d0e65592 ⬆️ Bump ruff from 0.6.7 to 0.6.8
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.7 to 0.6.8.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.7...0.6.8)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 00:59:42 +00:00
Onuralp SEZER 58a9588a53 chore: 🧹 split mkdocstring and mkdocstring-python package
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-26 18:01:34 +03:00
LinasKo 9522ac9a27 Merge pull request #1550 from ethanwhite/feat/colorpalette-len
feat: add __len__ to ColorPalette
2024-09-26 16:56:39 +03:00
Ethan White 363aa0629e feat: add __len__ to ColorPalette
For downstream use being able to determine how many colors are in a
ColorPalette object can be useful. E.g., checking if a user has provided
a color palette that matches the number of labels. ColorPalette doesn't
currenly have a __len__ attribute meaning that `mypalette.colors` has
to be used for calculating lengths. This change adds __len__ to make
this more intutitive by allowing `len(mypalette)`.
2024-09-26 09:09:28 -04:00
LinasKo f499430aa3 Fix mkdocs autorefs depreacation warnings 2024-09-26 14:56:12 +03:00
LinasKo 06d1640700 Fix documentation issues 2024-09-26 13:55:06 +03:00
LinasKo d50c51fc52 Update supervision docs on PyPi, add Linas as maintainer 2024-09-26 13:18:10 +03:00
LinasKo e63611deb3 Merge pull request #854 from jcruz-ferreyra/lc/diagonal_support
LineZoneAnnotator: Align text to line counter in non-horizontal lines
2024-09-26 13:01:42 +03:00
LinasKo c81aec9d1b Docstrings update 2024-09-26 12:38:35 +03:00
LinasKo 4a95aaa2c4 Fix basic_label offset 2024-09-26 12:27:07 +03:00
Thibault fedc1a47e0 fix: InferenceSlicer overlap_ratio_wh argument changed to None by default
Changed the default value of the overlap_ratio_wh argument to None, as this parameter will be deprecated in version 0.27.0. This change allows a smoother transition by not forcing users to explicitly set it to None.

Additionally, added tests for validating the overlap arguments (overlap_wh and overlap_ratio_wh) to ensure proper error handling and usage. Also started implementing tests for the offset generation method to verify that slices are correctly calculated based on the overlap settings.
2024-09-26 11:15:16 +02:00
LinasKo db0d965e8d Fix: incorrect label rotation 2024-09-26 12:12:53 +03:00
Onuralp SEZER d4eb957201 Merge pull request #1544 from roboflow/dependabot/pip/mkdocs-material-9.5.37
⬆️ Bump mkdocs-material from 9.5.36 to 9.5.37
2024-09-26 05:26:23 +03:00
dependabot[bot] 4722459bce ⬆️ Bump mkdocs-material from 9.5.36 to 9.5.37
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.36 to 9.5.37.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.36...9.5.37)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-26 00:29:52 +00:00
LinasKo 156dbd5046 New docstring for LineZoneAnnotator 2024-09-26 00:02:39 +03:00
LinasKo ebd5e0661b Make sure text is always displayed upright 2024-09-25 23:49:19 +03:00
LinasKo d6d3213f62 Move methods around, rename some variables 2024-09-25 23:31:53 +03:00
LinasKo 7b22cfa741 Bugfix: basic label would always have background box 2024-09-25 23:28:28 +03:00
LinasKo 356a86a469 Ruff formatting 2024-09-25 23:18:40 +03:00
LinasKo c1444c6d14 Futher LineZone code tidy-up 2024-09-25 23:17:55 +03:00
pre-commit-ci[bot] 5f86dbe29b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-09-25 19:23:19 +00:00
LinasKo 6fbabeefff Make color hashable, use cache for label images 2024-09-25 22:22:30 +03:00
pre-commit-ci[bot] 60c5292781 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-09-25 19:10:55 +00:00
LinasKo f8b60eab87 Further simplification & speedup 2024-09-25 22:10:08 +03:00
LinasKo d88d8ed58b Factorization, simplifaction, speedup
* Sped up computation from 600 fps to 1700 fps (Mac)
* Previously ran at 2000 fps
2024-09-25 21:52:27 +03:00
LinasKo 9bbd81e6ec Link to Workflows in the first paragraph 2024-09-24 17:17:49 +03:00
LinasKo 93190b2bb6 Merge pull request #1502 from patel-zeel/feat/oriented_box_iou_batch
Add `oriented_box_iou_batch` function to `detection.utils`
2024-09-24 13:16:19 +03:00
LinasKo a29d9599d7 Fix OBB shape when computing object size 2024-09-24 13:04:59 +03:00
Onuralp SEZER 787bbdac96 ci: 👷 pypi username - password swap with token
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-24 13:03:46 +03:00
LinasKo 1b6fd01f7e Fix arrya shapes in OBB IoU docstring 2024-09-24 12:57:04 +03:00
Onuralp SEZER 65f606e197 Merge pull request #1541 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-09-23 20:55:41 +03:00
pre-commit-ci[bot] 0d86159de1 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.5 → v0.6.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.5...v0.6.7)
2024-09-23 17:49:28 +00:00
LinasKo c211453def Merge pull request #1515 from roboflow/detection/easyocr
feat: ✨  from_easyocr detection added
2024-09-23 14:33:48 +03:00
LinasKo 943a21412e fix regression: ncnn docs 2024-09-23 14:28:04 +03:00
pre-commit-ci[bot] f221019208 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-09-23 11:15:39 +00:00
LinasKo d01e76a9c7 easy_ocr: docstring explaining usage 2024-09-23 14:14:08 +03:00
LinasKo 5ea6d95fe0 Merge branch 'develop' into detection/easyocr 2024-09-23 14:03:40 +03:00
LinasKo 270b5f564f Update annotator errors, suggesting fixes 2024-09-23 13:53:29 +03:00
LinasKo 47a7f929a2 from_easy_ocr: cast results to float, remove class_id 2024-09-23 13:47:13 +03:00
LinasKo 130308c28b Merge pull request #1524 from roboflow/detection/ncnn
feat: ✨ initial ncnn object detection support added
2024-09-23 13:13:16 +03:00
LinasKo 504fc59b68 ncnn docstring change + sneaky from_transformers tidy-up 2024-09-23 13:11:13 +03:00
pre-commit-ci[bot] 856c72eb67 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-09-23 09:55:04 +00:00
Onuralp SEZER fef9f10c03 fix: 🐞 ncnn xywh and conf types corrected to float32
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-23 12:53:37 +03:00
Onuralp SEZER f8397bdce9 Merge pull request #1539 from roboflow/dependabot/pip/ruff-0.6.7
⬆️ Bump ruff from 0.6.5 to 0.6.7
2024-09-23 06:12:14 +03:00
Onuralp SEZER 5d9d7446b8 Merge pull request #1538 from roboflow/dependabot/pip/mkdocs-material-9.5.36
⬆️ Bump mkdocs-material from 9.5.35 to 9.5.36
2024-09-23 06:12:02 +03:00
dependabot[bot] e10caa2215 ⬆️ Bump ruff from 0.6.5 to 0.6.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.5 to 0.6.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.5...0.6.7)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 00:28:39 +00:00
dependabot[bot] 6551fd6c29 ⬆️ Bump mkdocs-material from 9.5.35 to 9.5.36
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.35 to 9.5.36.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.35...9.5.36)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 00:27:07 +00:00
LinasKo fa8378e50c Merge pull request #1521 from roboflow/feat/f1-metric
F1 metric
2024-09-20 19:12:14 +03:00
LinasKo 7de1f5855d Merge branch 'develop' into feat/f1-metric 2024-09-20 19:06:39 +03:00
João d208250123 reduces border-radius 2024-09-20 11:12:13 -03:00
João 2759beb293 better copy 2024-09-20 11:11:01 -03:00
João 9a56513371 adds basic workflow to try supervision annotators on annotators doc page 2024-09-20 11:02:30 -03:00
LinasKo 7f85963314 Add F1 to docs, rewrite the docstrings 2024-09-20 14:50:46 +03:00
LinasKo 6713523e9b Disable alignment to line by default, move arguments around 2024-09-20 13:12:07 +03:00
LinasKo 3c79677039 Flip text if it is upside-down 2024-09-20 12:28:52 +03:00
LinasKo d85ceb0c23 Fix incorrect types 2024-09-20 11:18:24 +03:00
LinasKo 9d1549228f Merge branch 'develop' into lc/diagonal_support 2024-09-20 10:14:40 +03:00
Onuralp SEZER c94cfe2031 Merge pull request #1532 from roboflow/dependabot/pip/tox-4.20.0
⬆️ Bump tox from 4.19.0 to 4.20.0
2024-09-20 06:21:08 +03:00
dependabot[bot] dc07f13dec ⬆️ Bump tox from 4.19.0 to 4.20.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.19.0 to 4.20.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.19.0...4.20.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 00:27:14 +00:00
LinasKo fdda7254a3 Merge pull request #1527 from roboflow/feature/add-opacity-to-polygon-zone-annotator
Add opacity to PolygonZoneAnnotator
2024-09-19 22:39:19 +03:00
LinasKo 81ee4f1c2c Add draw_filled_polygon to docs, fix a few docstring types 2024-09-19 22:37:36 +03:00
LinasKo 85e053eae6 Merge branch 'develop' into feature/add-opacity-to-polygon-zone-annotator 2024-09-19 22:10:48 +03:00
Zeel B Patel 887bb7c2cb Merge branch 'roboflow:develop' into feat/oriented_box_iou_batch 2024-09-20 00:12:26 +05:30
LinasKo e3eeb5f2cc Merge pull request #1526 from roboflow/formatting/ruff-rules
refactor: ✨ ruff rules enabled and fixed and code refactor made
2024-09-19 21:18:09 +03:00
Onuralp SEZER a34f0e9597 refactor: ♻️ update Edges type from List to Tuple for better performance
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-19 20:38:43 +03:00
LinasKo 6acfdbf502 Add obb_iou_batch to __init__ and docs 2024-09-19 20:15:23 +03:00
Onuralp SEZER 1a870b9b1b fix: 🐞 empty image detection error corrected
refactor: ♻️  update text data field to CLASS_NAME_DATA_FIELD in config and detection modules to reduce complexity of datafield names

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-19 19:20:56 +03:00
LinasKo 784191a099 Merge pull request #1530 from roboflow/feature/pip-package-configuration
fix: 🐞 removal of opencv-python-headless package
2024-09-19 17:52:16 +03:00
Onuralp SEZER c9fa3d613f Merge branch 'develop' into feature/pip-package-configuration 2024-09-19 17:47:28 +03:00
Onuralp SEZER 4644f21340 docs: 📝 documentation adjusment for installations
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-19 17:42:37 +03:00
Onuralp SEZER 9be2fdf5c4 fix: 🐞 removal of opencv-python-headless package
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-19 17:02:42 +03:00
LinasKo c532758693 Merge pull request #1523 from roboflow/fix/fix-broken-links
Fix broken or outdated links
2024-09-19 16:37:11 +03:00
LinasKo 14fc6c2dca Merge pull request #1529 from roboflow/chore/black-config-cleanup
chore: 🧹 old black config removed
2024-09-19 16:34:35 +03:00
Onuralp SEZER ebdada92bd chore: 🧹 old black config removed
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-19 15:54:53 +03:00
Grzegorz Klimaszewski 7ad0bc361b Adjust docstrings 2024-09-19 12:08:12 +02:00
pre-commit-ci[bot] f307f981f6 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-09-19 10:05:06 +00:00
Grzegorz Klimaszewski 79858ad9d3 Move opacity handling to utils/draw; extend PolygonZoneAnnotator to draw filled polygon when opacity is greater than 0 2024-09-19 11:58:54 +02:00
Grzegorz Klimaszewski 1c30493ee0 Docstring 2024-09-19 10:54:04 +02:00
Grzegorz Klimaszewski 2961045177 Add opacity to PolygonZoneAnnotator 2024-09-19 10:49:30 +02:00
Onuralp SEZER 1f0c278c4e refactor: ✨ ruff rules enabled and fixed and code refactor made
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-19 10:35:30 +03:00
Onuralp SEZER 32d22efedc Merge pull request #1525 from roboflow/dependabot/pip/mkdocs-material-9.5.35
⬆️ Bump mkdocs-material from 9.5.34 to 9.5.35
2024-09-19 09:30:08 +03:00
dependabot[bot] 128358f183 ⬆️ Bump mkdocs-material from 9.5.34 to 9.5.35
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.34 to 9.5.35.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.34...9.5.35)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-19 00:31:40 +00:00
Onuralp SEZER ef2b604df6 Merge pull request #1483 from ediardo/docs/cookbook-sahi
New cookbook: Small Object Detection with SAHI
2024-09-18 23:24:36 +03:00
Onuralp SEZER 1b7344d6cc refactor: remove missing elapsed time and add interactive before/after image slider
chore: address comments
refactor: jupyter_compare_view resize and small typing and import fixes

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-18 23:16:56 +03:00
Onuralp SEZER 685929fbd9 feat: ✨ initial ncnn detection support added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-18 22:29:19 +03:00
LinasKo 95add3679d Fix broken or outdated links
* Not exhaustive.
2024-09-18 19:06:41 +03:00
LinasKo 9d2dc08e34 Change mAP result to match F1 result 2024-09-18 16:40:49 +03:00
Onuralp SEZER 420b832e8e Merge pull request #1520 from roboflow/dependabot/pip/tox-4.19.0
⬆️ Bump tox from 4.18.1 to 4.19.0
2024-09-18 13:41:34 +03:00
LinasKo d25016e428 Typo: mAP in F1 score class 2024-09-18 12:02:19 +03:00
LinasKo b9e9adaab3 Add F1 Score, remove dead code, list TODOs for mAP 2024-09-18 09:50:52 +03:00
Onuralp SEZER 96b8d53123 Merge pull request #1519 from roboflow/dependabot/pip/types-pyyaml-6.0.12.20240917
⬆️ Bump types-pyyaml from 6.0.12.20240808 to 6.0.12.20240917
2024-09-18 04:03:19 +03:00
dependabot[bot] 00cba6e8cf ⬆️ Bump tox from 4.18.1 to 4.19.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.18.1 to 4.19.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.18.1...4.19.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 00:17:28 +00:00
dependabot[bot] 0ddb62d024 ⬆️ Bump types-pyyaml from 6.0.12.20240808 to 6.0.12.20240917
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.12.20240808 to 6.0.12.20240917.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-pyyaml
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 00:16:20 +00:00
Onuralp SEZER d9b297a1d1 Merge pull request #1518 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-09-16 21:13:07 +03:00
pre-commit-ci[bot] 4919b84dd7 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5)
2024-09-16 17:49:12 +00:00
LinasKo 3cf9634907 WIP: F1 score 2024-09-16 15:14:40 +03:00
Onuralp SEZER cbd62ef741 Merge pull request #1517 from roboflow/dependabot/pip/ruff-0.6.5
⬆️ Bump ruff from 0.6.4 to 0.6.5
2024-09-16 03:37:44 +03:00
Onuralp SEZER 30f75bd3a9 Merge pull request #1516 from roboflow/dependabot/pip/types-requests-2.32.0.20240914
⬆️ Bump types-requests from 2.32.0.20240907 to 2.32.0.20240914
2024-09-16 03:36:52 +03:00
dependabot[bot] c9159f9f24 ⬆️ Bump ruff from 0.6.4 to 0.6.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.4 to 0.6.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.4...0.6.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 00:36:20 +00:00
dependabot[bot] d5efa5dbac ⬆️ Bump types-requests from 2.32.0.20240907 to 2.32.0.20240914
Bumps [types-requests](https://github.com/python/typeshed) from 2.32.0.20240907 to 2.32.0.20240914.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 00:34:55 +00:00
Onuralp SEZER 6e07b17422 feat: ✨ from_easyocr paragraph mode support added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-15 05:26:43 +03:00
Onuralp SEZER ef52dfff05 feat: ✨ from_easyocr detection added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-09-15 04:36:03 +03:00
Onuralp SEZER ade8976eaf Merge pull request #1512 from roboflow/dependabot/pip/mkdocs-git-revision-date-localized-plugin-1.2.9
⬆️ Bump mkdocs-git-revision-date-localized-plugin from 1.2.8 to 1.2.9
2024-09-13 05:10:58 +03:00
dependabot[bot] abec8da828 ⬆️ Bump mkdocs-git-revision-date-localized-plugin
Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.2.8 to 1.2.9.
- [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.2.8...v1.2.9)

---
updated-dependencies:
- dependency-name: mkdocs-git-revision-date-localized-plugin
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-13 00:52:04 +00:00
Onuralp SEZER 675d3e58bc Merge pull request #1511 from roboflow/dependabot/pip/pytest-8.3.3
⬆️ Bump pytest from 8.3.2 to 8.3.3
2024-09-11 03:24:11 +03:00
dependabot[bot] c30c9f4b4a ⬆️ Bump pytest from 8.3.2 to 8.3.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.2 to 8.3.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.3.2...8.3.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-11 00:16:28 +00:00
Onuralp SEZER 9203116468 Merge pull request #1510 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-09-10 22:12:46 +03:00
pre-commit-ci[bot] a9fe5ccd9a chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4)
2024-09-09 17:51:35 +00:00
Onuralp SEZER f5c50013b6 Merge pull request #1508 from roboflow/dependabot/pip/mkdocs-git-revision-date-localized-plugin-1.2.8
⬆️ Bump mkdocs-git-revision-date-localized-plugin from 1.2.7 to 1.2.8
2024-09-09 10:29:35 +03:00
Onuralp SEZER a063f0aa2b Merge pull request #1507 from roboflow/dependabot/pip/tox-4.18.1
⬆️ Bump tox from 4.18.0 to 4.18.1
2024-09-09 10:29:12 +03:00
Onuralp SEZER 23ce82f48d Merge pull request #1506 from roboflow/dependabot/pip/mkdocstrings-0.26.1
⬆️ Bump mkdocstrings from 0.26.0 to 0.26.1
2024-09-09 10:28:48 +03:00
Onuralp SEZER ea4bc3f875 Merge pull request #1505 from roboflow/dependabot/pip/build-1.2.2
⬆️ Bump build from 1.2.1 to 1.2.2
2024-09-09 10:28:25 +03:00
Onuralp SEZER cceecb095b Merge pull request #1509 from roboflow/dependabot/pip/types-requests-2.32.0.20240907
⬆️ Bump types-requests from 2.32.0.20240905 to 2.32.0.20240907
2024-09-09 10:27:54 +03:00
dependabot[bot] a20d784e25 ⬆️ Bump types-requests from 2.32.0.20240905 to 2.32.0.20240907
Bumps [types-requests](https://github.com/python/typeshed) from 2.32.0.20240905 to 2.32.0.20240907.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:32:38 +00:00
dependabot[bot] ce53f6c99d ⬆️ Bump mkdocs-git-revision-date-localized-plugin
Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.2.7 to 1.2.8.
- [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.2.7...v1.2.8)

---
updated-dependencies:
- dependency-name: mkdocs-git-revision-date-localized-plugin
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:31:33 +00:00
dependabot[bot] 2f942d296a ⬆️ Bump tox from 4.18.0 to 4.18.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.18.0 to 4.18.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.18.0...4.18.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:30:48 +00:00
dependabot[bot] 06f6286452 ⬆️ Bump mkdocstrings from 0.26.0 to 0.26.1
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.26.0 to 0.26.1.
- [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.26.0...0.26.1)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:30:02 +00:00
dependabot[bot] b987f98795 ⬆️ Bump build from 1.2.1 to 1.2.2
Bumps [build](https://github.com/pypa/build) from 1.2.1 to 1.2.2.
- [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/1.2.1...1.2.2)

---
updated-dependencies:
- dependency-name: build
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:28:20 +00:00
Onuralp SEZER f20a3149c8 Merge pull request #1504 from roboflow/dependabot/pip/types-requests-2.32.0.20240905
⬆️ Bump types-requests from 2.32.0.20240712 to 2.32.0.20240905
2024-09-06 04:04:39 +03:00
Onuralp SEZER 26987d7cf5 Merge pull request #1503 from roboflow/dependabot/pip/ruff-0.6.4
⬆️ Bump ruff from 0.6.3 to 0.6.4
2024-09-06 04:04:26 +03:00
dependabot[bot] 370e80d7ec ⬆️ Bump types-requests from 2.32.0.20240712 to 2.32.0.20240905
Bumps [types-requests](https://github.com/python/typeshed) from 2.32.0.20240712 to 2.32.0.20240905.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-06 00:20:09 +00:00
dependabot[bot] 8a912c5dde ⬆️ Bump ruff from 0.6.3 to 0.6.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.3 to 0.6.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.3...0.6.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-06 00:19:24 +00:00
patel-zeel 323e75844f Add oriented_box_iou_batch function to detection.utils 2024-09-05 12:00:10 +05:30
Onuralp SEZER 9410f033bb Merge pull request #1501 from roboflow/dependabot/pip/cryptography-43.0.1
⬆️ Bump cryptography from 43.0.0 to 43.0.1
2024-09-04 23:48:09 +03:00
dependabot[bot] b25f0b1ac2 ⬆️ Bump cryptography from 43.0.0 to 43.0.1
Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.0 to 43.0.1.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/43.0.0...43.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-04 15:36:47 +00:00
SkalskiP e52989280a bump version from 0.23.0 to 0.24.0rc1 2024-09-04 17:34:21 +02:00
Piotr Skalski 621f8e4d29 Merge pull request #1500 from roboflow/fix/101-coco-algorithm
101 AP algo: do not interpollate precision
2024-09-04 17:27:59 +02:00
LinasKo 7a1c3149fd Fix typo, false positives -> false negatives 2024-09-04 16:45:06 +02:00
LinasKo 5467db4447 101 AP algo: do not interpollate precision 2024-09-03 19:08:07 +02:00
Onuralp SEZER 96bdcb3dea Merge pull request #1499 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-09-03 00:52:13 +03:00
pre-commit-ci[bot] 54278ea1f0 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.2 → v0.6.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.3)
2024-09-02 17:48:28 +00:00
Onuralp SEZER 4e9aec6d2a Merge pull request #1497 from roboflow/dependabot/pip/mkdocs-material-9.5.34
⬆️ Bump mkdocs-material from 9.5.33 to 9.5.34
2024-09-02 16:17:47 +03:00
Onuralp SEZER db79350caa Merge pull request #1498 from roboflow/dependabot/pip/mkdocstrings-0.26.0
⬆️ Bump mkdocstrings from 0.25.2 to 0.26.0
2024-09-02 16:17:31 +03:00
dependabot[bot] a6e0177cb9 ⬆️ Bump mkdocstrings from 0.25.2 to 0.26.0
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.25.2 to 0.26.0.
- [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...0.26.0)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 00:12:29 +00:00
dependabot[bot] d3968822a7 ⬆️ Bump mkdocs-material from 9.5.33 to 9.5.34
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.33 to 9.5.34.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.33...9.5.34)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 00:09:45 +00:00
Onuralp SEZER b06d979525 Merge pull request #1494 from Kadermiyanyedi/fix-test-warnings
fix(refactor): 🐞 Update deprecated usages of supervision tests to the latest version
2024-08-31 01:47:06 +03:00
Onuralp SEZER 1ed782560d Merge pull request #1495 from Kadermiyanyedi/fix-typos
fix: documentation and notebook spell fixes
2024-08-31 01:42:35 +03:00
Kader Miyanyedi 3c41c9721d fix: fix typos 2024-08-31 01:31:16 +03:00
Onuralp SEZER e805212ae6 Merge pull request #1493 from roboflow/dependabot/pip/ruff-0.6.3
⬆️ Bump ruff from 0.6.2 to 0.6.3
2024-08-31 01:25:52 +03:00
Kader Miyanyedi 80567df1ea fix: fix test warnings 2024-08-31 01:21:27 +03:00
dependabot[bot] fab89d2374 ⬆️ Bump ruff from 0.6.2 to 0.6.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.2 to 0.6.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.2...0.6.3)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 00:35:59 +00:00
Onuralp SEZER a937fa5100 Merge pull request #1491 from roboflow/dependabot/pip/jupyterlab-4.2.5
⬆️ Bump jupyterlab from 4.2.4 to 4.2.5
2024-08-29 22:14:17 +03:00
pre-commit-ci[bot] 418ec5bad2 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-29 18:30:09 +00:00
Onuralp SEZER 9b52d62bde Merge branch 'develop' into docs/cookbook-sahi 2024-08-29 21:26:55 +03:00
dependabot[bot] a298ceb18c ⬆️ Bump jupyterlab from 4.2.4 to 4.2.5
Bumps [jupyterlab](https://github.com/jupyterlab/jupyterlab) from 4.2.4 to 4.2.5.
- [Release notes](https://github.com/jupyterlab/jupyterlab/releases)
- [Changelog](https://github.com/jupyterlab/jupyterlab/blob/@jupyterlab/lsp@4.2.5/CHANGELOG.md)
- [Commits](https://github.com/jupyterlab/jupyterlab/compare/@jupyterlab/lsp@4.2.4...@jupyterlab/lsp@4.2.5)

---
updated-dependencies:
- dependency-name: jupyterlab
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-29 18:12:52 +00:00
LinasKo c7c02bb4de Merge pull request #1490 from CharlesCNorton/patch-1
fix: correct typo in URL
2024-08-29 15:23:55 +03:00
Eddie Ramirez c1e249e2bb Chore: make it lean
- Drop leafmap
- Remove unnecesarry pkgs
- Consolidate imports
- Use sv.plot_image
- Refactor code
2024-08-28 20:51:13 -07:00
CharlesCNorton a7debec87d fix: correct typo in URL
fix: Correct typo in link URL from "disuss.roboflow.com" to "discuss.roboflow.com"
2024-08-28 17:36:04 -04:00
LinasKo b4b941fe1b hotfix: mAP docs 2024-08-28 20:59:54 +03:00
LinasKo 3ddc4bd007 Merge pull request #1489 from roboflow/main
Merge `main` back into `develop` after `supervision-0.23.0` release.
2024-08-28 20:43:02 +03:00
pre-commit-ci[bot] 2e55149d43 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-27 04:30:23 +00:00
Eddie Ramirez fb25fda6ef chore: comments 2024-08-26 21:27:54 -07:00
pre-commit-ci[bot] 838b8c345a fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-27 04:26:48 +00:00
Eddie Ramirez 1f21354f02 chore: rename file, format code 2024-08-26 21:26:25 -07:00
Eddie Ramirez 6c03451279 chore: fix typo 2024-08-26 18:29:03 -07:00
pre-commit-ci[bot] ab983bba27 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-27 01:27:49 +00:00
Eddie Ramirez 48a1f04060 fix: link to colab 2024-08-26 18:27:32 -07:00
pre-commit-ci[bot] 4356a90fc9 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-27 01:05:32 +00:00
Eddie Ramirez 5cda6e339e chore: delete all outputs 2024-08-26 18:04:23 -07:00
pre-commit-ci[bot] d6ebc433a7 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-27 00:53:26 +00:00
Eddie Ramirez cd2fd0d723 chore: added images, fixed typos 2024-08-26 17:52:21 -07:00
pre-commit-ci[bot] 3653755996 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-27 00:00:15 +00:00
Eddie Ramirez e6686ba71d chore: get secret from userdata (colab) 2024-08-26 16:59:54 -07:00
pre-commit-ci[bot] 3b5b8709e5 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-26 23:24:18 +00:00
Eddie Ramirez aed3feba14 New cookbook: Small Object Detection with SAHI 2024-08-26 16:17:37 -07:00
Juan Cruz 86d15c6c90 Merge branch 'develop' into lc/diagonal_support 2024-06-10 11:50:34 -03:00
Juan Cruz c1e4d79c56 Merge branch 'develop' into lc/diagonal_support 2024-06-06 13:21:05 -03:00
Juan Cruz ac6dd7bca4 Merge branch 'develop' into lc/diagonal_support 2024-05-31 11:06:05 -03:00
Juan Cruz cbec7f8580 Merge branch 'develop' into lc/diagonal_support 2024-05-02 12:28:34 -03:00
Juan Cruz a7c00ca5da Merge branch 'develop' into lc/diagonal_support 2024-04-22 11:55:57 -03:00
Juan Cruz 2a5dc6757c Merge branch 'develop' into lc/diagonal_support 2024-04-05 12:03:16 -03:00
Juan Cruz 71164fd0c1 feat(LineZonaAnnotator): Add support for non-horizontal lines 2024-02-05 11:09:04 -03:00
79 changed files with 5404 additions and 3023 deletions
+9 -10
View File
@@ -1,4 +1,4 @@
name: Supervision Test Releases to PyPi
name: Publish Supervision Pre-Releases to PyPI and TestPyPI
on:
push:
tags:
@@ -9,9 +9,11 @@ on:
workflow_dispatch:
jobs:
build-n-publish:
build-and-publish-pre-release-pypi:
name: Build and publish to PyPI
runs-on: ubuntu-latest
permissions:
id-token: write
strategy:
matrix:
python-version: ["3.10"]
@@ -30,14 +32,11 @@ jobs:
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: 🚀 Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_PASSWORD }}
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1.10
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@release/v1.10
with:
repository-url: https://test.pypi.org/legacy/
user: ${{ secrets.PYPI_TEST_USERNAME }}
password: ${{ secrets.PYPI_TEST_PASSWORD }}
+8 -9
View File
@@ -1,4 +1,4 @@
name: Supervision Releases to PyPi
name: Publish Supervision Releases to PyPI and TestPyPI
on:
push:
tags:
@@ -7,8 +7,10 @@ on:
workflow_dispatch:
jobs:
build:
build-and-publish-pre-release:
runs-on: ubuntu-latest
permissions:
id-token: write
strategy:
matrix:
python-version: ["3.10"]
@@ -27,14 +29,11 @@ jobs:
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_PASSWORD }}
uses: pypa/gh-action-pypi-publish@release/v1.10
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@release/v1.10
with:
repository-url: https://test.pypi.org/legacy/
user: ${{ secrets.PYPI_TEST_USERNAME }}
password: ${{ secrets.PYPI_TEST_PASSWORD }}
+17 -2
View File
@@ -25,16 +25,31 @@ repos:
- id: mixed-line-ending
- repo: https://github.com/PyCQA/bandit
rev: '1.7.9'
rev: '1.7.10'
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.2
rev: v0.6.8
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
types_or: [ python, pyi, jupyter ]
# - repo: https://github.com/executablebooks/mdformat
# rev: 0.7.17
# hooks:
# - id: mdformat
# additional_dependencies:
# - "mdformat-mkdocs[recommended]>=2.1.0"
# args: ["--number"]
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
additional_dependencies:
- tomli
+16 -17
View File
@@ -1,4 +1,3 @@
# Contributor Covenant Code of Conduct
## Our Pledge
@@ -6,7 +5,7 @@
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, socio-economic status,
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
@@ -18,23 +17,23 @@ diverse, inclusive, and healthy community.
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,
- 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
- 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
- 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,
- 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
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -121,14 +120,14 @@ 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].
[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/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
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[mozilla coc]: https://github.com/mozilla/diversity
[translations]: https://www.contributor-covenant.org/translations
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+43 -35
View File
@@ -6,22 +6,22 @@ We are actively improving this library to reduce the amount of work you need to
## Code of Conduct
Please read and adhere to our [Code of Conduct](CODE_OF_CONDUCT.md). This document outlines the expected behavior for all participants in our project.
Please read and adhere to our [Code of Conduct](https://supervision.roboflow.com/latest/code_of_conduct/). This document outlines the expected behavior for all participants in our project.
## Table of Contents
- [Contribution Guidelines](#contribution-guidelines)
- [Contributing Features](#contributing-features-)
- [Contributing Features](#contributing-features)
- [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)
- [Documentation](#-documentation)
- [Cookbooks](#-cookbooks)
- [Tests](#-tests)
- [License](#-license)
- [Code Style and Quality](#code-style-and-quality)
- [Pre-commit tool](#pre-commit-tool)
- [Docstrings](#docstrings)
- [Type checking](#type-checking)
- [Documentation](#documentation)
- [Cookbooks](#cookbooks)
- [Tests](#tests)
- [License](#license)
## Contribution Guidelines
@@ -83,9 +83,10 @@ git push -u origin <your_branch_name>
Use conventional commit messages to clearly describe your changes. The format is:
<type>[optional scope]: <description>
<type>\[optional scope\]: <description>
Common types include:
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
@@ -128,40 +129,47 @@ PRs must pass all tests and linting requirements before they can be merged.
Before starting your work on the project, set up your development environment:
1. Clone your fork of the project:
```bash
git clone https://github.com/YOUR_USERNAME/supervision.git
cd supervision
```
Replace `YOUR_USERNAME` with your GitHub username.
```bash
git clone https://github.com/YOUR_USERNAME/supervision.git
cd supervision
```
Replace `YOUR_USERNAME` with your GitHub username.
2. Create and activate a virtual environment:
```bash
python3 -m venv .venv
source .venv/bin/activate
```
```bash
python3 -m venv .venv
source .venv/bin/activate
```
3. Install Poetry:
Using pip:
```bash
pip install -U pip setuptools
pip install poetry
```
Using pip:
Or using pipx (recommended for global installation):
```bash
pipx install poetry
```
```bash
pip install -U pip setuptools
pip install poetry
```
Or using pipx (recommended for global installation):
```bash
pipx install poetry
```
4. Install project dependencies:
```bash
poetry install
```
```bash
poetry install
```
5. Run pytest to verify the setup:
```bash
poetry run pytest
```
```bash
poetry run pytest
```
## 🎨 Code Style and Quality
+88 -84
View File
@@ -8,11 +8,11 @@
</a>
</p>
<br>
<br>
[notebooks](https://github.com/roboflow/notebooks) | [inference](https://github.com/roboflow/inference) | [autodistill](https://github.com/autodistill/autodistill) | [maestro](https://github.com/roboflow/multimodal-maestro)
<br>
<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)
@@ -23,13 +23,18 @@
[![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)](https://discord.gg/GbfgXGJ8Bk)
[![built-with-material-for-mkdocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?logo=MaterialForMkDocs&logoColor=white)](https://squidfunk.github.io/mkdocs-material/)
<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>
</div>
</div>
## 👋 hello
**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! 🤝
[![supervision-hackfest](https://github.com/roboflow/supervision/assets/26109316/c05cc954-b9a6-4ed5-9a52-d0b4b619ff65)](https://github.com/orgs/roboflow/projects)
[![supervision-hackfest](https://media.roboflow.com/supervision/supervision-hacktoberfest-banner-2024.png)](https://github.com/roboflow/supervision/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest)
## 💻 install
@@ -54,7 +59,7 @@ import supervision as sv
from ultralytics import YOLO
image = cv2.imread(...)
model = YOLO('yolov8s.pt')
model = YOLO("yolov8s.pt")
result = model(image)[0]
detections = sv.Detections.from_ultralytics(result)
@@ -67,21 +72,21 @@ 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 cv2
import supervision as sv
from inference import get_model
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(...)
model = get_model(model_id="yolov8s-640", api_key=<ROBOFLOW API KEY>)
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
image = cv2.imread(...)
model = get_model(model_id="yolov8s-640", api_key=<ROBOFLOW API KEY>)
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
len(detections)
# 5
```
len(detections)
# 5
```
</details>
@@ -98,9 +103,8 @@ detections = sv.Detections(...)
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections
)
scene=image.copy(),
detections=detections)
```
https://github.com/roboflow/supervision/assets/26109316/691e219c-0565-4403-9218-ab5644f39bce
@@ -133,88 +137,88 @@ for path, image, annotation in ds:
- 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>
@@ -266,7 +270,7 @@ We love your input! Please see our [contributing guide](https://github.com/robof
<div align="center">
<div align="center">
<div align="center">
<a href="https://youtube.com/roboflow">
<img
src="https://media.roboflow.com/notebooks/template/icons/purple/youtube.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949634652"
@@ -295,7 +299,7 @@ We love your input! Please see our [contributing guide](https://github.com/robof
/>
</a>
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
<a href="https://disuss.roboflow.com">
<a href="https://discuss.roboflow.com">
<img
src="https://media.roboflow.com/notebooks/template/icons/purple/forum.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949633584"
width="3%"
Vendored
+1 -1
View File
@@ -1357,7 +1357,7 @@
}
],
"source": [
"IMAGE_NAME = list(ds.images.keys())[0]\n",
"IMAGE_NAME = next(iter(ds.images.keys()))\n",
"\n",
"image = ds.images[IMAGE_NAME]\n",
"annotations = ds.annotations[IMAGE_NAME]\n",
-1
View File
@@ -13,7 +13,6 @@ To install the Supervision assets utility, you can use `pip`. This utility is av
as an extra within the Supervision package.
!!! example "pip install"
```bash
pip install "supervision[assets]"
```
+181 -41
View File
@@ -1,6 +1,147 @@
# CHANGELOG
### 0.24.0 <small>Oct 4, 2024</small>
- Added [F1 score](https://supervision.roboflow.com/0.24.0/metrics/f1_score/#supervision.metrics.f1_score.F1Score) as a new metric for detection and segmentation. [#1521](https://github.com/roboflow/supervision/pull/1521)
```python
import supervision as sv
from supervision.metrics import F1Score
predictions = sv.Detections(...)
targets = sv.Detections(...)
f1_metric = F1Score()
f1_result = f1_metric.update(predictions, targets).compute()
print(f1_result)
print(f1_result.f1_50)
print(f1_result.small_objects.f1_50)
```
- Added new cookbook: [Small Object Detection with SAHI](https://supervision.roboflow.com/0.24.0/notebooks/small-object-detection-with-sahi/). This cookbook provides a detailed guide on using [`InferenceSlicer`](https://supervision.roboflow.com/0.24.0/detection/tools/inference_slicer/) for small object detection. [#1483](https://github.com/roboflow/supervision/pull/1483)
- Added an [Embedded Workflow](https://roboflow.com/workflows), which allows you to [preview annotators](https://supervision.roboflow.com/0.24.0/detection/annotators/). [#1533](https://github.com/roboflow/supervision/pull/1533)
- Enhanced [`LineZoneAnnotator`](https://supervision.roboflow.com/0.24.0/detection/tools/line_zone/#supervision.detection.line_zone.LineZoneAnnotator), allowing the labels to align with the line, even when it's not horizontal. Also, you can now disable text background, and choose to draw labels off-center which minimizes overlaps for multiple [`LineZone`](https://supervision.roboflow.com/0.24.0/detection/tools/line_zone/#supervision.detection.line_zone.LineZone) labels. [#854](https://github.com/roboflow/supervision/pull/854)
```python
import supervision as sv
import cv2
image = cv2.imread("<SOURCE_IMAGE_PATH>")
line_zone = sv.LineZone(
start=sv.Point(0, 100),
end=sv.Point(50, 200)
)
line_zone_annotator = sv.LineZoneAnnotator(
text_orient_to_line=True,
display_text_box=False,
text_centered=False
)
annotated_frame = line_zone_annotator.annotate(
frame=image.copy(), line_counter=line_zone
)
sv.plot_image(frame)
```
- Added per-class counting capabilities to [`LineZone`](https://supervision.roboflow.com/0.24.0/detection/tools/line_zone/#supervision.detection.line_zone.LineZone) and introduced [`LineZoneAnnotatorMulticlass`](https://supervision.roboflow.com/0.24.0/detection/tools/line_zone/#supervision.detection.line_zone.LineZoneAnnotatorMulticlass) for visualizing the counts per class. This feature allows tracking of individual classes crossing a line, enhancing the flexibility of use cases like traffic monitoring or crowd analysis. [#1555](https://github.com/roboflow/supervision/pull/1555)
```python
import supervision as sv
import cv2
image = cv2.imread("<SOURCE_IMAGE_PATH>")
line_zone = sv.LineZone(
start=sv.Point(0, 100),
end=sv.Point(50, 200)
)
line_zone_annotator = sv.LineZoneAnnotatorMulticlass()
frame = line_zone_annotator.annotate(
frame=frame, line_zones=[line_zone]
)
sv.plot_image(frame)
```
- Added [`from_easyocr`](https://supervision.roboflow.com/0.24.0/detection/core/#supervision.detection.core.Detections.from_easyocr), allowing integration of OCR results into the supervision framework. [EasyOCR](https://github.com/JaidedAI/EasyOCR) is an open-source optical character recognition (OCR) library that can read text from images. [#1515](https://github.com/roboflow/supervision/pull/1515)
```python
import supervision as sv
import easyocr
import cv2
image = cv2.imread("<SOURCE_IMAGE_PATH>")
reader = easyocr.Reader(["en"])
result = reader.readtext("<SOURCE_IMAGE_PATH>", paragraph=True)
detections = sv.Detections.from_easyocr(result)
box_annotator = sv.BoxAnnotator(color_lookup=sv.ColorLookup.INDEX)
label_annotator = sv.LabelAnnotator(color_lookup=sv.ColorLookup.INDEX)
annotated_image = image.copy()
annotated_image = box_annotator.annotate(scene=annotated_image, detections=detections)
annotated_image = label_annotator.annotate(scene=annotated_image, detections=detections)
sv.plot_image(annotated_image)
```
- Added [`oriented_box_iou_batch`](https://supervision.roboflow.com/0.24.0/detection/utils/#supervision.detection.utils.oriented_box_iou_batch) function to `detection.utils`. This function computes Intersection over Union (IoU) for oriented or rotated bounding boxes (OBB). [#1502](https://github.com/roboflow/supervision/pull/1502)
```python
import numpy as np
boxes_true = np.array([[[1, 0], [0, 1], [3, 4], [4, 3]]])
boxes_detection = np.array([[[1, 1], [2, 0], [4, 2], [3, 3]]])
ious = sv.oriented_box_iou_batch(boxes_true, boxes_detection)
print("IoU between true and detected boxes:", ious)
```
- Extended [`PolygonZoneAnnotator`](https://supervision.roboflow.com/0.24.0/detection/tools/polygon_zone/#supervision.detection.tools.polygon_zone.PolygonZoneAnnotator) to allow setting opacity when drawing zones, providing enhanced visualization by filling the zone with adjustable transparency. [#1527](https://github.com/roboflow/supervision/pull/1527)
```python
import cv2
from ncnn.model_zoo import get_model
import supervision as sv
image = cv2.imread("<SOURCE_IMAGE_PATH>")
model = get_model(
"yolov8s",
target_size=640,
prob_threshold=0.5,
nms_threshold=0.45,
num_threads=4,
use_gpu=True,
)
result = model(image)
detections = sv.Detections.from_ncnn(result)
```
!!! failure "Removed"
The `frame_resolution_wh` parameter in [`PolygonZone`](https://supervision.roboflow.com/0.24.0/detection/tools/polygon_zone/#supervision.detection.tools.polygon_zone.PolygonZone) has been removed.
!!! failure "Removed"
Supervision installation methods `"headless"` and `"desktop"` were removed, as they are no longer needed. `pip install supervision[headless]` will install the base library and harmlessly warn of non-existent extras.
- Supervision now depends on `opencv-python` rather than `opencv-python-headless`. [#1530](https://github.com/roboflow/supervision/pull/1530)
- Fixed the COCO 101 point Average Precision algorithm to correctly interpolate precision, providing a more precise calculation of average precision without averaging out intermediate values. [#1500](https://github.com/roboflow/supervision/pull/1500)
- Resolved miscellaneous issues highlighted when building documentation. This mostly includes whitespace adjustments and type inconsistencies. Updated documentation for clarity and fixed formatting issues. Added explicit version for `mkdocstrings-python`. [#1549](https://github.com/roboflow/supervision/pull/1549)
- Enabled and fixed Ruff rules for code formatting, including changes like avoiding unnecessary iterable allocations and using Optional for default mutable arguments. [#1526](https://github.com/roboflow/supervision/pull/1526)
### 0.23.0 <small>Aug 28, 2024</small>
- Added [#930](https://github.com/roboflow/supervision/pull/930): `IconAnnotator`, a [new annotator](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.IconAnnotator) that allows drawing icons on each detection. Useful if you want to draw a specific icon for each class.
- Added [#930](https://github.com/roboflow/supervision/pull/930): `IconAnnotator`, a [new annotator](https://supervision.roboflow.com/0.23.0/detection/annotators/#supervision.annotators.core.IconAnnotator) that allows drawing icons on each detection. Useful if you want to draw a specific icon for each class.
```python
import supervision as sv
@@ -31,7 +172,7 @@ annotated_frame = icon_annotator.annotate(
)
```
- Added [#1385](https://github.com/roboflow/supervision/pull/1385): [`BackgroundColorAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BackgroundColorAnnotator), that draws an overlay on the background images of the detections.
- Added [#1385](https://github.com/roboflow/supervision/pull/1385): [`BackgroundColorAnnotator`](https://supervision.roboflow.com/0.23.0/detection/annotators/#supervision.annotators.core.BackgroundColorAnnotator), that draws an overlay on the background images of the detections.
```python
import supervision as sv
@@ -50,7 +191,7 @@ annotated_frame = background_overlay_annotator.annotate(
)
```
- Added [#1386](https://github.com/roboflow/supervision/pull/1386): Support for Transformers v5 functions in [`sv.Detections.from_transformers`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_transformers). This includes the `DetrImageProcessor` methods `post_process_object_detection`, `post_process_panoptic_segmentation`, `post_process_semantic_segmentation`, and `post_process_instance_segmentation`.
- Added [#1386](https://github.com/roboflow/supervision/pull/1386): Support for Transformers v5 functions in [`sv.Detections.from_transformers`](https://supervision.roboflow.com/0.23.0/detection/core/#supervision.detection.core.Detections.from_transformers). This includes the `DetrImageProcessor` methods `post_process_object_detection`, `post_process_panoptic_segmentation`, `post_process_semantic_segmentation`, and `post_process_instance_segmentation`.
```python
import torch
@@ -76,7 +217,7 @@ detections = sv.Detections.from_transformers(
id2label=model.config.id2label)
```
- Added [#1354](https://github.com/roboflow/supervision/pull/1354): Ultralytics SAM (Segment Anything Model) support in [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_ultralytics). [SAM2](https://sam2.metademolab.com/) was released during this update, and is already supported via [`sv.Detections.from_sam`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_sam).
- Added [#1354](https://github.com/roboflow/supervision/pull/1354): Ultralytics SAM (Segment Anything Model) support in [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/0.23.0/detection/core/#supervision.detection.core.Detections.from_ultralytics). [SAM2](https://sam2.metademolab.com/) was released during this update, and is already supported via [`sv.Detections.from_sam`](https://supervision.roboflow.com/0.23.0/detection/core/#supervision.detection.core.Detections.from_sam).
```python
import supervision as sv
@@ -84,7 +225,6 @@ from segment_anything import (
sam_model_registry,
SamAutomaticMaskGenerator
)
sam_model_reg = sam_model_registry[MODEL_TYPE]
sam = sam_model_reg(checkpoint=CHECKPOINT_PATH).to(device=DEVICE)
mask_generator = SamAutomaticMaskGenerator(sam)
@@ -92,15 +232,15 @@ sam_result = mask_generator.generate(IMAGE)
detections = sv.Detections.from_sam(sam_result=sam_result)
```
- Added [#1458](https://github.com/roboflow/supervision/pull/1458): `outline_color` options for [`TriangleAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.TriangleAnnotator) and [`DotAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.DotAnnotator).
- Added [#1458](https://github.com/roboflow/supervision/pull/1458): `outline_color` options for [`TriangleAnnotator`](https://supervision.roboflow.com/0.23.0/detection/annotators/#supervision.annotators.core.TriangleAnnotator) and [`DotAnnotator`](https://supervision.roboflow.com/0.23.0/detection/annotators/#supervision.annotators.core.DotAnnotator).
- Added [#1409](https://github.com/roboflow/supervision/pull/1409): `text_color` option for [`VertexLabelAnnotator`](https://supervision.roboflow.com/latest/keypoint/annotators/#supervision.keypoint.annotators.VertexLabelAnnotator) keypoint annotator.
- Added [#1409](https://github.com/roboflow/supervision/pull/1409): `text_color` option for [`VertexLabelAnnotator`](https://supervision.roboflow.com/0.23.0/keypoint/annotators/#supervision.keypoint.annotators.VertexLabelAnnotator) keypoint annotator.
- Changed [#1434](https://github.com/roboflow/supervision/pull/1434): [`InferenceSlicer`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/) now features an `overlap_ratio_wh` parameter, making it easier to compute slice sizes when handling overlapping slices.
- Changed [#1434](https://github.com/roboflow/supervision/pull/1434): [`InferenceSlicer`](https://supervision.roboflow.com/0.23.0/detection/tools/inference_slicer/) now features an `overlap_wh` parameter, making it easier to compute slice sizes when handling overlapping slices.
- Fix [#1448](https://github.com/roboflow/supervision/pull/1448): Various annotator type issues have been resolved, supporting expanded error handling.
- Fix [#1348](https://github.com/roboflow/supervision/pull/1348): Introduced a new method for [seeking to a specific video frame](https://supervision.roboflow.com/latest/utils/video/#supervision.utils.video.get_video_frames_generator), addressing cases where traditional seek methods were failing. It can be enabled with `iterative_seek=True`.
- Fix [#1348](https://github.com/roboflow/supervision/pull/1348): Introduced a new method for [seeking to a specific video frame](https://supervision.roboflow.com/0.23.0/utils/video/#supervision.utils.video.get_video_frames_generator), addressing cases where traditional seek methods were failing. It can be enabled with `iterative_seek=True`.
```python
import supervision as sv
@@ -121,7 +261,7 @@ for frame in sv.get_video_frames_generator(
!!! failure "Removed"
The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) was removed as of `supervision-0.23.0`. Use `triggering_anchors ` instead.
The `triggering_position` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) was removed as of `supervision-0.23.0`. Use `triggering_anchors` instead.
!!! failure "Deprecated"
@@ -133,7 +273,7 @@ for frame in sv.get_video_frames_generator(
### 0.22.0 <small>Jul 12, 2024</small>
- Added [#1326](https://github.com/roboflow/supervision/pull/1326): [`sv.DetectionsDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset) and [`sv.ClassificationDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.ClassificationDataset) allowing to load the images into memory only when necessary (lazy loading).
- Added [#1326](https://github.com/roboflow/supervision/pull/1326): [`sv.DetectionsDataset`](https://supervision.roboflow.com/0.22.0/datasets/core/#supervision.dataset.core.DetectionDataset) and [`sv.ClassificationDataset`](https://supervision.roboflow.com/0.22.0/datasets/core/#supervision.dataset.core.ClassificationDataset) allowing to load the images into memory only when necessary (lazy loading).
!!! failure "Deprecated"
@@ -166,15 +306,15 @@ for path, image, annotation in ds_train:
# loads image on demand
```
- Added [#1296](https://github.com/roboflow/supervision/pull/1296): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_lmm) now supports parsing results from the [Florence 2](https://huggingface.co/microsoft/Florence-2-large) model, extending the capability to handle outputs from this Large Multimodal Model (LMM). This includes detailed object detection, OCR with region proposals, segmentation, and more. Find out more in our [Colab notebook](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-florence-2-on-detection-dataset.ipynb).
- Added [#1296](https://github.com/roboflow/supervision/pull/1296): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/0.22.0/detection/core/#supervision.detection.core.Detections.from_lmm) now supports parsing results from the [Florence 2](https://huggingface.co/microsoft/Florence-2-large) model, extending the capability to handle outputs from this Large Multimodal Model (LMM). This includes detailed object detection, OCR with region proposals, segmentation, and more. Find out more in our [Colab notebook](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-florence-2-on-detection-dataset.ipynb).
- Added [#1232](https://github.com/roboflow/supervision/pull/1232) to support keypoint detection with Mediapipe. Both [legacy](https://colab.research.google.com/github/googlesamples/mediapipe/blob/main/examples/pose_landmarker/python/%5BMediaPipe_Python_Tasks%5D_Pose_Landmarker.ipynb) and [modern](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker/python) pipelines are supported. See [`sv.KeyPoints.from_mediapipe`](https://supervision.roboflow.com/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe) for more.
- Added [#1232](https://github.com/roboflow/supervision/pull/1232) to support keypoint detection with Mediapipe. Both [legacy](https://colab.research.google.com/github/googlesamples/mediapipe/blob/main/examples/pose_landmarker/python/%5BMediaPipe_Python_Tasks%5D_Pose_Landmarker.ipynb) and [modern](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker/python) pipelines are supported. See [`sv.KeyPoints.from_mediapipe`](https://supervision.roboflow.com/0.22.0/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe) for more.
- Added [#1316](https://github.com/roboflow/supervision/pull/1316): [`sv.KeyPoints.from_mediapipe`](https://supervision.roboflow.com/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe) extended to support FaceMesh from Mediapipe. This enhancement allows for processing both face landmarks from `FaceLandmarker`, and legacy results from `FaceMesh`.
- Added [#1316](https://github.com/roboflow/supervision/pull/1316): [`sv.KeyPoints.from_mediapipe`](https://supervision.roboflow.com/0.22.0/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe) extended to support FaceMesh from Mediapipe. This enhancement allows for processing both face landmarks from `FaceLandmarker`, and legacy results from `FaceMesh`.
- Added [#1310](https://github.com/roboflow/supervision/pull/1310): [`sv.KeyPoints.from_detectron2`](https://supervision.roboflow.com/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_detectron2) is a new `KeyPoints` method, adding support for extracting keypoints from the popular [Detectron 2](https://github.com/facebookresearch/detectron2) platform.
- Added [#1310](https://github.com/roboflow/supervision/pull/1310): [`sv.KeyPoints.from_detectron2`](https://supervision.roboflow.com/0.22.0/keypoint/core/#supervision.keypoint.core.KeyPoints.from_detectron2) is a new `KeyPoints` method, adding support for extracting keypoints from the popular [Detectron 2](https://github.com/facebookresearch/detectron2) platform.
- Added [#1300](https://github.com/roboflow/supervision/pull/1300): [`sv.Detections.from_detectron2`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_detectron2) now supports segmentation models detectron2. The resulting masks can be used with [`sv.MaskAnnotator`](https://supervision.roboflow.com/latest/annotators/#supervision.annotators.core.MaskAnnotator) for displaying annotations.
- Added [#1300](https://github.com/roboflow/supervision/pull/1300): [`sv.Detections.from_detectron2`](https://supervision.roboflow.com/0.22.0/detection/core/#supervision.detection.core.Detections.from_detectron2) now supports segmentation models detectron2. The resulting masks can be used with [`sv.MaskAnnotator`](https://supervision.roboflow.com/0.22.0/detection/annotators/#supervision.annotators.core.MaskAnnotator) for displaying annotations.
```python
import supervision as sv
@@ -196,7 +336,7 @@ mask_annotator = sv.MaskAnnotator()
annotated_frame = mask_annotator.annotate(scene=image.copy(), detections=detections)
```
- Added [#1277](https://github.com/roboflow/supervision/pull/1277): if you provide a font that supports symbols of a language, [`sv.RichLabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator.annotate) will draw them on your images.
- Added [#1277](https://github.com/roboflow/supervision/pull/1277): if you provide a font that supports symbols of a language, [`sv.RichLabelAnnotator`](https://supervision.roboflow.com/0.22.0/detection/annotators/#supervision.annotators.core.LabelAnnotator.annotate) will draw them on your images.
- Various other annotators have been revised to ensure proper in-place functionality when used with `numpy` arrays. Additionally, we fixed a bug where `sv.ColorAnnotator` was filling boxes with solid color when used in-place.
```python
@@ -223,7 +363,7 @@ train_ds = sv.DetectionDataset.from_yolo(
images_directory_path="/content/dataset/train/images",
annotations_directory_path="/content/dataset/train/labels",
data_yaml_path="/content/dataset/data.yaml",
is_obb=True
is_obb=True,
)
_, image, detections in train_ds[0]
@@ -232,25 +372,25 @@ obb_annotator = OrientedBoxAnnotator()
annotated_image = obb_annotator.annotate(scene=image.copy(), detections=detections)
```
- Fixed [#1312](https://github.com/roboflow/supervision/pull/1312): Fixed [`CropAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.TraceAnnotator.annotate).
- Fixed [#1312](https://github.com/roboflow/supervision/pull/1312): Fixed [`CropAnnotator`](https://supervision.roboflow.com/0.22.0/detection/annotators/#supervision.annotators.core.TraceAnnotator.annotate).
!!! failure "Removed"
`BoxAnnotator` was removed, however `BoundingBoxAnnotator` has been renamed to `BoxAnnotator`. Use a combination of [`BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator) and [`LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator) to simulate old `BoundingBox` behavior.
`BoxAnnotator` was removed, however `BoundingBoxAnnotator` has been renamed to `BoxAnnotator`. Use a combination of [`BoxAnnotator`](https://supervision.roboflow.com/0.22.0/detection/annotators/#supervision.annotators.core.BoxAnnotator) and [`LabelAnnotator`](https://supervision.roboflow.com/0.22.0/detection/annotators/#supervision.annotators.core.LabelAnnotator) to simulate old `BoundingBox` behavior.
!!! failure "Deprecated"
The name `BoundingBoxAnnotator` has been deprecated and will be removed in `supervision-0.26.0`. It has been renamed to [`BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator).
The name `BoundingBoxAnnotator` has been deprecated and will be removed in `supervision-0.26.0`. It has been renamed to [`BoxAnnotator`](https://supervision.roboflow.com/0.22.0/detection/annotators/#supervision.annotators.core.BoxAnnotator).
- Added [#975](https://github.com/roboflow/supervision/pull/975) 📝 New Cookbooks: serialize detections into [json](https://github.com/roboflow/supervision/blob/de896189b83a1f9434c0a37dd9192ee00d2a1283/docs/notebooks/serialise-detections-to-json.ipynb) and [csv](https://github.com/roboflow/supervision/blob/de896189b83a1f9434c0a37dd9192ee00d2a1283/docs/notebooks/serialise-detections-to-csv.ipynb).
- Added [#1290](https://github.com/roboflow/supervision/pull/1290): Mostly an internal change, our file utility function now support both `str` and `pathlib` paths.
- Added [#1340](https://github.com/roboflow/supervision/pull/1340): Two new methods for converting between bounding box formats - [`xywh_to_xyxy`](https://supervision.roboflow.com/latest/detection/utils/#supervision.detection.utils.xywh_to_xyxy) and [`xcycwh_to_xyxy`](https://supervision.roboflow.com/latest/detection/utils/#supervision.detection.utils.xcycwh_to_xyxy)
- Added [#1340](https://github.com/roboflow/supervision/pull/1340): Two new methods for converting between bounding box formats - [`xywh_to_xyxy`](https://supervision.roboflow.com/0.22.0/detection/utils/#supervision.detection.utils.xywh_to_xyxy) and [`xcycwh_to_xyxy`](https://supervision.roboflow.com/0.22.0/detection/utils/#supervision.detection.utils.xcycwh_to_xyxy)
!!! failure "Removed"
`from_roboflow` method has been removed due to deprecation. Use [from_inference](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_inference) instead.
`from_roboflow` method has been removed due to deprecation. Use [from_inference](https://supervision.roboflow.com/0.22.0/detection/core/#supervision.detection.core.Detections.from_inference) instead.
!!! failure "Removed"
@@ -274,17 +414,17 @@ annotated_image = obb_annotator.annotate(scene=image.copy(), detections=detectio
!!! failure "Removed"
`ColorPalette.default()` has been removed due to deprecation. Use [ColorPalette.DEFAULT](https://supervision.roboflow.com/latest/utils/draw/#supervision.draw.color.ColorPalette.DEFAULT) instead.
`ColorPalette.default()` has been removed due to deprecation. Use [ColorPalette.DEFAULT](https://supervision.roboflow.com/0.22.0/utils/draw/#supervision.draw.color.ColorPalette.DEFAULT) instead.
!!! failure "Removed"
`FPSMonitor.__call__` has been removed due to deprecation. Use the attribute [FPSMonitor.fps](https://supervision.roboflow.com/latest/utils/video/#supervision.utils.video.FPSMonitor.fps) instead.
`FPSMonitor.__call__` has been removed due to deprecation. Use the attribute [FPSMonitor.fps](https://supervision.roboflow.com/0.22.0/utils/video/#supervision.utils.video.FPSMonitor.fps) instead.
### 0.21.0 <small>Jun 5, 2024</small>
- Added [#500](https://github.com/roboflow/supervision/pull/500): [`sv.Detections.with_nmm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.with_nmm) to perform non-maximum merging on the current set of object detections.
- Added [#500](https://github.com/roboflow/supervision/pull/500): [`sv.Detections.with_nmm`](https://supervision.roboflow.com/0.21.0/detection/core/#supervision.detection.core.Detections.with_nmm) to perform non-maximum merging on the current set of object detections.
- Added [#1221](https://github.com/roboflow/supervision/pull/1221): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_lmm) allowing to parse Large Multimodal Model (LMM) text result into [`sv.Detections`](https://supervision.roboflow.com/latest/detection/core/) object. For now `from_lmm` supports only [PaliGemma](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-paligemma-on-detection-dataset.ipynb) result parsing.
- Added [#1221](https://github.com/roboflow/supervision/pull/1221): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/0.21.0/detection/core/#supervision.detection.core.Detections.from_lmm) allowing to parse Large Multimodal Model (LMM) text result into [`sv.Detections`](https://supervision.roboflow.com/0.21.0/detection/core/) object. For now `from_lmm` supports only [PaliGemma](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-paligemma-on-detection-dataset.ipynb) result parsing.
```python
import supervision as sv
@@ -294,7 +434,7 @@ detections = sv.Detections.from_lmm(
sv.LMM.PALIGEMMA,
paligemma_result,
resolution_wh=(1000, 1000),
classes=['cat', 'dog']
classes=["cat", "dog"],
)
detections.xyxy
# array([[250., 250., 750., 750.]])
@@ -303,7 +443,7 @@ detections.class_id
# array([0])
```
- Added [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.VertexLabelAnnotator`](https://supervision.roboflow.com/latest/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator.annotate) allowing to annotate every vertex of a keypoint skeleton with custom text and color.
- Added [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.VertexLabelAnnotator`](https://supervision.roboflow.com/0.21.0/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator.annotate) allowing to annotate every vertex of a keypoint skeleton with custom text and color.
```python
import supervision as sv
@@ -321,15 +461,15 @@ annotated_frame = edge_annotator.annotate(
)
```
- Added [#1147](https://github.com/roboflow/supervision/pull/1147): [`sv.KeyPoints.from_inference`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints.from_inference) allowing to create [`sv.KeyPoints`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints) from [Inference](https://github.com/roboflow/inference) result.
- Added [#1147](https://github.com/roboflow/supervision/pull/1147): [`sv.KeyPoints.from_inference`](https://supervision.roboflow.com/0.21.0/keypoint/core/#supervision.keypoint.core.KeyPoints.from_inference) allowing to create [`sv.KeyPoints`](https://supervision.roboflow.com/0.21.0/keypoint/core/#supervision.keypoint.core.KeyPoints) from [Inference](https://github.com/roboflow/inference) result.
- Added [#1138](https://github.com/roboflow/supervision/pull/1138): [`sv.KeyPoints.from_yolo_nas`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints.from_yolo_nas) allowing to create [`sv.KeyPoints`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints) from [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) result.
- Added [#1138](https://github.com/roboflow/supervision/pull/1138): [`sv.KeyPoints.from_yolo_nas`](https://supervision.roboflow.com/0.21.0/keypoint/core/#supervision.keypoint.core.KeyPoints.from_yolo_nas) allowing to create [`sv.KeyPoints`](https://supervision.roboflow.com/0.21.0/keypoint/core/#supervision.keypoint.core.KeyPoints) from [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) result.
- Added [#1163](https://github.com/roboflow/supervision/pull/1163): [`sv.mask_to_rle`](https://supervision.roboflow.com/develop/datasets/utils/#supervision.dataset.utils.rle_to_mask) and [`sv.rle_to_mask`](https://supervision.roboflow.com/develop/datasets/utils/#supervision.dataset.utils.rle_to_mask) allowing for easy conversion between mask and rle formats.
- Added [#1163](https://github.com/roboflow/supervision/pull/1163): [`sv.mask_to_rle`](https://supervision.roboflow.com/0.21.0/datasets/utils/#supervision.dataset.utils.rle_to_mask) and [`sv.rle_to_mask`](https://supervision.roboflow.com/0.21.0/datasets/utils/#supervision.dataset.utils.rle_to_mask) allowing for easy conversion between mask and rle formats.
- Changed [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.InferenceSlicer`](https://supervision.roboflow.com/develop/detection/tools/inference_slicer/) allowing to select overlap filtering strategy (`NONE`, `NON_MAX_SUPPRESSION` and `NON_MAX_MERGE`).
- Changed [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.InferenceSlicer`](https://supervision.roboflow.com/0.21.0/detection/tools/inference_slicer/) allowing to select overlap filtering strategy (`NONE`, `NON_MAX_SUPPRESSION` and `NON_MAX_MERGE`).
- Changed [#1178](https://github.com/roboflow/supervision/pull/1178): [`sv.InferenceSlicer`](https://supervision.roboflow.com/develop/detection/tools/inference_slicer/) adding instance segmentation model support.
- Changed [#1178](https://github.com/roboflow/supervision/pull/1178): [`sv.InferenceSlicer`](https://supervision.roboflow.com/0.21.0/detection/tools/inference_slicer/) adding instance segmentation model support.
```python
import cv2
@@ -356,9 +496,9 @@ annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
- Changed [#1228](https://github.com/roboflow/supervision/pull/1228): [`sv.LineZone`](https://supervision.roboflow.com/develop/detection/tools/line_zone/) making it 10-20 times faster, depending on the use case.
- Changed [#1228](https://github.com/roboflow/supervision/pull/1228): [`sv.LineZone`](https://supervision.roboflow.com/0.21.0/detection/tools/line_zone/) making it 10-20 times faster, depending on the use case.
- Changed [#1163](https://github.com/roboflow/supervision/pull/1163): [`sv.DetectionDataset.from_coco`](https://supervision.roboflow.com/develop/datasets/core/#supervision.dataset.core.DetectionDataset.from_coco) and [`sv.DetectionDataset.as_coco`](https://supervision.roboflow.com/develop/datasets/core/#supervision.dataset.core.DetectionDataset.as_coco) adding support for run-length encoding (RLE) mask format.
- Changed [#1163](https://github.com/roboflow/supervision/pull/1163): [`sv.DetectionDataset.from_coco`](https://supervision.roboflow.com/0.21.0/datasets/core/#supervision.dataset.core.DetectionDataset.from_coco) and [`sv.DetectionDataset.as_coco`](https://supervision.roboflow.com/0.21.0/datasets/core/#supervision.dataset.core.DetectionDataset.as_coco) adding support for run-length encoding (RLE) mask format.
### 0.20.0 <small>April 24, 2024</small>
@@ -485,11 +625,11 @@ annotated_frame = crop_annotator.annotate(
)
```
- Changed [#827](https://github.com/roboflow/supervision/pull/827): [`sv.ByteTrack.reset`](/0.19.0/tracking/#supervision.tracking.ByteTrack.reset) allowing users to clear trackers state, enabling the processing of multiple video files in sequence.
- Changed [#827](https://github.com/roboflow/supervision/pull/827): [`sv.ByteTrack.reset`](/0.19.0/trackers/#supervision.tracker.ByteTrack.reset) allowing users to clear trackers state, enabling the processing of multiple video files in sequence.
- Changed [#802](https://github.com/roboflow/supervision/pull/802): [`sv.LineZoneAnnotator`](/0.19.0/detection/tools/line_zone/#supervision.detection.line_zone.LineZone) allowing to hide in/out count using `display_in_count` and `display_out_count` properties.
- Changed [#787](https://github.com/roboflow/supervision/pull/787): [`sv.ByteTrack`](/0.19.0/tracking/#supervision.tracking.ByteTrack) input arguments and docstrings updated to improve readability and ease of use.
- Changed [#787](https://github.com/roboflow/supervision/pull/787): [`sv.ByteTrack`](/0.19.0/trackers/#supervision.tracker.ByteTrack) input arguments and docstrings updated to improve readability and ease of use.
!!! failure "Deprecated"
@@ -916,7 +1056,7 @@ array([
- Added [#101](https://github.com/roboflow/supervision/pull/101): ability to extract masks from YOLOv8 result using [`sv.Detections.from_yolov8`](/0.8.0/detection/core/#supervision.detection.core.Detections.from_yolov8). Here is an example illustrating how to extract boolean masks from the result of the YOLOv8 model inference.
- Added [#122](https://github.com/roboflow/supervision/pull/122): ability to crop image using [`sv.crop`](/latest/utils/image/#crop). Here is an example showing how to get a separate crop for each detection in `sv.Detections`.
- Added [#122](https://github.com/roboflow/supervision/pull/122): ability to crop image using [`sv.crop`](/0.9.0/utils/image/#crop). Here is an example showing how to get a separate crop for each detection in `sv.Detections`.
- Added [#120](https://github.com/roboflow/supervision/pull/120): ability to conveniently save multiple images into directory using [`sv.ImageSink`](/0.9.0/utils/image/#imagesink). Here is an example showing how to save every tenth video frame as a separate image.
@@ -928,11 +1068,11 @@ array([
... sink.save_image(image=image)
```
- Fixed [#106](https://github.com/roboflow/supervision/issues/106): inconvenient handling of [`sv.PolygonZone`](/0.8.0/supervision/detection/tools/polygon_zone/#polygonzone) coordinates. Now `sv.PolygonZone` accepts coordinates in the form of `[[x1, y1], [x2, y2], ...]` that can be both integers and floats.
- Fixed [#106](https://github.com/roboflow/supervision/issues/106): inconvenient handling of [`sv.PolygonZone`](/0.8.0/detection/tools/polygon_zone/#polygonzone) coordinates. Now `sv.PolygonZone` accepts coordinates in the form of `[[x1, y1], [x2, y2], ...]` that can be both integers and floats.
### 0.8.0 <small>May 17, 2023</small>
- Added [#100](https://github.com/roboflow/supervision/pull/100): support for dataset inheritance. The current `Dataset` got renamed to `DetectionDataset`. Now [`DetectionDataset`](/0.8.0/supervision/dataset/core/#detectiondataset) inherits from `BaseDataset`. This change was made to enforce the future consistency of APIs of different types of computer vision datasets.
- Added [#100](https://github.com/roboflow/supervision/pull/100): support for dataset inheritance. The current `Dataset` got renamed to `DetectionDataset`. Now [`DetectionDataset`](/0.8.0/dataset/core/#detectiondataset) inherits from `BaseDataset`. This change was made to enforce the future consistency of APIs of different types of computer vision datasets.
- Added [#100](https://github.com/roboflow/supervision/pull/100): ability to save datasets in YOLO format using [`DetectionDataset.as_yolo`](/0.8.0/dataset/core/#supervision.dataset.core.DetectionDataset.as_yolo).
```python
-1
View File
@@ -5,7 +5,6 @@ comments: true
# 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`.
+12 -6
View File
@@ -7,30 +7,36 @@ status: 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.
- The `frame_resolution_wh ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) will be removed in `supervision-0.24.0`.
- Constructing [`DetectionDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset) and [`ClassificationDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.ClassificationDataset) with parameter `images` as `Dict[str, np.ndarray]` will be removed in `supervision-0.26.0`. Please pass a list of paths `List[str]` instead.
- The `DetectionDataset.images` property will be 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.
- `BoundingBoxAnnotator` has been renamed to `BoxAnnotator` after the old implementation of [`BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator) has been removed. `BoundingBoxAnnotator` will be removed in `supervision-0.26.0`.
- `overlap_filter_strategy` in [`InferenceSlicer.__init__`](https://supervision.roboflow.com/latest/detection/tools/inference_slicer/) is deprecated and will be removed in `supervision-0.27.0`. Use `overlap_strategy` instead.
- `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`. Use `overlap_wh` instead.
# Removed
### 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.
- Supervision installation methods `"headless"` and `"desktop"` were removed, as they are no longer needed. `pip install supervision[headless]` will install the base library and harmlessly warn of non-existent extras.
### 0.23.0
- The `track_buffer`, `track_thresh`, and `match_thresh` parameters in [`ByteTrack`](trackers.md/#supervision.tracker.byte_tracker.core.ByteTrack) are deprecated and were removed as of `supervision-0.23.0`. Use `lost_track_buffer,` `track_activation_threshold`, and `minimum_matching_threshold` instead.
- The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) was removed as of `supervision-0.23.0`. Use `triggering_anchors ` instead.
- The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) was removed as of `supervision-0.23.0`. Use `triggering_anchors` instead.
### 0.22.0
- [`Detections.from_froboflow`](detection/core.md/#supervision.detection.core.Detections.from_roboflow) is removed as of `supervision-0.22.0`. Use [`Detections.from_inference`](detection/core.md/#supervision.detection.core.Detections.from_inference) instead.
- `Detections.from_roboflow` is removed as of `supervision-0.22.0`. Use [`Detections.from_inference`](detection/core.md/#supervision.detection.core.Detections.from_inference) instead.
- The method `Color.white()` was removed as of `supervision-0.22.0`. Use the constant `Color.WHITE` instead.
- The method `Color.black()` was removed as of `supervision-0.22.0`. Use the constant `Color.BLACK` instead.
- The method `Color.red()` was removed as of `supervision-0.22.0`. Use the constant `Color.RED` instead.
- The method `Color.green()` was removed as of `supervision-0.22.0`. Use the constant `Color.GREEN` instead.
- The method `Color.blue()` was removed as of `supervision-0.22.0`. Use the constant `Color.BLUE` instead.
- The method [`ColorPalette.default()`](draw/color.md/#supervision.draw.color.ColorPalette.default) was removed as of `supervision-0.22.0`. Use the constant [`ColorPalette.DEFAULT`](draw/color.md/#supervision.draw.color.ColorPalette.DEFAULT) instead.
- The method `ColorPalette.default()` was removed as of `supervision-0.22.0`. Use the constant [`ColorPalette.DEFAULT`](/utils/draw/#supervision.draw.color.ColorPalette.DEFAULT) instead.
- `BoxAnnotator` was removed as of `supervision-0.22.0`, however `BoundingBoxAnnotator` was immediately renamed to `BoxAnnotator`. Use [`BoxAnnotator`](detection/annotators.md/#supervision.annotators.core.BoxAnnotator) and [`LabelAnnotator`](detection/annotators.md/#supervision.annotators.core.LabelAnnotator) instead of the old `BoxAnnotator`.
- The method [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) was removed as of `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead.
- The method `FPSMonitor.__call__` was removed as of `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead.
+9
View File
@@ -5,6 +5,8 @@ status: new
# Annotators
Annotators accept detections and apply box or mask visualizations to the detections. Annotators have many available styles.
=== "Box"
```python
@@ -481,6 +483,13 @@ status: new
</div>
<div class="md-typeset">
<h2>Try Supervision Annotators on your own image</h2>
Visualize annotators on images with COCO classes such as people, vehicles, animals, household items.
</div>
<div style="height: 400px; width: 100%; border-radius: 8px; overflow: hidden;"><iframe src="https://app.roboflow.com/workflows/embed/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3b3JrZmxvd0lkIjoiNDdtd2xuWW16S25VNWtOYUZjMG8iLCJ3b3Jrc3BhY2VJZCI6ImtyT1RBYm5jRmhvUU1DZExPbGU0IiwidXNlcklkIjoiRVJNUFBZY3FQMmZWWjB1NkRpNXZaYXJDdlZPMiIsImlhdCI6MTcyNjgzOTM2N30.gj2F6SnmmURAScJe4PTC1raUXsAK5mZyrUIGIJ44NhM?hideToolbar=true&hideHeader=true&defaultVisual=true" loading="lazy" title="Roboflow Workflow for Supervision Annotators" style="width: 100%; height: 100%; min-height: 400px; border: none;"></iframe></div>
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.BoxAnnotator">BoxAnnotator</a></h2>
</div>
+7
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
<div class="md-typeset">
@@ -13,3 +14,9 @@ comments: true
</div>
:::supervision.detection.line_zone.LineZoneAnnotator
<div class="md-typeset">
<h2>LineZoneAnnotatorMulticlass</h2>
</div>
:::supervision.detection.line_zone.LineZoneAnnotatorMulticlass
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
<div class="md-typeset">
+7
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# Detection Utils
@@ -16,6 +17,12 @@ comments: true
:::supervision.detection.utils.mask_iou_batch
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.oriented_box_iou_batch">oriented_box_iou_batch</a></h2>
</div>
:::supervision.detection.utils.oriented_box_iou_batch
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.polygon_to_mask">polygon_to_mask</a></h2>
</div>
+8 -23
View File
@@ -6,7 +6,7 @@ comments: true
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),
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
@@ -20,7 +20,6 @@ First, you'll need to obtain predictions from your object detection or segmentat
model.
=== "Inference"
```python
import cv2
from inference import get_model
@@ -31,7 +30,6 @@ model.
```
=== "Ultralytics"
```python
import cv2
from ultralytics import YOLO
@@ -42,7 +40,6 @@ model.
```
=== "Transformers"
```python
import torch
from PIL import Image
@@ -68,8 +65,7 @@ model.
Now that we have predictions from a model, we can load them into Supervision.
=== "Inference"
We can do so using the [`sv.Detections.from_inference`](detection/core/#supervision.detection.core.Detections.from_inference) method, which accepts model results from both detection and segmentation models.
We can do so using the [`sv.Detections.from_inference`](/latest/detection/core/#supervision.detection.core.Detections.from_inference) method, which accepts model results from both detection and segmentation models.
```{ .py hl_lines="2 8" }
import cv2
@@ -83,8 +79,7 @@ Now that we have predictions from a model, we can load them into Supervision.
```
=== "Ultralytics"
We can do so using the [`sv.Detections.from_ultralytics`](detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models.
We can do so using the [`sv.Detections.from_ultralytics`](/latest/detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models.
```{ .py hl_lines="2 8" }
import cv2
@@ -98,8 +93,7 @@ Now that we have predictions from a model, we can load them into Supervision.
```
=== "Transformers"
We can do so using the [`sv.Detections.from_transformers`](detection/core/#supervision.detection.core.Detections.from_transformers) method, which accepts model results from both detection and segmentation models.
We can do so using the [`sv.Detections.from_transformers`](/latest/detection/core/#supervision.detection.core.Detections.from_transformers) method, which accepts model results from both detection and segmentation models.
```{ .py hl_lines="2 19-21" }
import torch
@@ -135,10 +129,9 @@ You can load predictions from other computer vision frameworks and libraries usi
## Annotate Image with Detections
Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoxAnnotator) and [`sv.LabelAnnotator`](/latest/annotators/#supervision.annotators.core.LabelAnnotator) classes.
Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoxAnnotator`](/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator) and [`sv.LabelAnnotator`](/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator) classes.
=== "Inference"
```{ .py hl_lines="10-16" }
import cv2
import supervision as sv
@@ -159,7 +152,6 @@ Finally, we can annotate the image with the predictions. Since we are working wi
```
=== "Ultralytics"
```{ .py hl_lines="10-16" }
import cv2
import supervision as sv
@@ -180,7 +172,6 @@ Finally, we can annotate the image with the predictions. Since we are working wi
```
=== "Transformers"
```{ .py hl_lines="23-30" }
import torch
import supervision as sv
@@ -217,12 +208,11 @@ Finally, we can annotate the image with the predictions. Since we are working wi
## Display Custom Labels
By default, [`sv.LabelAnnotator`](/latest/annotators/#supervision.annotators.core.LabelAnnotator)
By default, [`sv.LabelAnnotator`](/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"
```{ .py hl_lines="13-17 22" }
import cv2
import supervision as sv
@@ -249,7 +239,6 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
```
=== "Ultralytics"
```{ .py hl_lines="13-17 22" }
import cv2
import supervision as sv
@@ -276,7 +265,6 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
```
=== "Transformers"
```{ .py hl_lines="26-30 35" }
import torch
import supervision as sv
@@ -320,13 +308,12 @@ 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`](/latest/annotators/#supervision.annotators.core.MaskAnnotator)
[`sv.MaskAnnotator`](/latest/detection/annotators/#supervision.annotators.core.MaskAnnotator)
is a drop-in replacement for
[`sv.BoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoxAnnotator)
[`sv.BoxAnnotator`](/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator)
that will allow you to draw masks instead of boxes.
=== "Inference"
```python
import cv2
import supervision as sv
@@ -347,7 +334,6 @@ that will allow you to draw masks instead of boxes.
```
=== "Ultralytics"
```python
import cv2
import supervision as sv
@@ -368,7 +354,6 @@ that will allow you to draw masks instead of boxes.
```
=== "Transformers"
```python
import torch
import supervision as sv
-10
View File
@@ -20,7 +20,6 @@ Small object detection in high-resolution images presents challenges due to the
size relative to the image resolution.
=== "Inference"
```python
import cv2
import supervision as sv
@@ -41,7 +40,6 @@ size relative to the image resolution.
```
=== "Ultralytics"
```python
import cv2
import supervision as sv
@@ -62,7 +60,6 @@ size relative to the image resolution.
```
=== "Transformers"
```python
import torch
import supervision as sv
@@ -108,7 +105,6 @@ identification at the cost of processing speed and increased memory usage. This
is less effective for ultra-high-resolution images (4K and above).
=== "Inference"
```{ .py hl_lines="5" }
import cv2
import supervision as sv
@@ -129,7 +125,6 @@ is less effective for ultra-high-resolution images (4K and above).
```
=== "Ultralytics"
```{ .py hl_lines="7" }
import cv2
import supervision as sv
@@ -162,7 +157,6 @@ objects within each, and aggregating the results.
</video>
=== "Inference"
```{ .py hl_lines="9-14" }
import cv2
import numpy as np
@@ -189,7 +183,6 @@ objects within each, and aggregating the results.
```
=== "Ultralytics"
```{ .py hl_lines="9-14" }
import cv2
import numpy as np
@@ -216,7 +209,6 @@ objects within each, and aggregating the results.
```
=== "Transformers"
```{ .py hl_lines="13-28" }
import cv2
import torch
@@ -269,7 +261,6 @@ objects within each, and aggregating the results.
[`InferenceSlicer`](/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer) can perform segmentation tasks too.
=== "Inference"
```{ .py hl_lines="6 16 19-20" }
import cv2
import numpy as np
@@ -296,7 +287,6 @@ objects within each, and aggregating the results.
```
=== "Ultralytics"
```{ .py hl_lines="6 16 19-20" }
import cv2
import numpy as np
-16
View File
@@ -15,7 +15,6 @@ the filters in their applications.
Allows you to select detections that belong only to one selected class.
=== "After"
```python
import supervision as sv
@@ -30,7 +29,6 @@ Allows you to select detections that belong only to one selected class.
</div>
=== "Before"
```python
import supervision as sv
@@ -49,7 +47,6 @@ Allows you to select detections that belong only to one selected class.
Allows you to select detections that belong only to selected set of classes.
=== "After"
```python
import numpy as np
import supervision as sv
@@ -66,7 +63,6 @@ Allows you to select detections that belong only to selected set of classes.
</div>
=== "Before"
```python
import numpy as np
import supervision as sv
@@ -87,7 +83,6 @@ Allows you to select detections that belong only to selected set of classes.
Allows you to select detections with specific confidence value, for example higher than selected threshold.
=== "After"
```python
import supervision as sv
@@ -102,7 +97,6 @@ Allows you to select detections with specific confidence value, for example high
</div>
=== "Before"
```python
import supervision as sv
@@ -122,7 +116,6 @@ Allows you to select detections based on their size. We define the area as the n
detection in the image. In the example below, we have sifted out the detections that are too small.
=== "After"
```python
import supervision as sv
@@ -137,7 +130,6 @@ detection in the image. In the example below, we have sifted out the detections
</div>
=== "Before"
```python
import supervision as sv
@@ -159,7 +151,6 @@ but small on a 3840x2160 image. In such cases, we can filter out detections base
occupied by them. In the example below, we remove too large detections.
=== "After"
```python
import supervision as sv
@@ -178,7 +169,6 @@ occupied by them. In the example below, we remove too large detections.
</div>
=== "Before"
```python
import supervision as sv
@@ -203,7 +193,6 @@ can be criteria for rejecting detection. Implementing such filtering requires a
simple and fast.
=== "After"
```python
import supervision as sv
@@ -220,7 +209,6 @@ simple and fast.
</div>
=== "Before"
```python
import supervision as sv
@@ -242,7 +230,6 @@ Allows you to use `Detections` in combination with `PolygonZone` to weed out bou
zone. In the example below you can see how to filter out all detections located in the lower part of the image.
=== "After"
```python
import supervision as sv
@@ -259,7 +246,6 @@ zone. In the example below you can see how to filter out all detections located
</div>
=== "Before"
```python
import supervision as sv
@@ -280,7 +266,6 @@ zone. In the example below you can see how to filter out all detections located
`Detections`' greatest strength, however, is that you can build arbitrarily complex logical conditions by simply combining separate conditions using `&` or `|`.
=== "After"
```python
import supervision as sv
@@ -297,7 +282,6 @@ zone. In the example below you can see how to filter out all detections located
</div>
=== "Before"
```python
import supervision as sv
+10 -22
View File
@@ -19,7 +19,6 @@ model. You can learn more on this topic in our
[How to Detect and Annotate](/latest/how_to/detect_and_annotate.md) guide.
=== "Inference"
```python
import supervision as sv
from inference import get_model
@@ -34,7 +33,6 @@ model. You can learn more on this topic in our
```
=== "Ultralytics"
```python
import supervision as sv
from ultralytics import YOLO
@@ -49,7 +47,6 @@ model. You can learn more on this topic in our
```
=== "Transformers"
```python
import torch
import supervision as sv
@@ -83,7 +80,6 @@ and then pass the
object resulting from the inference to it. Its fields are parsed and saved on disk.
=== "Inference"
```{ .py hl_lines="7 12" }
import supervision as sv
from inference import get_model
@@ -100,7 +96,6 @@ object resulting from the inference to it. Its fields are parsed and saved on di
```
=== "Ultralytics"
```{ .py hl_lines="7 12" }
import supervision as sv
from ultralytics import YOLO
@@ -117,7 +112,6 @@ object resulting from the inference to it. Its fields are parsed and saved on di
```
=== "Transformers"
```{ .py hl_lines="9 23" }
import torch
import supervision as sv
@@ -144,11 +138,11 @@ object resulting from the inference to it. Its fields are parsed and saved on di
sink.append(detections, {})
```
| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name |
|---------|----------|---------|----------|----------|------------|------------|------------|
| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car |
| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck |
| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car |
| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name |
| ------- | ------- | ------- | ------- | -------- | ---------- | ---------- | ---------- |
| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car |
| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck |
| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car |
## Custom Fields
@@ -160,7 +154,6 @@ also allows you to add custom information to each row, which can be passed via t
frame index from which the detections originate.
=== "Inference"
```{ .py hl_lines="8 12" }
import supervision as sv
from inference import get_model
@@ -177,7 +170,6 @@ frame index from which the detections originate.
```
=== "Ultralytics"
```{ .py hl_lines="8 12" }
import supervision as sv
from ultralytics import YOLO
@@ -194,7 +186,6 @@ frame index from which the detections originate.
```
=== "Transformers"
```{ .py hl_lines="10 23" }
import torch
import supervision as sv
@@ -221,11 +212,11 @@ frame index from which the detections originate.
sink.append(detections, {"frame_index": frame_index})
```
| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name | frame_index |
|---------|----------|---------|----------|----------|------------|------------|------------|-------------|
| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car | 0 |
| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck | 0 |
| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car | 0 |
| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name | frame_index |
| ------- | ------- | ------- | ------- | -------- | ---------- | ---------- | ---------- | ----------- |
| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car | 0 |
| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck | 0 |
| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car | 0 |
## Save Detections as JSON
@@ -236,7 +227,6 @@ with
[`sv.JSONSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink).
=== "Inference"
```{ .py hl_lines="7" }
import supervision as sv
from inference import get_model
@@ -253,7 +243,6 @@ with
```
=== "Ultralytics"
```{ .py hl_lines="7" }
import supervision as sv
from ultralytics import YOLO
@@ -270,7 +259,6 @@ with
```
=== "Transformers"
```{ .py hl_lines="9" }
import torch
import supervision as sv
+2 -10
View File
@@ -41,7 +41,6 @@ This `callback` function will be essential in the subsequent steps of the tutori
it will be modified to include tracking, labeling, and trace annotations.
=== "Ultralytics"
```{ .py }
import numpy as np
import supervision as sv
@@ -63,7 +62,6 @@ it will be modified to include tracking, labeling, and trace annotations.
```
=== "Inference"
```{ .py }
import numpy as np
import supervision as sv
@@ -97,7 +95,6 @@ functionality, each detected object is assigned a unique tracker ID,
enabling the continuous following of the object's motion path across different frames.
=== "Ultralytics"
```{ .py hl_lines="6 12" }
import numpy as np
import supervision as sv
@@ -121,7 +118,6 @@ enabling the continuous following of the object's motion path across different f
```
=== "Inference"
```{ .py hl_lines="6 12" }
import numpy as np
import supervision as sv
@@ -148,12 +144,11 @@ enabling the continuous following of the object's motion path across different f
Annotating the video with tracking IDs helps in distinguishing and following each object
distinctly. With the
[`sv.LabelAnnotator`](/latest/annotators.md/#supervision.annotators.core.LabelAnnotator)
[`sv.LabelAnnotator`](/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"
```{ .py hl_lines="8 15-19 23-24" }
import numpy as np
import supervision as sv
@@ -188,7 +183,6 @@ offering a clear visual representation of each object's class and unique identif
```
=== "Inference"
```{ .py hl_lines="8 15-19 23-24" }
import numpy as np
import supervision as sv
@@ -230,12 +224,11 @@ offering a clear visual representation of each object's class and unique identif
Adding traces to the video involves overlaying the historical paths of the detected
objects. This feature, powered by the
[`sv.TraceAnnotator`](/latest/annotators/#supervision.annotators.core.TraceAnnotator),
[`sv.TraceAnnotator`](/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"
```{ .py hl_lines="9 26-27" }
import numpy as np
import supervision as sv
@@ -273,7 +266,6 @@ movement patterns and interactions between objects in the video.
```
=== "Inference"
```{ .py hl_lines="9 26-27" }
import numpy as np
import supervision as sv
+16 -32
View File
@@ -34,25 +34,18 @@ You can install `supervision` in a
[**Python>=3.8**](https://www.python.org/) environment.
!!! example "pip install (recommended)"
=== "headless"
The headless installation of `supervision` is designed for environments where graphical user interfaces (GUI) are not needed, making it more lightweight and suitable for server-side applications.
=== "pip"
[![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)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
pip install supervision
```
=== "desktop"
If you require the full version of `supervision` with GUI support you can install the desktop version. This version includes the GUI components of OpenCV, allowing you to display images and videos on the screen.
```bash
pip install "supervision[desktop]"
```
!!! example "conda/mamba install"
=== "conda"
[![conda-recipe](https://img.shields.io/badge/recipe-supervision-green.svg)](https://anaconda.org/conda-forge/supervision) [![conda-downloads](https://img.shields.io/conda/dn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![conda-version](https://img.shields.io/conda/vn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![conda-platforms](https://img.shields.io/conda/pn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision)
```bash
@@ -60,7 +53,6 @@ You can install `supervision` in a
```
=== "mamba"
[![mamba-recipe](https://img.shields.io/badge/recipe-supervision-green.svg)](https://anaconda.org/conda-forge/supervision) [![mamba-downloads](https://img.shields.io/conda/dn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![mamba-version](https://img.shields.io/conda/vn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![mamba-platforms](https://img.shields.io/conda/pn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision)
```bash
@@ -68,9 +60,7 @@ You can install `supervision` in a
```
!!! example "git clone (for development)"
=== "virtualenv"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
@@ -81,15 +71,11 @@ You can install `supervision` in a
source venv/bin/activate
pip install --upgrade pip
# headless install
# installation
pip install -e "."
# desktop install
pip install -e ".[desktop]"
```
=== "poetry"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
@@ -99,18 +85,15 @@ You can install `supervision` in a
poetry env use python3.10
poetry shell
# headless install
# installation
poetry install
# desktop install
poetry install --extras "desktop"
```
## 🚀 Quickstart
<div class="grid cards" markdown>
- __Detect and Annotate__
- **Detect and Annotate**
---
@@ -118,7 +101,7 @@ You can install `supervision` in a
[:octicons-arrow-right-24: Tutorial](how_to/detect_and_annotate.md)
- __Track Objects__
- **Track Objects**
---
@@ -126,7 +109,7 @@ You can install `supervision` in a
[:octicons-arrow-right-24: Tutorial](how_to/track_objects.md)
- __Detect Small Objects__
- **Detect Small Objects**
---
@@ -134,25 +117,26 @@ You can install `supervision` in a
[:octicons-arrow-right-24: Tutorial](how_to/detect_small_objects.md)
- > __Count Objects Crossing Line__
- **Count Objects Crossing Line**
---
Explore methods to accurately count and analyze objects crossing a predefined line
- > __Filter Objects in Zone__
[:octicons-arrow-right-24: Notebook](https://supervision.roboflow.com/latest/notebooks/count-objects-crossing-the-line/)
- > **Filter Objects in Zone**
---
Master the techniques to selectively filter and focus on objects within a specific zone
- **Cheatsheet**
- **Cheatsheet**
***
---
Access a quick reference guide to the most common `supervision` functions
[:octicons-arrow-right-24: Cheatsheet](https://roboflow.github.io/cheatsheet-supervision/)
</div>
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Annotators
+18
View File
@@ -0,0 +1,18 @@
---
comments: true
status: new
---
# F1 Score
<div class="md-typeset">
<h2><a href="#supervision.metrics.f1_score.F1Score">F1Score</a></h2>
</div>
:::supervision.metrics.f1_score.F1Score
<div class="md-typeset">
<h2><a href="#supervision.metrics.f1_score.F1ScoreResult">F1ScoreResult</a></h2>
</div>
:::supervision.metrics.f1_score.F1ScoreResult
+5 -5
View File
@@ -287,10 +287,10 @@
],
"source": [
"# Create a bounding box annotator object.\n",
"bounding_box = sv.BoundingBoxAnnotator()\n",
"box_annotator = sv.BoxAnnotator()\n",
"\n",
"# Annotate our frame with detections.\n",
"annotated_frame = bounding_box.annotate(scene=frame.copy(), detections=detections)\n",
"annotated_frame = box_annotator.annotate(scene=frame.copy(), detections=detections)\n",
"\n",
"# Display the frame.\n",
"sv.plot_image(annotated_frame)"
@@ -302,7 +302,7 @@
"id": "o8SsyCid6YV3"
},
"source": [
"Notice that we create a `box_annoator` variable by initalizing a [BoundingBoxAnnotator](https://supervision.roboflow.com/latest/annotators/#boundingboxannotator). We can change the color and thickness, but for simplicity we keep the defaults. There are a ton of easy to use [annotators](https://supervision.roboflow.com/latest/annotators/) available in the Supervision package other than a bounding box that are fun to play with."
"Notice that we create a `box_annotator` variable by initalizing a [BoxAnnotator](https://supervision.roboflow.com/latest/detection/annotators/#boxannotator). We can change the color and thickness, but for simplicity we keep the defaults. There are a ton of easy to use [annotators](https://supervision.roboflow.com/latest/detection/annotators/) available in the Supervision package other than a bounding box that are fun to play with."
]
},
{
@@ -342,7 +342,7 @@
" detections = sv.Detections.from_inference(result)\n",
"\n",
" # Apply bounding box to detections on a copy of the frame.\n",
" annotated_frame = bounding_box.annotate(\n",
" annotated_frame = box_annotator.annotate(\n",
" scene=frame.copy(),\n",
" detections=detections\n",
" )\n",
@@ -368,7 +368,7 @@
"<br>\n",
"\n",
"\n",
"In the code above we've created a`video_info` variable to pass information about the video to our `VideoSink`. The `VideoSink` is a cool little context manager that allows us to `write_frames()` to a video ouput file. We're also optionally using `tqdm` to display a progress bar with a % complete. We only scratched the surface of all of the customizable Annotators and additional features that Supervision and Inference have to offer. Stay tuned for more cookbooks on how to take advantge of them in your computer vision applications. Happy building! \ud83d\ude80"
"In the code above we've created a`video_info` variable to pass information about the video to our `VideoSink`. The `VideoSink` is a cool little context manager that allows us to `write_frames()` to a video output file. We're also optionally using `tqdm` to display a progress bar with a % complete. We only scratched the surface of all of the customizable Annotators and additional features that Supervision and Inference have to offer. Stay tuned for more cookbooks on how to take advantage of them in your computer vision applications. Happy building! \ud83d\ude80"
]
}
],
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -951,7 +951,7 @@
},
"source": [
"#### Import your data\n",
"To load your data back in, upload the saved file to the Colab enviorment and run the code cell."
"To load your data back in, upload the saved file to the Colab environment and run the code cell."
]
},
{
+94 -94
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4
View File
@@ -50,6 +50,10 @@
<p class="card repo-card" data-name="Serialise Detections to a JSON File"
data-labels="DETECTIONS,JSON SINK,INFERENCE" data-version="v0.21.0" data-author="onuralpszr"></p>
</a>
<a href="/develop/notebooks/small-object-detection-with-sahi">
<p class="card repo-card" data-name="Small Object Detection with SAHI"
data-labels="DETECTIONS,SAHI,SMALL,OBJECT,INFERENCE" data-version="v0.23.0" data-author="ediardo"></p>
</a>
</div>
</div>
</section>
+6
View File
@@ -28,6 +28,12 @@ comments: true
:::supervision.draw.utils.draw_polygon
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_filled_polygon">draw_filled_polygon</a></h2>
</div>
:::supervision.draw.utils.draw_filled_polygon
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_text">draw_text</a></h2>
</div>
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Image Utils
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Video Utils
+73 -64
View File
@@ -16,67 +16,76 @@ https://github.com/roboflow/supervision/assets/26109316/f84db7b5-79e2-4142-a1da-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/count_people_in_zone
```
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/count_people_in_zone
```
- setup python environment and activate it [optional]
- setup python environment and activate it \[optional\]
```bash
python3 -m venv venv
source venv/bin/activate
```
```bash
python3 -m venv venv
source venv/bin/activate
```
- install required dependencies
```bash
pip install -r requirements.txt
```
```bash
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.
- `--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.
- `--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`.
- `--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.
- `--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`.
- `--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`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default
value is `"yolov8x-1280"`.
- `--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`.
- `--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.
- `--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`.
- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold
for the model. Default is `0.7`.
- `--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.
- `--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.
- `--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`.
## 📌 zone configuration
@@ -89,35 +98,35 @@ 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.
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.
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.
+19 -20
View File
@@ -6,7 +6,6 @@ This script performs heatmap and tracking analysis using YOLOv8, an object-detec
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
@@ -16,7 +15,7 @@ supervision package for multiple tasks such as drawing heatmap annotations, trac
cd supervision/examples/heatmap_and_track
```
- setup python environment and activate it [optional]
- setup python environment and activate it \[optional\]
```bash
python3 -m venv venv
@@ -32,17 +31,17 @@ supervision package for multiple tasks such as drawing heatmap annotations, trac
## 🛠️ 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.
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
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.
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.
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.
@@ -53,11 +52,11 @@ particularly in distinguishing between different objects.
```bash
python script.py \
--source_weights_path weight.pt \
--source_video_path input_video.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
--target_video_path output_video.mp4
--source_weights_path weight.pt \
--source_video_path input_video.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
--target_video_path output_video.mp4
```
## © license
@@ -65,11 +64,11 @@ 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.
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.
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.
+59 -54
View File
@@ -11,7 +11,7 @@ supervision package for multiple tasks such as tracking, annotations, etc.
https://github.com/roboflow/supervision/assets/26109316/d50118c1-2ae4-458d-915a-5d860fd36f71
> [!IMPORTANT]
> \[!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
@@ -21,97 +21,102 @@ https://github.com/roboflow/supervision/assets/26109316/d50118c1-2ae4-458d-915a-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/speed_estimation
```
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/speed_estimation
```
- setup python environment and activate it [optional]
- setup python environment and activate it \[optional\]
```bash
python3.10 -m venv venv
source venv/bin/activate
```
```bash
python3.10 -m venv venv
source venv/bin/activate
```
- install required dependencies
```bash
pip install -r requirements.txt
```
```bash
pip install -r requirements.txt
```
- download `vehicles.mp4` file
```bash
python3.10 video_downloader.py
```
```bash
python3.10 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`.
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"`.
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.
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.
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.
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.
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.
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
```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
```
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
--iou_threshold 0.5
```
- inference
```bash
```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
```
--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
```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
```
--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.
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.
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.
+114 -111
View File
@@ -15,23 +15,23 @@ https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/time_in_zone
```
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/time_in_zone
```
- setup python environment and activate it [optional]
- setup python environment and activate it \[optional\]
```bash
python3 -m venv venv
source venv/bin/activate
```
```bash
python3 -m venv venv
source venv/bin/activate
```
- install required dependencies
```bash
pip install -r requirements.txt
```
```bash
pip install -r requirements.txt
```
## 🛠 scripts
@@ -45,16 +45,16 @@ This script allows you to download a video from YouTube.
```bash
python scripts/download_from_youtube.py \
--url "https://www.youtube.com/watch?v=-8zyEwAa50Q" \
--output_path "data/checkout" \
--file_name "video.mp4"
--url "https://www.youtube.com/watch?v=-8zyEwAa50Q" \
--output_path "data/checkout" \
--file_name "video.mp4"
```
```bash
python scripts/download_from_youtube.py \
--url "https://www.youtube.com/watch?v=MNn9qKG2UFI" \
--output_path "data/traffic" \
--file_name "video.mp4"
--url "https://www.youtube.com/watch?v=MNn9qKG2UFI" \
--output_path "data/traffic" \
--file_name "video.mp4"
```
### `stream_from_file`
@@ -68,14 +68,14 @@ mock a live video stream for local testing. Video will be streamed in a loop und
```bash
python scripts/stream_from_file.py \
--video_directory "data/checkout" \
--number_of_streams 1
--video_directory "data/checkout" \
--number_of_streams 1
```
```bash
python scripts/stream_from_file.py \
--video_directory "data/traffic" \
--number_of_streams 1
--video_directory "data/traffic" \
--number_of_streams 1
```
### `draw_zones`
@@ -86,24 +86,27 @@ window where you can draw polygons on the source image or video file. The polygo
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.
- `enter` - finish drawing the current polygon.
- `escape` - cancel drawing the current polygon.
- `q` - quit the drawing window.
- `s` - save zone configuration to a JSON file.
```bash
python scripts/draw_zones.py \
--source_path "data/checkout/video.mp4" \
--zone_configuration_path "data/checkout/config.json"
--source_path "data/checkout/video.mp4" \
--zone_configuration_path "data/checkout/config.json"
```
```bash
python scripts/draw_zones.py \
--source_path "data/traffic/video.mp4" \
--zone_configuration_path "data/traffic/config.json"
--source_path "data/traffic/video.mp4" \
--zone_configuration_path "data/traffic/config.json"
```
https://github.com/roboflow/supervision/assets/26109316/9d514c9e-2a61-418b-ae49-6ac1ad6ae5ac
@@ -114,33 +117,33 @@ https://github.com/roboflow/supervision/assets/26109316/9d514c9e-2a61-418b-ae49-
Script to run object detection on a video file using the Roboflow Inference model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--source_video_path`: Path to the source video file.
- `--model_id`: Roboflow model ID.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--source_video_path`: Path to the source video file.
- `--model_id`: Roboflow model ID.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--model_id "yolov8x-640" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--model_id "yolov8x-640" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-d38b86334c39
```bash
python inference_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--model_id "yolov8x-640" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--model_id "yolov8x-640" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
https://github.com/roboflow/supervision/assets/26109316/5ec896d7-4b39-4426-8979-11e71666878b
@@ -149,31 +152,31 @@ https://github.com/roboflow/supervision/assets/26109316/5ec896d7-4b39-4426-8979-
Script to run object detection on a video stream using the Roboflow Inference model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--rtsp_url`: Complete RTSP URL for the video stream.
- `--model_id`: Roboflow model ID.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--rtsp_url`: Complete RTSP URL for the video stream.
- `--model_id`: Roboflow model ID.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_stream_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python inference_stream_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
<details>
@@ -183,68 +186,68 @@ python inference_stream_example.py \
Script to run object detection on a video file using the Ultralytics YOLOv8 model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--source_video_path`: Path to the source video file.
- `--weights`: Path to the model weights file. Default is `'yolov8s.pt'`.
- `--device`: Computation device (`'cpu'`, `'mps'` or `'cuda'`). Default is `'cpu'`.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--source_video_path`: Path to the source video file.
- `--weights`: Path to the model weights file. Default is `'yolov8s.pt'`.
- `--device`: Computation device (`'cpu'`, `'mps'` or `'cuda'`). Default is `'cpu'`.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python ultralytics_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python ultralytics_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
### `ultralytics_stream_example`
Script to run object detection on a video stream using the Ultralytics YOLOv8 model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--rtsp_url`: Complete RTSP URL for the video stream.
- `--weights`: Path to the model weights file. Default is `'yolov8s.pt'`.
- `--device`: Computation device (`'cpu'`, `'mps'` or `'cuda'`). Default is `'cpu'`.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--rtsp_url`: Complete RTSP URL for the video stream.
- `--weights`: Path to the model weights file. Default is `'yolov8s.pt'`.
- `--device`: Computation device (`'cpu'`, `'mps'` or `'cuda'`). Default is `'cpu'`.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python ultralytics_stream_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python ultralytics_stream_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
</details>
@@ -254,11 +257,11 @@ 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.
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.
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.
+66 -59
View File
@@ -9,93 +9,100 @@ detection and Supervision for tracking and annotation.
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/tracking
```
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/tracking
```
- setup python environment and activate it [optional]
- setup python environment and activate it \[optional\]
```bash
python3 -m venv venv
source venv/bin/activate
```
```bash
python3 -m venv venv
source venv/bin/activate
```
- install required dependencies
```bash
pip install -r requirements.txt
```
```bash
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.
- `--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.
- `--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.
- `--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.
- `--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.
- 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`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default
value is `"yolov8x-1280"`.
- `--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`.
- `--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.
- `--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.
- `--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.
- `--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.
- `--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.
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.
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.
+75 -68
View File
@@ -12,105 +12,112 @@ https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/traffic_analysis
```
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/traffic_analysis
```
- setup python environment and activate it [optional]
- setup python environment and activate it \[optional\]
```bash
python3 -m venv venv
source venv/bin/activate
```
```bash
python3 -m venv venv
source venv/bin/activate
```
- install required dependencies
```bash
pip install -r requirements.txt
```
```bash
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.
- `--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.
- `--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.
- `--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.
- `--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.
- 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`.
- `--model_id` (optional): Designates the Roboflow model ID to be used. The default
value is `"vehicle-count-in-drone-video/6"`.
- `--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`.
- `--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.
- `--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.
- `--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.
- `--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.
- `--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.
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.
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.
@@ -1,6 +1,6 @@
import argparse
import os
from typing import Dict, Iterable, List, Set
from typing import Dict, Iterable, List, Optional, Set
import cv2
import numpy as np
@@ -77,7 +77,7 @@ class VideoProcessor:
roboflow_api_key: str,
model_id: str,
source_video_path: str,
target_video_path: str = None,
target_video_path: Optional[str] = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
) -> None:
@@ -1,5 +1,5 @@
import argparse
from typing import Dict, Iterable, List, Set
from typing import Dict, Iterable, List, Optional, Set
import cv2
import numpy as np
@@ -74,7 +74,7 @@ class VideoProcessor:
self,
source_weights_path: str,
source_video_path: str,
target_video_path: str = None,
target_video_path: Optional[str] = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
) -> None:
+1
View File
@@ -66,6 +66,7 @@ nav:
- Utils: datasets/utils.md
- Metrics:
- mAP: metrics/mean_average_precision.md
- F1 Score: metrics/f1_score.md
- Legacy Metrics: detection/metrics.md
- Utils:
- Video: utils/video.md
Generated
+723 -691
View File
File diff suppressed because it is too large Load Diff
+17 -27
View File
@@ -1,15 +1,18 @@
[tool.poetry]
name = "supervision"
version = "0.23.0"
version = "0.24.0"
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
authors = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
maintainers = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
maintainers = [
"Piotr Skalski <piotr.skalski92@gmail.com>",
"Linas Kondrackis <linas@roboflow.com>",
]
readme = "README.md"
license = "MIT"
packages = [{ include = "supervision" }]
homepage = "https://github.com/roboflow/supervision"
repository = "https://github.com/roboflow/supervision"
documentation = "https://github.com/roboflow/supervision/blob/main/README.md"
documentation = "https://supervision.roboflow.com/latest/"
keywords = [
"machine-learning",
"deep-learning",
@@ -52,16 +55,14 @@ matplotlib = ">=3.6.0"
pyyaml = ">=5.3"
defusedxml = "^0.7.1"
pillow = ">=9.4"
opencv-python = { version = ">=4.5.5.64", optional = true }
opencv-python-headless = ">=4.5.5.64"
requests = { version = ">=2.26.0,<=2.32.3", optional = true }
tqdm = { version = ">=4.62.3,<=4.66.5", optional = true }
# pandas: picked lowest major version that supports Python 3.8
pandas = { version = ">=2.0.0", optional = true }
pandas-stubs = { version = ">=2.0.0.230412", optional = true }
opencv-python = ">=4.5.5.64"
[tool.poetry.extras]
desktop = ["opencv-python"]
assets = ["requests", "tqdm"]
metrics = ["pandas", "pandas-stubs"]
@@ -85,12 +86,13 @@ docutils = [
[tool.poetry.group.docs.dependencies]
mkdocs-material = { extras = ["imaging"], version = "^9.5.5" }
mkdocstrings = { extras = ["python"], version = "^0.25.2" }
mkdocstrings = ">=0.25.2,<0.27.0"
mkdocstrings-python = "^1.10.9"
mike = "^2.0.0"
# For Documentation Development use Python 3.10 or above
# Use Latest mkdocs-jupyter min 0.24.6 for Jupyter Notebook Theme support
mkdocs-jupyter = "^0.24.3"
mkdocs-git-committers-plugin-2 = "^2.2.3"
mkdocs-git-committers-plugin-2 = "^2.4.1"
mkdocs-git-revision-date-localized-plugin = "^1.2.4"
[tool.poetry.group.typecheck]
@@ -111,24 +113,6 @@ tests = ["B201", "B301", "B318", "B314", "B303", "B413", "B412", "B410"]
check = true
imports = ["cv2", "supervision"]
[tool.black]
target-version = ["py38"]
line-length = 88
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| docs
)/
'''
[tool.ruff]
target-version = "py38"
@@ -166,7 +150,7 @@ indent-width = 4
[tool.ruff.lint]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F", "I", "A", "Q", "W"]
select = ["E", "F", "I", "A", "Q", "W", "RUF"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = [
@@ -252,6 +236,12 @@ skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
[tool.codespell]
skip = "*.ipynb,poetry.lock"
count = true
quiet-level = 3
ignore-words-list = "STrack,sTrack,strack"
[tool.setuptools]
include-package-data = false
+12 -12
View File
@@ -6,19 +6,19 @@ It assumes you already have the code changes, as well as a draft of the release
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.
- 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 tag, the [PyPi](https://pypi.org/project/supervision/) should update to the new version. Check this!
- 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 tag, 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.
- 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.
- Go to releases
- Assign the release notes to the tag created in step 3.
- Publish the release.
+7 -1
View File
@@ -40,7 +40,11 @@ from supervision.dataset.core import (
)
from supervision.dataset.utils import mask_to_rle, rle_to_mask
from supervision.detection.core import Detections
from supervision.detection.line_zone import LineZone, LineZoneAnnotator
from supervision.detection.line_zone import (
LineZone,
LineZoneAnnotator,
LineZoneAnnotatorMulticlass,
)
from supervision.detection.lmm import LMM
from supervision.detection.overlap_filter import (
OverlapFilter,
@@ -65,6 +69,7 @@ from supervision.detection.utils import (
mask_to_xyxy,
move_boxes,
move_masks,
oriented_box_iou_batch,
pad_boxes,
polygon_to_mask,
polygon_to_xyxy,
@@ -76,6 +81,7 @@ from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import (
calculate_optimal_line_thickness,
calculate_optimal_text_scale,
draw_filled_polygon,
draw_filled_rectangle,
draw_image,
draw_line,
+2 -1
View File
@@ -68,7 +68,8 @@ class BoxAnnotator(BaseAnnotator):
Args:
scene (ImageType): The image where bounding boxes will be drawn. `ImageType`
is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`.
is a flexible type, accepting either `numpy.ndarray` or
`PIL.Image.Image`.
detections (Detections): Object detections to annotate.
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
Allows to override the default color mapping strategy.
+5 -2
View File
@@ -51,14 +51,17 @@ def resolve_color_idx(
if detections.class_id is None:
raise ValueError(
"Could not resolve color by class because "
"Detections do not have class_id"
"Detections do not have class_id. If using an annotator, "
"try setting color_lookup to sv.ColorLookup.INDEX or "
"sv.ColorLookup.TRACK."
)
return detections.class_id[detection_idx]
elif color_lookup == ColorLookup.TRACK:
if detections.tracker_id is None:
raise ValueError(
"Could not resolve color by track because "
"Detections do not have tracker_id"
"Detections do not have tracker_id. Did you call "
"tracker.update_with_detections(...) before annotating?"
)
return detections.tracker_id[detection_idx]
+124 -10
View File
@@ -6,7 +6,10 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
import numpy as np
from supervision.config import CLASS_NAME_DATA_FIELD, ORIENTED_BOX_COORDINATES
from supervision.config import (
CLASS_NAME_DATA_FIELD,
ORIENTED_BOX_COORDINATES,
)
from supervision.detection.lmm import (
LMM,
from_florence_2,
@@ -45,7 +48,7 @@ class Detections:
The `sv.Detections` class in the Supervision library standardizes results from
various object detection and segmentation models into a consistent format. This
class simplifies data manipulation and filtering, providing a uniform API for
integration with Supervision [trackers](/trackers/), [annotators](/detection/annotators/), and [tools](/detection/tools/line_zone/).
integration with Supervision [trackers](/trackers/), [annotators](/latest/detection/annotators/), and [tools](/detection/tools/line_zone/).
=== "Inference"
@@ -249,7 +252,7 @@ class Detections:
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```
""" # noqa: E501 // docs
"""
if hasattr(ultralytics_results, "obb") and ultralytics_results.obb is not None:
class_id = ultralytics_results.obb.cls.cpu().numpy().astype(int)
@@ -356,7 +359,7 @@ class Detections:
result = model(img)
detections = sv.Detections.from_tensorflow(result)
```
""" # noqa: E501 // docs
"""
boxes = tensorflow_results["detection_boxes"][0].numpy()
boxes[:, [0, 2]] *= resolution_wh[0]
@@ -431,7 +434,7 @@ class Detections:
result = inference_detector(model, image)
detections = sv.Detections.from_mmdetection(result)
```
""" # noqa: E501 // docs
"""
return cls(
xyxy=mmdet_results.pred_instances.bboxes.cpu().numpy(),
@@ -490,7 +493,7 @@ class Detections:
id2label=model.config.id2label
)
```
""" # noqa: E501 // docs
"""
if (
transformers_results.__class__.__name__ == "Tensor"
@@ -514,14 +517,21 @@ class Detections:
**process_transformers_detection_result(transformers_results, id2label)
)
else:
raise ValueError(
"The provided Transformers results do not contain any valid fields."
" Expected fields are 'boxes', 'masks', 'segments_info' or"
" 'segmentation'."
)
@classmethod
def from_detectron2(cls, detectron2_results) -> Detections:
def from_detectron2(cls, detectron2_results: Any) -> Detections:
"""
Create a Detections object from the
[Detectron2](https://github.com/facebookresearch/detectron2) inference result.
Args:
detectron2_results: The output of a
detectron2_results (Any): The output of a
Detectron2 model containing instances with prediction data.
Returns:
@@ -782,7 +792,7 @@ class Detections:
@classmethod
def from_lmm(
cls, lmm: Union[LMM, str], result: Union[str, dict], **kwargs
cls, lmm: Union[LMM, str], result: Union[str, dict], **kwargs: Any
) -> Detections:
"""
Creates a Detections object from the given result string based on the specified
@@ -791,7 +801,7 @@ class Detections:
Args:
lmm (Union[LMM, str]): The type of LMM (Large Multimodal Model) to use.
result (str): The result string containing the detection data.
**kwargs: Additional keyword arguments required by the specified LMM.
**kwargs (Any): Additional keyword arguments required by the specified LMM.
Returns:
Detections: A new Detections object.
@@ -843,6 +853,110 @@ class Detections:
raise ValueError(f"Unsupported LMM: {lmm}")
@classmethod
def from_easyocr(cls, easyocr_results: list) -> Detections:
"""
Create a Detections object from the
[EasyOCR](https://github.com/JaidedAI/EasyOCR) result.
Results are placed in the `data` field with the key `"class_name"`.
Args:
easyocr_results (List): The output Results instance from EasyOCR
Returns:
Detections: A new Detections object.
Example:
```python
import supervision as sv
import easyocr
reader = easyocr.Reader(['en'])
results = reader.readtext(<SOURCE_IMAGE_PATH>)
detections = sv.Detections.from_easyocr(results)
detected_text = detections["class_name"]
```
"""
if len(easyocr_results) == 0:
return cls.empty()
bbox = np.array([result[0] for result in easyocr_results])
xyxy = np.hstack((np.min(bbox, axis=1), np.max(bbox, axis=1)))
confidence = np.array(
[
result[2] if len(result) > 2 and result[2] else 0
for result in easyocr_results
]
)
ocr_text = np.array([result[1] for result in easyocr_results])
return cls(
xyxy=xyxy.astype(np.float32),
confidence=confidence.astype(np.float32),
data={
CLASS_NAME_DATA_FIELD: ocr_text,
},
)
@classmethod
def from_ncnn(cls, ncnn_results) -> Detections:
"""
Creates a Detections instance from the
[ncnn](https://github.com/Tencent/ncnn) inference result.
Supports object detection models.
Arguments:
ncnn_results (dict): The output Results instance from ncnn.
Returns:
Detections: A new Detections object.
Example:
```python
import cv2
from ncnn.model_zoo import get_model
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(
"yolov8s",
target_size=640
prob_threshold=0.5,
nms_threshold=0.45,
num_threads=4,
use_gpu=True,
)
result = model(image)
detections = sv.Detections.from_ncnn(result)
```
"""
xywh, confidences, class_ids = [], [], []
if len(ncnn_results) == 0:
return cls.empty()
for ncnn_result in ncnn_results:
rect = ncnn_result.rect
xywh.append(
[
rect.x.astype(np.float32),
rect.y.astype(np.float32),
rect.w.astype(np.float32),
rect.h.astype(np.float32),
]
)
confidences.append(ncnn_result.prob)
class_ids.append(ncnn_result.label)
return cls(
xyxy=xywh_to_xyxy(np.array(xywh, dtype=np.float32)),
confidence=np.array(confidences, dtype=np.float32),
class_id=np.array(class_ids, dtype=int),
)
@classmethod
def empty(cls) -> Detections:
"""
+543 -91
View File
@@ -1,16 +1,23 @@
import math
import warnings
from typing import Dict, Iterable, Optional, Tuple
from collections import Counter
from functools import lru_cache
from typing import Any, Dict, Iterable, List, Literal, Optional, Tuple
import cv2
import numpy as np
from supervision.config import CLASS_NAME_DATA_FIELD
from supervision.detection.core import Detections
from supervision.detection.utils import cross_product
from supervision.draw.color import Color
from supervision.draw.utils import draw_text
from supervision.geometry.core import Point, Position, Vector
from supervision.draw.utils import draw_rectangle, draw_text
from supervision.geometry.core import Point, Position, Rect, Vector
from supervision.utils.image import overlay_image
from supervision.utils.internal import SupervisionWarnings
TEXT_MARGIN = 10
class LineZone:
"""
@@ -55,7 +62,7 @@ class LineZone:
line_zone.in_count, line_zone.out_count
# 7, 2
```
""" # noqa: E501 // docs
"""
def __init__(
self,
@@ -81,11 +88,44 @@ class LineZone:
self.vector = Vector(start=start, end=end)
self.limits = self.calculate_region_of_interest_limits(vector=self.vector)
self.tracker_state: Dict[str, bool] = {}
self.in_count: int = 0
self.out_count: int = 0
self._in_count_per_class: Counter = Counter()
self._out_count_per_class: Counter = Counter()
self.triggering_anchors = triggering_anchors
if not list(self.triggering_anchors):
raise ValueError("Triggering anchors cannot be empty.")
self.class_id_to_name: Dict[int, str] = {}
@property
def in_count(self) -> int:
"""
Number of objects that have crossed the line from
outside to inside.
"""
return sum(self._in_count_per_class.values())
@property
def out_count(self) -> int:
"""
Number of objects that have crossed the line from
inside to outside.
"""
return sum(self._out_count_per_class.values())
@property
def in_count_per_class(self) -> Dict[int, int]:
"""
Number of objects of each class that have crossed
the line from outside to inside.
"""
return dict(self._in_count_per_class)
@property
def out_count_per_class(self) -> Dict[int, int]:
"""
Number of objects of each class that have crossed the line
from inside to outside.
"""
return dict(self._out_count_per_class)
@staticmethod
def calculate_region_of_interest_limits(vector: Vector) -> Tuple[Vector, Vector]:
@@ -170,7 +210,22 @@ class LineZone:
has_any_left_trigger = np.any(triggers, axis=0)
has_any_right_trigger = np.any(~triggers, axis=0)
is_uniformly_triggered = ~(has_any_left_trigger & has_any_right_trigger)
for i, tracker_id in enumerate(detections.tracker_id):
class_ids = (
list(detections.class_id)
if detections.class_id is not None
else [None] * len(detections)
)
tracker_ids = list(detections.tracker_id)
if CLASS_NAME_DATA_FIELD in detections.data:
class_names = detections.data[CLASS_NAME_DATA_FIELD]
for class_id, class_name in zip(class_ids, class_names):
if class_id is None:
class_name = "No class"
self.class_id_to_name[class_id] = class_name
for i, (class_ids, tracker_id) in enumerate(zip(class_ids, tracker_ids)):
if not in_limits[i]:
continue
@@ -187,10 +242,10 @@ class LineZone:
self.tracker_state[tracker_id] = tracker_state
if tracker_state:
self.in_count += 1
self._in_count_per_class[class_ids] += 1
crossed_in[i] = True
else:
self.out_count += 1
self._out_count_per_class[class_ids] += 1
crossed_out[i] = True
return crossed_in, crossed_out
@@ -199,9 +254,9 @@ class LineZone:
class LineZoneAnnotator:
def __init__(
self,
thickness: float = 2,
thickness: int = 2,
color: Color = Color.WHITE,
text_thickness: float = 2,
text_thickness: int = 2,
text_color: Color = Color.BLACK,
text_scale: float = 0.5,
text_offset: float = 1.5,
@@ -210,86 +265,67 @@ class LineZoneAnnotator:
custom_out_text: Optional[str] = None,
display_in_count: bool = True,
display_out_count: bool = True,
display_text_box: bool = True,
text_orient_to_line: bool = False,
text_centered: bool = True,
):
"""
Initialize the LineCounterAnnotator object with default values.
A class for drawing the `LineZone` and its detected object count
on an image.
Attributes:
thickness (float): The thickness of the line that will be drawn.
color (Color): The color of the line that will be drawn.
text_thickness (float): The thickness of the text that will be drawn.
text_color (Color): The color of the text that will be drawn.
text_scale (float): The scale of the text that will be drawn.
text_offset (float): The offset of the text that will be drawn.
text_padding (int): The padding of the text that will be drawn.
display_in_count (bool): Whether to display the in count or not.
display_out_count (bool): Whether to display the out count or not.
thickness (int): Line thickness.
color (Color): Line color.
text_thickness (int): Text thickness.
text_color (Color): Text color.
text_scale (float): Text scale.
text_offset (float): How far the text will be from the line.
text_padding (int): The empty space in the text box, surrounding the text.
custom_in_text (Optional[str]): Write something else instead of "in".
custom_out_text (Optional[str]): Write something else instead of "out".
display_in_count (bool): Pass `False` to hide the "in" count.
display_out_count (bool): Pass `False` to hide the "out" count.
display_text_box (bool): Pass `False` to hide the text background box.
text_orient_to_line (bool): ⭐ Match text orientation to the line.
Recommended to set to `True`.
text_centered (bool): Pass `False` to disable text centering. Useful
when the label overlaps something important.
"""
self.thickness: float = thickness
self.thickness: int = thickness
self.color: Color = color
self.text_thickness: float = text_thickness
self.text_thickness: int = text_thickness
self.text_color: Color = text_color
self.text_scale: float = text_scale
self.text_offset: float = text_offset
self.text_padding: int = text_padding
self.custom_in_text: str = custom_in_text
self.custom_out_text: str = custom_out_text
self.in_text: str = custom_in_text if custom_in_text else "in"
self.out_text: str = custom_out_text if custom_out_text else "out"
self.display_in_count: bool = display_in_count
self.display_out_count: bool = display_out_count
def _annotate_count(
self,
frame: np.ndarray,
center_text_anchor: Point,
text: str,
is_in_count: bool,
) -> None:
"""This method is drawing the text on the frame.
Args:
frame (np.ndarray): The image on which the text will be drawn.
center_text_anchor: The center point that the text will be drawn.
text (str): The text that will be drawn.
is_in_count (bool): Whether to display the in count or out count.
"""
_, text_height = cv2.getTextSize(
text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
)[0]
if is_in_count:
center_text_anchor.y -= int(self.text_offset * text_height)
else:
center_text_anchor.y += int(self.text_offset * text_height)
draw_text(
scene=frame,
text=text,
text_anchor=center_text_anchor,
text_color=self.text_color,
text_scale=self.text_scale,
text_thickness=self.text_thickness,
text_padding=self.text_padding,
background_color=self.color,
)
self.display_text_box: bool = display_text_box
self.text_orient_to_line: bool = text_orient_to_line
self.text_centered: bool = text_centered
def annotate(self, frame: np.ndarray, line_counter: LineZone) -> np.ndarray:
"""
Draws the line on the frame using the line_counter provided.
Draws the line on the frame using the line zone provided.
Attributes:
frame (np.ndarray): The image on which the line will be drawn.
line_counter (LineCounter): The line counter
line_counter (LineZone): The line zone
that will be used to draw the line.
Returns:
np.ndarray: The image with the line drawn on it.
(np.ndarray): The image with the line drawn on it.
"""
line_start = line_counter.vector.start.as_xy_int_tuple()
line_end = line_counter.vector.end.as_xy_int_tuple()
cv2.line(
frame,
line_counter.vector.start.as_xy_int_tuple(),
line_counter.vector.end.as_xy_int_tuple(),
line_start,
line_end,
self.color.as_bgr(),
self.thickness,
lineType=cv2.LINE_AA,
@@ -297,7 +333,7 @@ class LineZoneAnnotator:
)
cv2.circle(
frame,
line_counter.vector.start.as_xy_int_tuple(),
line_start,
radius=5,
color=self.text_color.as_bgr(),
thickness=-1,
@@ -305,40 +341,456 @@ class LineZoneAnnotator:
)
cv2.circle(
frame,
line_counter.vector.end.as_xy_int_tuple(),
line_end,
radius=5,
color=self.text_color.as_bgr(),
thickness=-1,
lineType=cv2.LINE_AA,
)
text_anchor = Vector(
start=line_counter.vector.start, end=line_counter.vector.end
in_text = f"{self.in_text}: {line_counter.in_count}"
out_text = f"{self.out_text}: {line_counter.out_count}"
line_angle_degrees = self._get_line_angle(line_counter)
for text, is_shown, is_in_count in [
(in_text, self.display_in_count, True),
(out_text, self.display_out_count, False),
]:
if not is_shown:
continue
if line_angle_degrees == 0 or not self.text_orient_to_line:
self._draw_basic_label(
frame=frame,
line_center=line_counter.vector.center,
text=text,
is_in_count=is_in_count,
)
else:
self._draw_oriented_label(
frame=frame,
line_zone=line_counter,
text=text,
is_in_count=is_in_count,
)
return frame
def _get_line_angle(self, line_zone: LineZone) -> float:
"""
Calculate the line counter angle (in degrees).
Args:
line_zone (LineZone): The line zone object.
Returns:
(float): Line counter angle, in degrees.
"""
start_point = line_zone.vector.start.as_xy_int_tuple()
end_point = line_zone.vector.end.as_xy_int_tuple()
delta_x = end_point[0] - start_point[0]
delta_y = end_point[1] - start_point[1]
if delta_x == 0:
line_angle = 90.0
line_angle += 180 if delta_y < 0 else 0
else:
line_angle = math.degrees(math.atan(delta_y / delta_x))
line_angle += 180 if delta_x < 0 else 0
return line_angle
def _calculate_anchor_in_frame(
self,
line_zone: LineZone,
text_width: int,
text_height: int,
is_in_count: bool,
label_dimension: int,
) -> Tuple[int, int]:
"""
Calculate insertion anchor in frame to position the center of the count image.
Args:
line_zone (LineZone): The line counter object used for counting.
text_width (int): Text width.
text_height (int): Text height.
is_in_count (bool): Whether the count should be placed over or below line.
label_dimension (int): Size of the label image. Assumes the
label is rectangular.
Returns:
(Tuple[int, int]): xy, point in an image where the label will be placed.
"""
line_angle = self._get_line_angle(line_zone)
if self.text_centered:
mid_point = Vector(
start=line_zone.vector.start, end=line_zone.vector.end
).center.as_xy_int_tuple()
anchor = list(mid_point)
else:
end_point = line_zone.vector.end.as_xy_int_tuple()
anchor = list(end_point)
move_along_x = int(
math.cos(math.radians(line_angle))
* (text_width / 2 + self.text_padding)
)
move_along_y = int(
math.sin(math.radians(line_angle))
* (text_width / 2 + self.text_padding)
)
anchor[0] -= move_along_x
anchor[1] -= move_along_y
move_perpendicular_x = int(
math.sin(math.radians(line_angle)) * (self.text_offset * text_height)
)
move_perpendicular_y = int(
math.cos(math.radians(line_angle)) * (self.text_offset * text_height)
)
if self.display_in_count:
in_text = (
f"{self.custom_in_text}: {line_counter.in_count}"
if self.custom_in_text is not None
else f"in: {line_counter.in_count}"
)
self._annotate_count(
frame=frame,
center_text_anchor=text_anchor.center,
text=in_text,
is_in_count=True,
if is_in_count:
anchor[0] += move_perpendicular_x
anchor[1] -= move_perpendicular_y
else:
anchor[0] -= move_perpendicular_x
anchor[1] += move_perpendicular_y
x1 = max(anchor[0] - label_dimension // 2, 0)
y1 = max(anchor[1] - label_dimension // 2, 0)
return x1, y1
def _draw_basic_label(
self,
frame: np.ndarray,
line_center: Point,
text: str,
is_in_count: bool,
) -> np.ndarray:
"""
Draw the count label on the frame. For example: "out: 7".
The label contains horizontal text and is not rotated.
Args:
frame (np.ndarray): The entire scene, on which the label will be placed.
line_center (Point): The center of the line zone.
text (str): The text that will be drawn.
is_in_count (bool): Whether to display the in count (above line)
or out count (below line).
Returns:
(np.ndarray): The scene with the label drawn on it.
"""
_, text_height = cv2.getTextSize(
text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
)[0]
if is_in_count:
line_center.y -= int(self.text_offset * text_height)
else:
line_center.y += int(self.text_offset * text_height)
draw_text(
scene=frame,
text=text,
text_anchor=line_center,
text_color=self.text_color,
text_scale=self.text_scale,
text_thickness=self.text_thickness,
text_padding=self.text_padding,
background_color=self.color if self.display_text_box else None,
)
return frame
def _draw_oriented_label(
self,
frame: np.ndarray,
line_zone: LineZone,
text: str,
is_in_count: bool,
) -> np.ndarray:
"""
Draw the count label on the frame. For example: "out: 7".
The label is oriented to match the line angle.
Args:
frame (np.ndarray): The entire scene, on which the label will be placed.
line_zone (LineZone): The line zone responsible for counting
objects crossing it.
text (str): The text that will be drawn.
is_in_count (bool): Whether to display the in count (above line)
or out count (below line).
Returns:
(np.ndarray): The scene with the label drawn on it.
"""
line_angle_degrees = self._get_line_angle(line_zone)
label_image = self._make_label_image(
text,
text_scale=self.text_scale,
text_thickness=self.text_thickness,
text_padding=self.text_padding,
text_color=self.text_color,
text_box_show=self.display_text_box,
text_box_color=self.color,
line_angle_degrees=line_angle_degrees,
)
assert label_image.shape[0] == label_image.shape[1]
text_width, text_height = cv2.getTextSize(
text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
)[0]
label_anchor = self._calculate_anchor_in_frame(
line_zone=line_zone,
text_width=text_width,
text_height=text_height,
is_in_count=is_in_count,
label_dimension=label_image.shape[0],
)
frame = overlay_image(frame, label_image, label_anchor)
return frame
@staticmethod
@lru_cache(maxsize=32)
def _make_label_image(
text: str,
*,
text_scale: float,
text_thickness: int,
text_padding: int,
text_color: Color,
text_box_show: bool,
text_box_color: Color,
line_angle_degrees: float,
) -> np.ndarray:
"""
Create the small text box displaying line zone count. E.g. "out: 7".
Args:
text (str): The text to display.
text_scale (float): The scale of the text.
text_thickness (int): The thickness of the text.
text_padding (int): The padding around the text.
text_color (Color): The color of the text.
text_box_show (bool): Whether to display the text box.
text_box_color (Color): The color of the text box.
line_angle_degrees (float): The angle of the line in degrees.
Returns:
(np.ndarray): The label of shape (H, W, 4), in BGRA format.
"""
text_width, text_height = cv2.getTextSize(
text, cv2.FONT_HERSHEY_SIMPLEX, text_scale, text_thickness
)[0]
annotation_dim = int((max(text_width, text_height) + text_padding * 2) * 1.5)
annotation_shape = (annotation_dim, annotation_dim)
annotation_center = Point(annotation_dim // 2, annotation_dim // 2)
annotation = np.zeros((*annotation_shape, 3), dtype=np.uint8)
annotation_alpha = np.zeros((*annotation_shape, 1), dtype=np.uint8)
text_args: Dict[str, Any] = dict(
text=text,
text_anchor=annotation_center,
text_scale=text_scale,
text_thickness=text_thickness,
text_padding=text_padding,
)
draw_text(
scene=annotation,
text_color=text_color,
background_color=text_box_color if text_box_show else None,
**text_args,
)
draw_text(
scene=annotation_alpha,
text_color=Color.WHITE,
background_color=Color.WHITE if text_box_show else None,
**text_args,
)
annotation = np.dstack((annotation, annotation_alpha))
# Make sure text is displayed upright
if 90 < line_angle_degrees % 360 < 270:
annotation = cv2.flip(annotation, flipCode=-1).astype(np.uint8)
rotation_angle = -line_angle_degrees
rotation_matrix = cv2.getRotationMatrix2D(
annotation_center.as_xy_float_tuple(), rotation_angle, scale=1
)
annotation = cv2.warpAffine(annotation, rotation_matrix, annotation_shape)
return annotation
class LineZoneAnnotatorMulticlass:
def __init__(
self,
*,
table_position: Literal[
Position.TOP_LEFT,
Position.TOP_RIGHT,
Position.BOTTOM_LEFT,
Position.BOTTOM_RIGHT,
] = Position.TOP_RIGHT,
table_color: Color = Color.WHITE,
table_margin: int = 10,
table_padding: int = 10,
table_max_width: int = 400,
text_color: Color = Color.BLACK,
text_scale: float = 0.75,
text_thickness: int = 1,
force_draw_class_ids: bool = False,
):
"""
Draw a table showing how many items of each class crossed each line.
Args:
table_position (Position): The position of the table.
table_color (Color): The color of the table.
table_margin (int): The margin of the table from the image border.
table_padding (int): The padding of the table.
table_max_width (int): The maximum width of the table.
text_color (Color): The color of the text.
text_scale (float): The scale of the text.
text_thickness (int): The thickness of the text.
force_draw_class_ids (bool): Instead of writing the class names,
on the table, write the class IDs. E.g. instead of `person: 6`,
write `0: 6`.
"""
if table_position not in {
Position.TOP_LEFT,
Position.TOP_RIGHT,
Position.BOTTOM_LEFT,
Position.BOTTOM_RIGHT,
}:
raise ValueError(
"Invalid table position. Supported values are:"
" TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT."
)
if self.display_out_count:
out_text = (
f"{self.custom_out_text}: {line_counter.out_count}"
if self.custom_out_text is not None
else f"out: {line_counter.out_count}"
)
self._annotate_count(
frame=frame,
center_text_anchor=text_anchor.center,
text=out_text,
is_in_count=False,
self.table_position = table_position
self.table_color = table_color
self.table_margin = table_margin
self.table_padding = table_padding
self.table_max_width = table_max_width
self.text_color = text_color
self.text_scale = text_scale
self.text_thickness = text_thickness
self.force_draw_class_ids = force_draw_class_ids
def annotate(
self,
frame: np.ndarray,
line_zones: List[LineZone],
line_zone_labels: Optional[List[str]] = None,
) -> np.ndarray:
"""
Draws a table with the number of objects of each class that crossed each line.
Attributes:
frame (np.ndarray): The image on which the table will be drawn.
line_zones (List[LineZone]): The line zones to be annotated.
line_zone_labels (Optional[List[str]]): The labels, one for each
line zone. If not provided, the default labels will be used.
Returns:
(np.ndarray): The image with the table drawn on it.
"""
if line_zone_labels is None:
line_zone_labels = [f"Line {i + 1}:" for i in range(len(line_zones))]
if len(line_zones) != len(line_zone_labels):
raise ValueError("The number of line zones and their labels must match.")
text_lines = ["Line Crossings:"]
for line_zone, line_zone_label in zip(line_zones, line_zone_labels):
text_lines.append(line_zone_label)
class_id_to_name = line_zone.class_id_to_name
for direction, count_per_class in [
("In", line_zone.in_count_per_class),
("Out", line_zone.out_count_per_class),
]:
if not count_per_class:
continue
text_lines.append(f" {direction}:")
for class_id, count in count_per_class.items():
class_name = (
class_id_to_name.get(class_id, str(class_id))
if not self.force_draw_class_ids
else str(class_id)
)
text_lines.append(f" {class_name}: {count}")
table_width, table_height = 0, 0
for line in text_lines:
text_width, text_height = cv2.getTextSize(
line, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
)[0]
text_height += TEXT_MARGIN
table_width = max(table_width, text_width)
table_height += text_height
table_width += 2 * self.table_padding
table_height += 2 * self.table_padding
table_max_height = frame.shape[0] - 2 * self.table_margin
table_height = min(table_height, table_max_height)
table_width = min(table_width, self.table_max_width)
position_map = {
Position.TOP_LEFT: (self.table_margin, self.table_margin),
Position.TOP_RIGHT: (
frame.shape[1] - table_width - self.table_margin,
self.table_margin,
),
Position.BOTTOM_LEFT: (
self.table_margin,
frame.shape[0] - table_height - self.table_margin,
),
Position.BOTTOM_RIGHT: (
frame.shape[1] - table_width - self.table_margin,
frame.shape[0] - table_height - self.table_margin,
),
}
table_x1, table_y1 = position_map[self.table_position]
table_rect = Rect(
x=table_x1, y=table_y1, width=table_width, height=table_height
)
frame = draw_rectangle(
scene=frame, rect=table_rect, color=self.table_color, thickness=-1
)
for i, line in enumerate(text_lines):
_, text_height = cv2.getTextSize(
line, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
)[0]
text_height += TEXT_MARGIN
anchor_x = table_x1 + self.table_padding
anchor_y = table_y1 + self.table_padding + (i + 1) * text_height
cv2.putText(
img=frame,
text=line,
org=(anchor_x, anchor_y),
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=self.text_scale,
color=self.text_color.as_bgr(),
thickness=self.text_thickness,
lineType=cv2.LINE_AA,
)
return frame
+1 -1
View File
@@ -113,7 +113,7 @@ def from_florence_2(
oriented bounding boxes.
"""
assert len(result) == 1, f"Expected result with a single element. Got: {result}"
task = list(result.keys())[0]
task = next(iter(result.keys()))
if task not in SUPPORTED_TASKS_FLORENCE_2:
raise ValueError(
f"{task} not supported. Supported tasks are: {SUPPORTED_TASKS_FLORENCE_2}"
+2 -2
View File
@@ -66,7 +66,7 @@ def mask_non_max_suppression(
Raises:
AssertionError: If `iou_threshold` is not within the closed
range from `0` to `1`.
range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
@@ -183,7 +183,7 @@ def group_overlapping_boxes(
ious = ious.flatten()
above_threshold = ious >= iou_threshold
merge_group = [idx] + np.flip(order[above_threshold]).tolist()
merge_group = [idx, *np.flip(order[above_threshold]).tolist()]
merge_groups.append(merge_group)
order = order[~above_threshold]
return merge_groups
+3 -3
View File
@@ -48,7 +48,7 @@ class CSVSink:
detections = sv.Detections.from_ultralytics(result)
sink.append(detections, custom_data={'<CUSTOM_LABEL>':'<CUSTOM_DATA>'})
```
""" # noqa: E501 // docs
"""
def __init__(self, file_name: str = "output.csv") -> None:
"""
@@ -104,7 +104,7 @@ class CSVSink:
@staticmethod
def parse_detection_data(
detections: Detections, custom_data: Dict[str, Any] = None
detections: Detections, custom_data: Optional[Dict[str, Any]] = None
) -> List[Dict[str, Any]]:
parsed_rows = []
for i in range(len(detections.xyxy)):
@@ -137,7 +137,7 @@ class CSVSink:
return parsed_rows
def append(
self, detections: Detections, custom_data: Dict[str, Any] = None
self, detections: Detections, custom_data: Optional[Dict[str, Any]] = None
) -> None:
"""
Append detection data to the CSV file.
@@ -11,7 +11,6 @@ from supervision.detection.utils import move_boxes, move_masks, move_oriented_bo
from supervision.utils.image import crop_image
from supervision.utils.internal import (
SupervisionWarnings,
deprecated_parameter,
warn_deprecated,
)
@@ -60,13 +59,15 @@ class InferenceSlicer:
Args:
slice_wh (Tuple[int, int]): Dimensions of each slice measured in pixels. The
tuple should be in the format `(width, height)`.
overlap_ratio_wh (Optional[Tuple[float, float]]): A tuple representing the
overlap_ratio_wh (Optional[Tuple[float, float]]): [⚠️ Deprecated: please set
to `None` and use `overlap_wh`] A tuple representing the
desired overlap ratio for width and height between consecutive slices.
Each value should be in the range [0, 1), where 0 means no overlap and
a value close to 1 means high overlap.
overlap_wh (Optional[Tuple[int, int]]): A tuple representing the desired
overlap for width and height between consecutive slices measured in pixels.
Each value should be greater than or equal to 0.
Each value should be greater than or equal to 0. Takes precedence over
`overlap_ratio_wh`.
overlap_filter (Union[OverlapFilter, str]): Strategy for
filtering or merging overlapping detections in slices.
iou_threshold (float): Intersection over Union (IoU) threshold
@@ -82,14 +83,6 @@ class InferenceSlicer:
not a multiple of the slice's width or height minus the overlap.
"""
@deprecated_parameter(
old_parameter="overlap_filter_strategy",
new_parameter="overlap_filter",
map_function=lambda x: x,
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be removed in `supervision-0.27.0`. Use '{new_parameter}' "
"instead.",
)
def __init__(
self,
callback: Callable[[np.ndarray], Detections],
@@ -103,7 +96,8 @@ class InferenceSlicer:
if overlap_ratio_wh is not None:
warn_deprecated(
"`overlap_ratio_wh` in `InferenceSlicer.__init__` is deprecated and "
"will be removed in `supervision-0.27.0`. Use `overlap_wh` instead."
"will be removed in `supervision-0.27.0`. Please manually set it to "
"`None` and use `overlap_wh` instead."
)
self._validate_overlap(overlap_ratio_wh, overlap_wh)
+3 -3
View File
@@ -38,7 +38,7 @@ class JSONSink:
detections = sv.Detections.from_ultralytics(result)
sink.append(detections, custom_data={'<CUSTOM_LABEL>':'<CUSTOM_DATA>'})
```
""" # noqa: E501 // docs
"""
def __init__(self, file_name: str = "output.json") -> None:
"""
@@ -92,7 +92,7 @@ class JSONSink:
@staticmethod
def parse_detection_data(
detections: Detections, custom_data: Dict[str, Any] = None
detections: Detections, custom_data: Optional[Dict[str, Any]] = None
) -> List[Dict[str, Any]]:
parsed_rows = []
for i in range(len(detections.xyxy)):
@@ -126,7 +126,7 @@ class JSONSink:
return parsed_rows
def append(
self, detections: Detections, custom_data: Dict[str, Any] = None
self, detections: Detections, custom_data: Optional[Dict[str, Any]] = None
) -> None:
"""
Append detection data to the JSON file.
+25 -19
View File
@@ -1,6 +1,5 @@
import warnings
from dataclasses import replace
from typing import Iterable, Optional, Tuple
from typing import Iterable, Optional
import cv2
import numpy as np
@@ -9,10 +8,9 @@ import numpy.typing as npt
from supervision import Detections
from supervision.detection.utils import clip_boxes, polygon_to_mask
from supervision.draw.color import Color
from supervision.draw.utils import draw_polygon, draw_text
from supervision.draw.utils import draw_filled_polygon, draw_polygon, draw_text
from supervision.geometry.core import Position
from supervision.geometry.utils import get_polygon_center
from supervision.utils.internal import SupervisionWarnings
class PolygonZone:
@@ -33,17 +31,8 @@ class PolygonZone:
def __init__(
self,
polygon: npt.NDArray[np.int64],
frame_resolution_wh: Optional[Tuple[int, int]] = None,
triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
):
if frame_resolution_wh is not None:
warnings.warn(
"The `frame_resolution_wh` parameter is no longer required and will be "
"dropped in version supervision-0.24.0. The mask resolution is now "
"calculated automatically based on the polygon coordinates.",
category=SupervisionWarnings,
)
self.polygon = polygon.astype(int)
self.triggering_anchors = triggering_anchors
if not list(self.triggering_anchors):
@@ -109,6 +98,7 @@ class PolygonZoneAnnotator:
default is cv2.FONT_HERSHEY_SIMPLEX
center (Tuple[int, int]): The center of the polygon for text placement
display_in_zone_count (bool): Show the label of the zone or not. Default is True
opacity: The opacity of zone filling when drawn on the scene. Default is 0
"""
def __init__(
@@ -121,6 +111,7 @@ class PolygonZoneAnnotator:
text_thickness: int = 1,
text_padding: int = 10,
display_in_zone_count: bool = True,
opacity: float = 0,
):
self.zone = zone
self.color = color
@@ -132,6 +123,7 @@ class PolygonZoneAnnotator:
self.font = cv2.FONT_HERSHEY_SIMPLEX
self.center = get_polygon_center(polygon=zone.polygon)
self.display_in_zone_count = display_in_zone_count
self.opacity = opacity
def annotate(self, scene: np.ndarray, label: Optional[str] = None) -> np.ndarray:
"""
@@ -145,12 +137,26 @@ class PolygonZoneAnnotator:
Returns:
np.ndarray: The image with the polygon zone and count of detected objects
"""
annotated_frame = draw_polygon(
scene=scene,
polygon=self.zone.polygon,
color=self.color,
thickness=self.thickness,
)
if self.opacity == 0:
annotated_frame = draw_polygon(
scene=scene,
polygon=self.zone.polygon,
color=self.color,
thickness=self.thickness,
)
else:
annotated_frame = draw_filled_polygon(
scene=scene.copy(),
polygon=self.zone.polygon,
color=self.color,
opacity=self.opacity,
)
annotated_frame = draw_polygon(
scene=annotated_frame,
polygon=self.zone.polygon,
color=self.color,
thickness=self.thickness,
)
if self.display_in_zone_count:
annotated_frame = draw_text(
+1 -1
View File
@@ -53,7 +53,7 @@ class DetectionsSmoother:
annotated_frame = box_annotator.annotate(frame.copy(), detections)
sink.write_frame(annotated_frame)
```
""" # noqa: E501 // docs
"""
def __init__(self, length: int = 5) -> None:
"""
+40 -1
View File
@@ -140,6 +140,45 @@ def mask_iou_batch(
return np.vstack(ious)
def oriented_box_iou_batch(
boxes_true: np.ndarray, boxes_detection: np.ndarray
) -> np.ndarray:
"""
Compute Intersection over Union (IoU) of two sets of oriented bounding boxes -
`boxes_true` and `boxes_detection`. Both sets of boxes are expected to be in
`((x1, y1), (x2, y2), (x3, y3), (x4, y4))` format.
Args:
boxes_true (np.ndarray): a `np.ndarray` representing ground-truth boxes.
`shape = (N, 4, 2)` where `N` is number of true objects.
boxes_detection (np.ndarray): a `np.ndarray` representing detection boxes.
`shape = (M, 4, 2)` where `M` is number of detected objects.
Returns:
np.ndarray: Pairwise IoU of boxes from `boxes_true` and `boxes_detection`.
`shape = (N, M)` where `N` is number of true objects and
`M` is number of detected objects.
"""
boxes_true = boxes_true.reshape(-1, 4, 2)
boxes_detection = boxes_detection.reshape(-1, 4, 2)
max_height = max(boxes_true[:, :, 0].max(), boxes_detection[:, :, 0].max()) + 1
# adding 1 because we are 0-indexed
max_width = max(boxes_true[:, :, 1].max(), boxes_detection[:, :, 1].max()) + 1
mask_true = np.zeros((boxes_true.shape[0], max_height, max_width))
for i, box_true in enumerate(boxes_true):
mask_true[i] = polygon_to_mask(box_true, (max_width, max_height))
mask_detection = np.zeros((boxes_detection.shape[0], max_height, max_width))
for i, box_detection in enumerate(boxes_detection):
mask_detection[i] = polygon_to_mask(box_detection, (max_width, max_height))
ious = mask_iou_batch(mask_true, mask_detection)
return ious
def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
"""
Clips bounding boxes coordinates to fit within the frame resolution.
@@ -147,7 +186,7 @@ def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
Args:
xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each
row corresponds to a bounding box in
the format `(x_min, y_min, x_max, y_max)`.
the format `(x_min, y_min, x_max, y_max)`.
resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
representing the resolution of the frame.
+20
View File
@@ -255,6 +255,17 @@ class Color:
def ROBOFLOW(cls) -> Color:
return Color.from_hex("#A351FB")
def __hash__(self):
return hash((self.r, self.g, self.b))
def __eq__(self, other):
return (
isinstance(other, Color)
and self.r == other.r
and self.g == other.g
and self.b == other.b
)
@dataclass
class ColorPalette:
@@ -386,6 +397,15 @@ class ColorPalette:
idx = idx % len(self.colors)
return self.colors[idx]
def __len__(self) -> int:
"""
Returns the number of colors in the palette.
Returns:
int: The number of colors.
"""
return len(self.colors)
def unify_to_bgr(color: Union[Tuple[int, int, int], Color]) -> Tuple[int, int, int]:
"""
+51 -8
View File
@@ -59,7 +59,9 @@ def draw_rectangle(
return scene
def draw_filled_rectangle(scene: np.ndarray, rect: Rect, color: Color) -> np.ndarray:
def draw_filled_rectangle(
scene: np.ndarray, rect: Rect, color: Color, opacity: float = 1
) -> np.ndarray:
"""
Draws a filled rectangle on an image.
@@ -67,17 +69,32 @@ def draw_filled_rectangle(scene: np.ndarray, rect: Rect, color: Color) -> np.nda
scene (np.ndarray): The scene on which the rectangle will be drawn
rect (Rect): The rectangle to be drawn
color (Color): The color of the rectangle
opacity (float): The opacity of rectangle when drawn on the scene.
Returns:
np.ndarray: The scene with the rectangle drawn on it
"""
cv2.rectangle(
scene,
rect.top_left.as_xy_int_tuple(),
rect.bottom_right.as_xy_int_tuple(),
color.as_bgr(),
-1,
)
if opacity == 1:
cv2.rectangle(
scene,
rect.top_left.as_xy_int_tuple(),
rect.bottom_right.as_xy_int_tuple(),
color.as_bgr(),
-1,
)
else:
scene_with_annotations = scene.copy()
cv2.rectangle(
scene_with_annotations,
rect.top_left.as_xy_int_tuple(),
rect.bottom_right.as_xy_int_tuple(),
color.as_bgr(),
-1,
)
cv2.addWeighted(
scene_with_annotations, opacity, scene, 1 - opacity, gamma=0, dst=scene
)
return scene
@@ -153,6 +170,32 @@ def draw_polygon(
return scene
def draw_filled_polygon(
scene: np.ndarray, polygon: np.ndarray, color: Color, opacity: float = 1
) -> np.ndarray:
"""Draw a filled polygon on a scene.
Parameters:
scene (np.ndarray): The scene to draw the polygon on.
polygon (np.ndarray): The polygon to be drawn, given as a list of vertices.
color (Color): The color of the polygon.
opacity (float): The opacity of polygon when drawn on the scene.
Returns:
np.ndarray: The scene with the polygon drawn on it.
"""
if opacity == 1:
cv2.fillPoly(scene, [polygon], color=color.as_bgr())
else:
scene_with_annotations = scene.copy()
cv2.fillPoly(scene_with_annotations, [polygon], color=color.as_bgr())
cv2.addWeighted(
scene_with_annotations, opacity, scene, 1 - opacity, gamma=0, dst=scene
)
return scene
def draw_text(
scene: np.ndarray,
text: str,
+7 -7
View File
@@ -18,11 +18,11 @@ class KeyPoints:
The `sv.KeyPoints` class in the Supervision library standardizes results from
various keypoint detection and pose estimation models into a consistent format. This
class simplifies data manipulation and filtering, providing a uniform API for
integration with Supervision [keypoints annotators](/keypoint/annotators).
integration with Supervision [keypoints annotators](/latest/keypoint/annotators).
=== "Ultralytics"
Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics)
Use [`sv.KeyPoints.from_ultralytics`](/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics)
method, which accepts [YOLOv8](https://github.com/ultralytics/ultralytics)
pose result.
@@ -40,7 +40,7 @@ class KeyPoints:
=== "Inference"
Use [`sv.KeyPoints.from_inference`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_inference)
Use [`sv.KeyPoints.from_inference`](/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_inference)
method, which accepts [Inference](https://inference.roboflow.com/) pose result.
```python
@@ -57,7 +57,7 @@ class KeyPoints:
=== "MediaPipe"
Use [`sv.KeyPoints.from_mediapipe`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe)
Use [`sv.KeyPoints.from_mediapipe`](/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe)
method, which accepts [MediaPipe](https://github.com/google-ai-edge/mediapipe)
pose result.
@@ -429,7 +429,7 @@ class KeyPoints:
results = model.predict(image, conf=0.1)
key_points = sv.KeyPoints.from_yolo_nas(results)
```
""" # noqa: E501 // docs
"""
if len(yolo_nas_results.prediction.poses) == 0:
return cls.empty()
@@ -459,13 +459,13 @@ class KeyPoints:
)
@classmethod
def from_detectron2(cls, detectron2_results) -> KeyPoints:
def from_detectron2(cls, detectron2_results: Any) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the
[Detectron2](https://github.com/facebookresearch/detectron2) inference result.
Args:
detectron2_results: The output of a
detectron2_results (Any): The output of a
Detectron2 model containing instances with prediction data.
Returns:
+11 -10
View File
@@ -1,11 +1,11 @@
from enum import Enum
from typing import Dict, List, Tuple
from typing import Dict, Tuple
Edges = List[Tuple[int, int]]
Edges = Tuple[Tuple[int, int], ...]
class Skeleton(Enum):
COCO = [
COCO: Edges = (
(1, 2),
(1, 3),
(2, 3),
@@ -23,9 +23,9 @@ class Skeleton(Enum):
(15, 13),
(16, 14),
(17, 15),
]
)
GHUM = [
GHUM: Edges = (
(1, 2),
(1, 5),
(2, 3),
@@ -61,9 +61,9 @@ class Skeleton(Enum):
(29, 33),
(30, 32),
(31, 33),
]
)
FACEMESH_TESSELATION_NO_IRIS = [
FACEMESH_TESSELATION_NO_IRIS: Edges = (
(128, 35),
(35, 140),
(140, 128),
@@ -2620,9 +2620,9 @@ class Skeleton(Enum):
(340, 449),
(449, 256),
(256, 340),
]
)
FACEMESH_TESSELATION = [
FACEMESH_TESSELATION: Edges = (
(474, 474),
(475, 476),
(476, 477),
@@ -2633,7 +2633,8 @@ class Skeleton(Enum):
(471, 472),
(472, 473),
(473, 470),
] + FACEMESH_TESSELATION_NO_IRIS
*FACEMESH_TESSELATION_NO_IRIS,
)
SKELETONS_BY_EDGE_COUNT: Dict[int, Edges] = {}
+7 -1
View File
@@ -1,4 +1,10 @@
from supervision.metrics.core import CLASS_ID_NONE, Metric, MetricTarget
from supervision.metrics.core import (
CLASS_ID_NONE,
AveragingMethod,
Metric,
MetricTarget,
)
from supervision.metrics.f1_score import F1Score, F1ScoreResult
from supervision.metrics.mean_average_precision import (
MeanAveragePrecision,
MeanAveragePrecisionResult,
+23
View File
@@ -48,3 +48,26 @@ class MetricTarget(Enum):
BOXES = "boxes"
MASKS = "masks"
ORIENTED_BOUNDING_BOXES = "obb"
class AveragingMethod(Enum):
"""
Defines different ways of averaging the metric results.
Suppose, before returning the final result, a metric is computed for each class.
How do you combine those to get the final number?
* MACRO: Calculate the metric for each class and average the results. The simplest
averaging method, but it does not take class imbalance into account.
* MICRO: Calculate the metric globally by counting the total true positives, false
positives, and false negatives. Micro averaging is useful when you want to give
more importance to classes with more samples. It's also more appropriate if you
have an imbalance in the number of instances per class.
* WEIGHTED: Calculate the metric for each class and average the results, weighted by
the number of true instances of each class. Use weighted averaging if you want
to take class imbalance into account.
"""
MACRO = "macro"
MICRO = "micro"
WEIGHTED = "weighted"
+2 -2
View File
@@ -440,8 +440,8 @@ class ConfusionMatrix:
class_names = classes if classes is not None else self.classes
use_labels_for_ticks = class_names is not None and (0 < len(class_names) < 99)
if use_labels_for_ticks:
x_tick_labels = class_names + ["FN"]
y_tick_labels = class_names + ["FP"]
x_tick_labels = [*class_names, "FN"]
y_tick_labels = [*class_names, "FP"]
num_ticks = len(x_tick_labels)
else:
x_tick_labels = None
+545
View File
@@ -0,0 +1,545 @@
from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
import numpy as np
from matplotlib import pyplot as plt
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.detection.core import Detections
from supervision.detection.utils import box_iou_batch, mask_iou_batch
from supervision.draw.color import LEGACY_COLOR_PALETTE
from supervision.metrics.core import AveragingMethod, Metric, MetricTarget
from supervision.metrics.utils.object_size import (
ObjectSizeCategory,
get_detection_size_category,
)
from supervision.metrics.utils.utils import ensure_pandas_installed
if TYPE_CHECKING:
import pandas as pd
class F1Score(Metric):
def __init__(
self,
metric_target: MetricTarget = MetricTarget.BOXES,
averaging_method: AveragingMethod = AveragingMethod.WEIGHTED,
):
self._metric_target = metric_target
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
raise NotImplementedError(
"F1 score is not implemented for oriented bounding boxes."
)
self._metric_target = metric_target
self.averaging_method = averaging_method
self._predictions_list: List[Detections] = []
self._targets_list: List[Detections] = []
def reset(self) -> None:
self._predictions_list = []
self._targets_list = []
def update(
self,
predictions: Union[Detections, List[Detections]],
targets: Union[Detections, List[Detections]],
) -> F1Score:
if not isinstance(predictions, list):
predictions = [predictions]
if not isinstance(targets, list):
targets = [targets]
if len(predictions) != len(targets):
raise ValueError(
f"The number of predictions ({len(predictions)}) and"
f" targets ({len(targets)}) during the update must be the same."
)
self._predictions_list.extend(predictions)
self._targets_list.extend(targets)
return self
def compute(self) -> F1ScoreResult:
result = self._compute(self._predictions_list, self._targets_list)
small_predictions, small_targets = self._filter_predictions_and_targets_by_size(
self._predictions_list, self._targets_list, ObjectSizeCategory.SMALL
)
result.small_objects = self._compute(small_predictions, small_targets)
medium_predictions, medium_targets = (
self._filter_predictions_and_targets_by_size(
self._predictions_list, self._targets_list, ObjectSizeCategory.MEDIUM
)
)
result.medium_objects = self._compute(medium_predictions, medium_targets)
large_predictions, large_targets = self._filter_predictions_and_targets_by_size(
self._predictions_list, self._targets_list, ObjectSizeCategory.LARGE
)
result.large_objects = self._compute(large_predictions, large_targets)
return result
def _compute(
self, predictions_list: List[Detections], targets_list: List[Detections]
) -> F1ScoreResult:
iou_thresholds = np.linspace(0.5, 0.95, 10)
stats = []
for predictions, targets in zip(predictions_list, targets_list):
prediction_contents = self._detections_content(predictions)
target_contents = self._detections_content(targets)
if len(targets) > 0:
if len(predictions) == 0:
stats.append(
(
np.zeros((0, iou_thresholds.size), dtype=bool),
np.zeros((0,), dtype=np.float32),
np.zeros((0,), dtype=int),
targets.class_id,
)
)
else:
if self._metric_target == MetricTarget.BOXES:
iou = box_iou_batch(target_contents, prediction_contents)
elif self._metric_target == MetricTarget.MASKS:
iou = mask_iou_batch(target_contents, prediction_contents)
else:
raise NotImplementedError(
"Unsupported metric target for IoU calculation"
)
matches = self._match_detection_batch(
predictions.class_id, targets.class_id, iou, iou_thresholds
)
stats.append(
(
matches,
predictions.confidence,
predictions.class_id,
targets.class_id,
)
)
if not stats:
return F1ScoreResult(
metric_target=self._metric_target,
averaging_method=self.averaging_method,
f1_scores=np.zeros(iou_thresholds.shape[0]),
f1_per_class=np.zeros((0, iou_thresholds.shape[0])),
iou_thresholds=iou_thresholds,
matched_classes=np.array([], dtype=int),
small_objects=None,
medium_objects=None,
large_objects=None,
)
concatenated_stats = [np.concatenate(items, 0) for items in zip(*stats)]
f1_scores, f1_per_class, unique_classes = self._compute_f1_for_classes(
*concatenated_stats
)
return F1ScoreResult(
metric_target=self._metric_target,
averaging_method=self.averaging_method,
f1_scores=f1_scores,
f1_per_class=f1_per_class,
iou_thresholds=iou_thresholds,
matched_classes=unique_classes,
small_objects=None,
medium_objects=None,
large_objects=None,
)
def _compute_f1_for_classes(
self,
matches: np.ndarray,
prediction_confidence: np.ndarray,
prediction_class_ids: np.ndarray,
true_class_ids: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
sorted_indices = np.argsort(-prediction_confidence)
matches = matches[sorted_indices]
prediction_class_ids = prediction_class_ids[sorted_indices]
unique_classes, class_counts = np.unique(true_class_ids, return_counts=True)
# Shape: PxTh,P,C,C -> CxThx3
confusion_matrix = self._compute_confusion_matrix(
matches, prediction_class_ids, unique_classes, class_counts
)
# Shape: CxThx3 -> CxTh
f1_per_class = self._compute_f1(confusion_matrix)
# Shape: CxTh -> Th
if self.averaging_method == AveragingMethod.MACRO:
f1_scores = np.mean(f1_per_class, axis=0)
elif self.averaging_method == AveragingMethod.MICRO:
confusion_matrix_merged = confusion_matrix.sum(0)
f1_scores = self._compute_f1(confusion_matrix_merged)
elif self.averaging_method == AveragingMethod.WEIGHTED:
class_counts = class_counts.astype(np.float32)
f1_scores = np.average(f1_per_class, axis=0, weights=class_counts)
return f1_scores, f1_per_class, unique_classes
@staticmethod
def _match_detection_batch(
predictions_classes: np.ndarray,
target_classes: np.ndarray,
iou: np.ndarray,
iou_thresholds: np.ndarray,
) -> np.ndarray:
num_predictions, num_iou_levels = (
predictions_classes.shape[0],
iou_thresholds.shape[0],
)
correct = np.zeros((num_predictions, num_iou_levels), dtype=bool)
correct_class = target_classes[:, None] == predictions_classes
for i, iou_level in enumerate(iou_thresholds):
matched_indices = np.where((iou >= iou_level) & correct_class)
if matched_indices[0].shape[0]:
combined_indices = np.stack(matched_indices, axis=1)
iou_values = iou[matched_indices][:, None]
matches = np.hstack([combined_indices, iou_values])
if matched_indices[0].shape[0] > 1:
matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
correct[matches[:, 1].astype(int), i] = True
return correct
@staticmethod
def _compute_confusion_matrix(
sorted_matches: np.ndarray,
sorted_prediction_class_ids: np.ndarray,
unique_classes: np.ndarray,
class_counts: np.ndarray,
) -> np.ndarray:
"""
Compute the confusion matrix for each class and IoU threshold.
Assumes the matches and prediction_class_ids are sorted by confidence
in descending order.
Arguments:
sorted_matches: np.ndarray, bool, shape (P, Th), that is True
if the prediction is a true positive at the given IoU threshold.
sorted_prediction_class_ids: np.ndarray, int, shape (P,), containing
the class id for each prediction.
unique_classes: np.ndarray, int, shape (C,), containing the unique
class ids.
class_counts: np.ndarray, int, shape (C,), containing the number
of true instances for each class.
Returns:
np.ndarray, shape (C, Th, 3), containing the true positives, false
positives, and false negatives for each class and IoU threshold.
"""
num_thresholds = sorted_matches.shape[1]
num_classes = unique_classes.shape[0]
confusion_matrix = np.zeros((num_classes, num_thresholds, 3))
for class_idx, class_id in enumerate(unique_classes):
is_class = sorted_prediction_class_ids == class_id
num_true = class_counts[class_idx]
num_predictions = is_class.sum()
if num_predictions == 0:
true_positives = np.zeros(num_thresholds)
false_positives = np.zeros(num_thresholds)
false_negatives = np.full(num_thresholds, num_true)
elif num_true == 0:
true_positives = np.zeros(num_thresholds)
false_positives = np.full(num_thresholds, num_predictions)
false_negatives = np.zeros(num_thresholds)
else:
true_positives = sorted_matches[is_class].sum(0)
false_positives = (1 - sorted_matches[is_class]).sum(0)
false_negatives = num_true - true_positives
confusion_matrix[class_idx] = np.stack(
[true_positives, false_positives, false_negatives], axis=1
)
return confusion_matrix
@staticmethod
def _compute_f1(confusion_matrix: np.ndarray) -> np.ndarray:
"""
Broadcastable function, computing the F1 score from the confusion matrix.
Arguments:
confusion_matrix: np.ndarray, shape (N, ..., 3), where the last dimension
contains the true positives, false positives, and false negatives.
Returns:
np.ndarray, shape (N, ...), containing the F1 score for each element.
"""
if not confusion_matrix.shape[-1] == 3:
raise ValueError(
f"Confusion matrix must have shape (..., 3), got "
f"{confusion_matrix.shape}"
)
true_positives = confusion_matrix[..., 0]
false_positives = confusion_matrix[..., 1]
false_negatives = confusion_matrix[..., 2]
# Alternate formula, avoids multiple zero division checks
denominator = 2 * true_positives + false_positives + false_negatives
f1_score = np.where(denominator == 0, 0, 2 * true_positives / denominator)
return f1_score
def _detections_content(self, detections: Detections) -> np.ndarray:
"""Return boxes, masks or oriented bounding boxes from detections."""
if self._metric_target == MetricTarget.BOXES:
return detections.xyxy
if self._metric_target == MetricTarget.MASKS:
return (
detections.mask
if detections.mask is not None
else np.empty((0, 0, 0), dtype=bool)
)
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
if obb := detections.data.get(ORIENTED_BOX_COORDINATES):
return np.ndarray(obb, dtype=np.float32)
return np.empty((0, 8), dtype=np.float32)
raise ValueError(f"Invalid metric target: {self._metric_target}")
def _filter_detections_by_size(
self, detections: Detections, size_category: ObjectSizeCategory
) -> Detections:
"""Return a copy of detections with contents filtered by object size."""
new_detections = deepcopy(detections)
if detections.is_empty() or size_category == ObjectSizeCategory.ANY:
return new_detections
sizes = get_detection_size_category(new_detections, self._metric_target)
size_mask = sizes == size_category.value
new_detections.xyxy = new_detections.xyxy[size_mask]
if new_detections.mask is not None:
new_detections.mask = new_detections.mask[size_mask]
if new_detections.class_id is not None:
new_detections.class_id = new_detections.class_id[size_mask]
if new_detections.confidence is not None:
new_detections.confidence = new_detections.confidence[size_mask]
if new_detections.tracker_id is not None:
new_detections.tracker_id = new_detections.tracker_id[size_mask]
if new_detections.data is not None:
for key, value in new_detections.data.items():
new_detections.data[key] = np.array(value)[size_mask]
return new_detections
def _filter_predictions_and_targets_by_size(
self,
predictions_list: List[Detections],
targets_list: List[Detections],
size_category: ObjectSizeCategory,
) -> Tuple[List[Detections], List[Detections]]:
"""
Filter predictions and targets by object size category.
"""
new_predictions_list = []
new_targets_list = []
for predictions, targets in zip(predictions_list, targets_list):
new_predictions_list.append(
self._filter_detections_by_size(predictions, size_category)
)
new_targets_list.append(
self._filter_detections_by_size(targets, size_category)
)
return new_predictions_list, new_targets_list
@dataclass
class F1ScoreResult:
"""
The results of the F1 score metric calculation.
Defaults to `0` if no detections or targets were provided.
Provides a custom `__str__` method for pretty printing.
Attributes:
metric_target (MetricTarget): the type of data used for the metric -
boxes, masks or oriented bounding boxes.
averaging_method (AveragingMethod): the averaging method used to compute the
F1 scores. Determines how the F1 scores are aggregated across classes.
f1_50 (float): the F1 score at IoU threshold of `0.5`.
f1_75 (float): the F1 score at IoU threshold of `0.75`.
f1_scores (np.ndarray): the F1 scores at each IoU threshold.
Shape: `(num_iou_thresholds,)`
f1_per_class (np.ndarray): the F1 scores per class and IoU threshold.
Shape: `(num_target_classes, num_iou_thresholds)`
iou_thresholds (np.ndarray): the IoU thresholds used in the calculations.
matched_classes (np.ndarray): the class IDs of all matched classes.
Corresponds to the rows of `f1_per_class`.
small_objects (Optional[F1ScoreResult]): the F1 metric results
for small objects.
medium_objects (Optional[F1ScoreResult]): the F1 metric results
for medium objects.
large_objects (Optional[F1ScoreResult]): the F1 metric results
for large objects.
"""
metric_target: MetricTarget
averaging_method: AveragingMethod
@property
def f1_50(self) -> float:
return self.f1_scores[0]
@property
def f1_75(self) -> float:
return self.f1_scores[5]
f1_scores: np.ndarray
f1_per_class: np.ndarray
iou_thresholds: np.ndarray
matched_classes: np.ndarray
small_objects: Optional[F1ScoreResult]
medium_objects: Optional[F1ScoreResult]
large_objects: Optional[F1ScoreResult]
def __str__(self) -> str:
"""
Format as a pretty string.
Example:
```python
print(f1_result)
```
"""
out_str = (
f"{self.__class__.__name__}:\n"
f"Metric target: {self.metric_target}\n"
f"Averaging method: {self.averaging_method}\n"
f"F1 @ 50: {self.f1_50:.4f}\n"
f"F1 @ 75: {self.f1_75:.4f}\n"
f"F1 @ thresh: {self.f1_scores}\n"
f"IoU thresh: {self.iou_thresholds}\n"
f"F1 per class:\n"
)
if self.f1_per_class.size == 0:
out_str += " No results\n"
for class_id, f1_of_class in zip(self.matched_classes, self.f1_per_class):
out_str += f" {class_id}: {f1_of_class}\n"
indent = " "
if self.small_objects is not None:
indented = indent + str(self.small_objects).replace("\n", f"\n{indent}")
out_str += f"\nSmall objects:\n{indented}"
if self.medium_objects is not None:
indented = indent + str(self.medium_objects).replace("\n", f"\n{indent}")
out_str += f"\nMedium objects:\n{indented}"
if self.large_objects is not None:
indented = indent + str(self.large_objects).replace("\n", f"\n{indent}")
out_str += f"\nLarge objects:\n{indented}"
return out_str
def to_pandas(self) -> "pd.DataFrame":
"""
Convert the result to a pandas DataFrame.
Returns:
(pd.DataFrame): The result as a DataFrame.
"""
ensure_pandas_installed()
import pandas as pd
pandas_data = {
"F1@50": self.f1_50,
"F1@75": self.f1_75,
}
if self.small_objects is not None:
small_objects_df = self.small_objects.to_pandas()
for key, value in small_objects_df.items():
pandas_data[f"small_objects_{key}"] = value
if self.medium_objects is not None:
medium_objects_df = self.medium_objects.to_pandas()
for key, value in medium_objects_df.items():
pandas_data[f"medium_objects_{key}"] = value
if self.large_objects is not None:
large_objects_df = self.large_objects.to_pandas()
for key, value in large_objects_df.items():
pandas_data[f"large_objects_{key}"] = value
return pd.DataFrame(pandas_data, index=[0])
def plot(self):
"""
Plot the F1 results.
"""
labels = ["F1@50", "F1@75"]
values = [self.f1_50, self.f1_75]
colors = [LEGACY_COLOR_PALETTE[0]] * 2
if self.small_objects is not None:
small_objects = self.small_objects
labels += ["Small: F1@50", "Small: F1@75"]
values += [small_objects.f1_50, small_objects.f1_75]
colors += [LEGACY_COLOR_PALETTE[3]] * 2
if self.medium_objects is not None:
medium_objects = self.medium_objects
labels += ["Medium: F1@50", "Medium: F1@75"]
values += [medium_objects.f1_50, medium_objects.f1_75]
colors += [LEGACY_COLOR_PALETTE[2]] * 2
if self.large_objects is not None:
large_objects = self.large_objects
labels += ["Large: F1@50", "Large: F1@75"]
values += [large_objects.f1_50, large_objects.f1_75]
colors += [LEGACY_COLOR_PALETTE[4]] * 2
plt.rcParams["font.family"] = "monospace"
_, ax = plt.subplots(figsize=(10, 6))
ax.set_ylim(0, 1)
ax.set_ylabel("Value", fontweight="bold")
title = (
f"F1 Score, by Object Size"
f"\n(target: {self.metric_target.value},"
f" averaging: {self.averaging_method.value})"
)
ax.set_title(title, fontweight="bold")
x_positions = range(len(labels))
bars = ax.bar(x_positions, values, color=colors, align="center")
ax.set_xticks(x_positions)
ax.set_xticklabels(labels, rotation=45, ha="right")
for bar in bars:
y_value = bar.get_height()
ax.text(
bar.get_x() + bar.get_width() / 2,
y_value + 0.02,
f"{y_value:.2f}",
ha="center",
va="bottom",
)
plt.rcParams["font.family"] = "sans-serif"
plt.tight_layout()
plt.show()
+95 -95
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, Union
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
import numpy as np
from matplotlib import pyplot as plt
@@ -86,19 +86,10 @@ class MeanAveragePrecision(Metric):
) -> MeanAveragePrecisionResult:
"""
Calculate Mean Average Precision based on predicted and ground-truth
detections at different threshold.
detections at different thresholds.
Args:
predictions (List[np.ndarray]): Each element of the list describes
a single image and has `shape = (M, 6)` where `M` is
the number of detected objects. Each row is expected to be
in `(x_min, y_min, x_max, y_max, class, conf)` format.
targets (List[np.ndarray]): Each element of the list describes a single
image and has `shape = (N, 5)` where `N` is the
number of ground-truth objects. Each row is expected to be in
`(x_min, y_min, x_max, y_max, class)` format.
Returns:
(MeanAveragePrecision): New instance of MeanAveragePrecision.
(MeanAveragePrecisionResult): New instance of MeanAveragePrecision.
Example:
```python
@@ -201,25 +192,25 @@ class MeanAveragePrecision(Metric):
# Compute average precisions if any matches exist
if stats:
concatenated_stats = [np.concatenate(items, 0) for items in zip(*stats)]
average_precisions = self._average_precisions_per_class(*concatenated_stats)
map50 = average_precisions[:, 0].mean()
map75 = average_precisions[:, 5].mean()
map50_95 = average_precisions.mean()
average_precisions, unique_classes = self._average_precisions_per_class(
*concatenated_stats
)
mAP_scores = np.mean(average_precisions, axis=0)
else:
map50, map75, map50_95 = 0, 0, 0
mAP_scores = np.zeros((10,), dtype=np.float32)
unique_classes = np.empty((0,), dtype=int)
average_precisions = np.empty((0, len(iou_thresholds)), dtype=np.float32)
return MeanAveragePrecisionResult(
iou_thresholds=iou_thresholds,
map50_95=map50_95,
map50=map50,
map75=map75,
per_class_ap50_95=average_precisions,
metric_target=self._metric_target,
mAP_scores=mAP_scores,
iou_thresholds=iou_thresholds,
matched_classes=unique_classes,
ap_per_class=average_precisions,
)
@staticmethod
def compute_average_precision(recall: np.ndarray, precision: np.ndarray) -> float:
def _compute_average_precision(recall: np.ndarray, precision: np.ndarray) -> float:
"""
Compute the average precision using 101-point interpolation (COCO), given
the recall and precision curves.
@@ -231,16 +222,15 @@ class MeanAveragePrecision(Metric):
Returns:
(float): Average precision.
"""
extended_recall = np.concatenate(([0.0], recall, [1.0]))
extended_precision = np.concatenate(([1.0], precision, [0.0]))
max_accumulated_precision = np.flip(
np.maximum.accumulate(np.flip(extended_precision))
)
interpolated_recall_levels = np.linspace(0, 1, 101)
interpolated_precision = np.interp(
interpolated_recall_levels, extended_recall, max_accumulated_precision
)
average_precision = np.trapz(interpolated_precision, interpolated_recall_levels)
if len(recall) == 0 and len(precision) == 0:
return 0.0
recall_levels = np.linspace(0, 1, 101)
precision_levels = np.zeros_like(recall_levels)
for r, p in zip(recall[::-1], precision[::-1]):
precision_levels[recall_levels <= r] = p
average_precision = (1 / 100 * precision_levels).sum()
return average_precision
@staticmethod
@@ -280,7 +270,7 @@ class MeanAveragePrecision(Metric):
prediction_confidence: np.ndarray,
prediction_class_ids: np.ndarray,
true_class_ids: np.ndarray,
) -> np.ndarray:
) -> Tuple[np.ndarray, np.ndarray]:
"""
Compute the average precision, given the recall and precision curves.
Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
@@ -293,7 +283,8 @@ class MeanAveragePrecision(Metric):
eps (float, optional): Small value to prevent division by zero.
Returns:
(np.ndarray): Average precision for different IoU levels.
(Tuple[np.ndarray, np.ndarray]): Average precision for different
IoU levels, and an array of class IDs that were matched.
"""
eps = 1e-16
@@ -316,19 +307,19 @@ class MeanAveragePrecision(Metric):
false_positives = (1 - matches[is_class]).cumsum(0)
true_positives = matches[is_class].cumsum(0)
true_negatives = total_true - true_positives
false_negatives = total_true - true_positives
recall = true_positives / (true_positives + true_negatives + eps)
recall = true_positives / (true_positives + false_negatives + eps)
precision = true_positives / (true_positives + false_positives)
for iou_level_idx in range(matches.shape[1]):
average_precisions[class_idx, iou_level_idx] = (
MeanAveragePrecision.compute_average_precision(
MeanAveragePrecision._compute_average_precision(
recall[:, iou_level_idx], precision[:, iou_level_idx]
)
)
return average_precisions
return average_precisions, unique_classes
def _detections_content(self, detections: Detections) -> np.ndarray:
"""Return boxes, masks or oriented bounding boxes from detections."""
@@ -384,38 +375,57 @@ class MeanAveragePrecision(Metric):
@dataclass
class MeanAveragePrecisionResult:
iou_thresholds: np.ndarray
"""Array of IoU thresholds used in the calculations"""
map50_95: float
"""Mean Average Precision over IoU thresholds from 0.5 to 0.95"""
"""
The result of the Mean Average Precision calculation.
map50: float
"""Mean Average Precision at IoU threshold of 0.5"""
Defaults to `0` when no detections or targets are present.
map75: float
"""Mean Average Precision at IoU threshold of 0.75"""
per_class_ap50_95: np.ndarray
"""Average precision for each class at different IoU thresholds"""
Attributes:
metric_target (MetricTarget): the type of data used for the metric -
boxes, masks or oriented bounding boxes.
mAP_map50_95 (float): the mAP score at IoU thresholds from `0.5` to `0.95`.
mAP_map50 (float): the mAP score at IoU threshold of `0.5`.
mAP_map75 (float): the mAP score at IoU threshold of `0.75`.
mAP_scores (np.ndarray): the mAP scores at each IoU threshold.
Shape: `(num_iou_thresholds,)`
ap_per_class (np.ndarray): the average precision scores per
class and IoU threshold. Shape: `(num_target_classes, num_iou_thresholds)`
iou_thresholds (np.ndarray): the IoU thresholds used in the calculations.
matched_classes (np.ndarray): the class IDs of all matched classes.
Corresponds to the rows of `ap_per_class`.
small_objects (Optional[MeanAveragePrecisionResult]): the mAP results
for small objects.
medium_objects (Optional[MeanAveragePrecisionResult]): the mAP results
for medium objects.
large_objects (Optional[MeanAveragePrecisionResult]): the mAP results
for large objects.
"""
metric_target: MetricTarget
"""
Defines the type of data used for the metric - boxes, masks or
oriented bounding boxes.
"""
@property
def map50_95(self) -> float:
return self.mAP_scores.mean()
@property
def map50(self) -> float:
return self.mAP_scores[0]
@property
def map75(self) -> float:
return self.mAP_scores[5]
mAP_scores: np.ndarray
ap_per_class: np.ndarray
iou_thresholds: np.ndarray
matched_classes: np.ndarray
small_objects: Optional[MeanAveragePrecisionResult] = None
"""Mean Average Precision results for small objects"""
medium_objects: Optional[MeanAveragePrecisionResult] = None
"""Mean Average Precision results for medium objects"""
large_objects: Optional[MeanAveragePrecisionResult] = None
"""Mean Average Precision results for large objects"""
def __str__(self) -> str:
"""
Format the mAP results as a pretty string.
Format as a pretty string.
Example:
```python
@@ -425,28 +435,29 @@ class MeanAveragePrecisionResult:
out_str = (
f"{self.__class__.__name__}:\n"
f"iou_thresholds: {self.iou_thresholds}\n"
f"map50_95: {self.map50_95:.4f}\n"
f"map50: {self.map50:.4f}\n"
f"map75: {self.map75:.4f}\n"
f"per_class_ap50_95:"
f"Metric target: {self.metric_target}\n"
f"mAP @ 50:95: {self.map50_95:.4f}\n"
f"mAP @ 50: {self.map50:.4f}\n"
f"mAP @ 75: {self.map75:.4f}\n"
f"mAP scores: {self.mAP_scores}\n"
f"IoU thresh: {self.iou_thresholds}\n"
f"AP per class:\n"
)
for class_id, ap in enumerate(self.per_class_ap50_95):
out_str += f"\n {class_id}: {ap}"
if self.ap_per_class.size == 0:
out_str += " No results\n"
for class_id, ap_of_class in zip(self.matched_classes, self.ap_per_class):
out_str += f" {class_id}: {ap_of_class}\n"
indent = " "
if self.small_objects is not None:
indented_str = indent + str(self.small_objects).replace("\n", f"\n{indent}")
out_str += f"\nSmall objects:\n{indented_str}"
indented = indent + str(self.small_objects).replace("\n", f"\n{indent}")
out_str += f"\nSmall objects:\n{indented}"
if self.medium_objects is not None:
indented_str = indent + str(self.medium_objects).replace(
"\n", f"\n{indent}"
)
out_str += f"\nMedium objects:\n{indented_str}"
indented = indent + str(self.medium_objects).replace("\n", f"\n{indent}")
out_str += f"\nMedium objects:\n{indented}"
if self.large_objects is not None:
indented_str = indent + str(self.large_objects).replace("\n", f"\n{indent}")
out_str += f"\nLarge objects:\n{indented_str}"
indented = indent + str(self.large_objects).replace("\n", f"\n{indent}")
out_str += f"\nLarge objects:\n{indented}"
return out_str
@@ -461,10 +472,11 @@ class MeanAveragePrecisionResult:
import pandas as pd
pandas_data = {
"mAP_50_95": self.map50_95,
"mAP_50": self.map50,
"mAP_75": self.map75,
"mAP@50:95": self.map50_95,
"mAP@50": self.map50,
"mAP@75": self.map75,
}
if self.small_objects is not None:
small_objects_df = self.small_objects.to_pandas()
for key, value in small_objects_df.items():
@@ -490,16 +502,12 @@ class MeanAveragePrecisionResult:
Plot the mAP results.
"""
labels = ["mAP_50_95", "mAP_50", "mAP_75"]
labels = ["mAP@50:95", "mAP@50", "mAP@75"]
values = [self.map50_95, self.map50, self.map75]
colors = [LEGACY_COLOR_PALETTE[0]] * 3
if self.small_objects is not None:
labels += [
"small_objects_mAP_50_95",
"small_objects_mAP_50",
"small_objects_mAP_75",
]
labels += ["Small: mAP@50:95", "Small: mAP@50", "Small: mAP@75"]
values += [
self.small_objects.map50_95,
self.small_objects.map50,
@@ -508,11 +516,7 @@ class MeanAveragePrecisionResult:
colors += [LEGACY_COLOR_PALETTE[3]] * 3
if self.medium_objects is not None:
labels += [
"medium_objects_mAP_50_95",
"medium_objects_mAP_50",
"medium_objects_mAP_75",
]
labels += ["Medium: mAP@50:95", "Medium: mAP@50", "Medium: mAP@75"]
values += [
self.medium_objects.map50_95,
self.medium_objects.map50,
@@ -521,11 +525,7 @@ class MeanAveragePrecisionResult:
colors += [LEGACY_COLOR_PALETTE[2]] * 3
if self.large_objects is not None:
labels += [
"large_objects_mAP_50_95",
"large_objects_mAP_50",
"large_objects_mAP_75",
]
labels += ["Large: mAP@50:95", "Large: mAP@50", "Large: mAP@75"]
values += [
self.large_objects.map50_95,
self.large_objects.map50,
@@ -1,300 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, List, Optional, Set, Tuple
import numpy as np
import numpy.typing as npt
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.metrics.core import CLASS_ID_NONE, MetricTarget
from supervision.metrics.utils.object_size import (
ObjectSizeCategory,
get_object_size_category,
)
from supervision.metrics.utils.utils import unify_pad_masks_shape
if TYPE_CHECKING:
from supervision.detection.core import Detections
class MetricData:
"""
A container for detection contents, decouple from `Detections`.
"""
def __init__(self, metric_target: MetricTarget, class_agnostic: bool = False):
self._metric_target = metric_target
self._class_agnostic = class_agnostic
self._content_list: List[npt.NDArray] = []
self._confidence_list: List[npt.NDArray[np.float32]] = []
self._class_id_list: List[npt.NDArray[np.int_]] = []
def update(self, detections: Detections):
"""Add new detections to the store."""
if detections.is_empty() or len(detections) == 0:
return
# Relies on Detections to ensure that member vars are equal in length
# or None/empty.
self._validate_new_entry(detections)
new_content = self._detections_content(detections)
self._validate_shape(new_content)
self._content_list.append(new_content)
if detections.confidence is not None:
self._confidence_list.append(detections.confidence)
if detections.class_id is not None:
if self._class_agnostic:
self._class_id_list.append(
np.full(len(detections.class_id), CLASS_ID_NONE, dtype=int)
)
else:
self._class_id_list.append(detections.class_id)
def get_classes(self) -> Set[int]:
"""Return all class IDs."""
if self._class_agnostic:
return {CLASS_ID_NONE}
return set.union(*[set(class_id) for class_id in self._class_id_list])
def get(
self, class_id: Optional[int] = None, size_category=ObjectSizeCategory.ANY
) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray]:
"""
Get the contents, class_ids and confidences, optionally filtered by
class_id and/or size.
Args:
class_id (Optional[int]): The class ID for the data to retrieve
size_category (ObjectSizeCategory): The size of the objects to retrieve.
Returns:
(np.ndarray): Boxes, masks or obb, all stacked in a single array.
"""
if self._metric_target in [
MetricTarget.BOXES,
MetricTarget.ORIENTED_BOUNDING_BOXES,
]:
self._merge_boxes()
if len(self._content_list) == 0:
content = self._make_empty_content()
else:
content = self._content_list[0]
elif self._metric_target == MetricTarget.MASKS:
self._merge_masks()
if len(self._content_list) == 0:
content = self._make_empty_content()
else:
content = self._content_list[0]
size_mask = np.full(len(content), True)
if size_category != ObjectSizeCategory.ANY:
sizes = get_object_size_category(content, self._metric_target)
size_mask = sizes == size_category.value
class_mask = np.full(len(content), True)
if class_id is not None:
self._merge_class_id()
class_ids = self._class_id_list[0]
class_mask = class_ids == class_id
content = content[(size_mask & class_mask)]
self._merge_class_id()
if len(self._class_id_list) == 0:
class_ids = np.array([], dtype=int)
else:
class_ids = self._class_id_list[0]
if len(class_ids) > 0:
class_ids = class_ids[(size_mask & class_mask)]
self._merge_confidence()
if len(self._confidence_list) == 0:
confidences = np.array([], dtype=float)
else:
confidences = self._confidence_list[0]
if len(confidences) > 0:
confidences = confidences[(size_mask & class_mask)]
return content, class_ids, confidences
def get_class_id(self) -> npt.NDArray[np.int_]:
self._merge_class_id()
if len(self._class_id_list) == 0:
return np.array([], dtype=int)
return self._class_id_list[0]
def get_confidence(self) -> npt.NDArray[np.float32]:
self._merge_confidence()
if len(self._confidence_list) == 0:
return np.array([], dtype=float)
return self._confidence_list[0]
def _detections_content(self, detections: Detections) -> npt.NDArray:
"""Return boxes, masks or oriented bounding boxes from detections."""
if self._metric_target == MetricTarget.BOXES:
return detections.xyxy
if self._metric_target == MetricTarget.MASKS:
return (
detections.mask
if detections.mask is not None
else self._make_empty_content()
)
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
if obb := detections.data.get(ORIENTED_BOX_COORDINATES):
return np.ndarray(obb, dtype=np.float32)
return self._make_empty_content()
raise ValueError(f"Invalid metric target: {self._metric_target}")
def _make_empty_content(self) -> npt.NDArray:
if self._metric_target == MetricTarget.BOXES:
return np.empty((0, 4), dtype=np.float32)
if self._metric_target == MetricTarget.MASKS:
return np.empty((0, 0, 0), dtype=bool)
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
return np.empty((0, 8), dtype=np.float32)
raise ValueError(f"Invalid metric target: {self._metric_target}")
def _validate_new_entry(self, detections: Detections) -> None:
if (
len(self._content_list) == 0
or detections.is_empty()
or len(detections) == 0
):
return
is_self_class_empty = len(self._class_id_list) == 0
is_self_confidence_empty = len(self._confidence_list) == 0
is_detection_class_empty = (
detections.class_id is None or len(detections.class_id) == 0
)
is_detection_confidence_empty = (
detections.confidence is None or len(detections.confidence) == 0
)
if is_self_confidence_empty and not is_detection_confidence_empty:
raise ValueError(
"Previously stored detections without confidence,"
" but new detections have it."
)
if is_self_class_empty and not is_detection_class_empty:
raise ValueError(
"Previously stored detections without class ID,"
" but new detections have it."
)
if not is_self_class_empty and is_detection_class_empty:
raise ValueError(
"Started storing detections with class ID, but"
" new detections do not have it."
)
if not is_self_confidence_empty and is_detection_confidence_empty:
raise ValueError(
"Started storing detections with confidence,"
" but new detections do not have it."
)
def _validate_shape(self, data: npt.NDArray) -> None:
if self._metric_target == MetricTarget.BOXES:
if len(data.shape) != 2 or data.shape[1] != 4:
raise ValueError(f"Invalid xyxy shape: {data.shape}. Expected: (N, 4)")
elif self._metric_target == MetricTarget.MASKS:
if len(data.shape) != 3:
raise ValueError(
f"Invalid mask shape: {data.shape}. Expected: (N, H, W)"
)
elif self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
if len(data.shape) != 2 or data.shape[1] != 8:
raise ValueError(f"Invalid obb shape: {data.shape}. Expected: (N, 8)")
else:
raise ValueError(f"Invalid metric target: {self._metric_target}")
def _merge_boxes(self):
"""Merge all boxes into a single array."""
if self._metric_target not in (
MetricTarget.BOXES,
MetricTarget.ORIENTED_BOUNDING_BOXES,
):
raise ValueError("Invalid metric target for merging boxes")
if len(self._content_list) < 2:
return
self._content_list = [np.vstack(self._content_list)]
def _merge_masks(self):
"""Merge all masks into a single array."""
if self._metric_target != MetricTarget.MASKS:
raise ValueError("Invalid metric target for merging masks")
if len(self._content_list) < 2:
return
mew_mask_list = unify_pad_masks_shape(*self._content_list)
self._content_list = [np.vstack(mew_mask_list)]
def _merge_class_id(self):
"""Merge all class IDs into a single array."""
if len(self._class_id_list) < 2:
return
self._class_id_list = [np.hstack(self._class_id_list)]
def _merge_confidence(self):
"""Merge all confidences into a single array."""
if len(self._confidence_list) < 2:
return
self._confidence_list = [np.hstack(self._confidence_list)]
class MetricDataStore:
"""
Stores internal data for metrics.
Provides a class-agnostic way to access it.
"""
def __init__(self, metric_target: MetricTarget, class_agnostic: bool = False):
self._metric_target = metric_target
self._class_agnostic = class_agnostic
self._data_1: MetricData
self._data_2: MetricData
self.reset()
def reset(self) -> None:
self._data_1 = MetricData(self._metric_target, self._class_agnostic)
self._data_2 = MetricData(self._metric_target, self._class_agnostic)
def update(self, data_1: Detections, data_2: Detections) -> None:
"""
Add new data to the store.
Use sv.Detections.empty() if only one set of data is available.
"""
self._data_1.update(data_1)
self._data_2.update(data_2)
def get(
self,
class_id: Optional[int] = None,
size_category: ObjectSizeCategory = ObjectSizeCategory.ANY,
) -> Tuple[
Tuple[npt.NDArray, npt.NDArray, npt.NDArray],
Tuple[npt.NDArray, npt.NDArray, npt.NDArray],
]:
"""
Get the data for both sets, optionally filtered by class_id and/or size.
"""
data_1, class_id_1, confidence_1 = self._data_1.get(class_id, size_category)
data_2, class_id_2, confidence_2 = self._data_2.get(class_id, size_category)
if self._metric_target == MetricTarget.MASKS:
data_1, data_2 = unify_pad_masks_shape(data_1, data_2)
return (
(data_1, class_id_1, confidence_1),
(data_2, class_id_2, confidence_2),
)
def get_classes(self) -> Set[int]:
"""Return all class IDs."""
return self._data_1.get_classes() | self._data_2.get_classes()
+7 -4
View File
@@ -101,17 +101,20 @@ def get_obb_size_category(xyxyxyxy: npt.NDArray[np.float32]) -> npt.NDArray[np.i
Get the size category of a oriented bounding boxes array.
Args:
xyxyxyxy (np.ndarray): The bounding boxes array shaped (N, 8).
xyxyxyxy (np.ndarray): The bounding boxes array shaped (N, 4, 2).
Returns:
(np.ndarray) The size category of each bounding box, matching
the enum values of ObjectSizeCategory. Shaped (N,).
"""
if len(xyxyxyxy.shape) != 2 or xyxyxyxy.shape[1] != 8:
raise ValueError("Oriented bounding boxes must be shaped (N, 8)")
if len(xyxyxyxy.shape) != 3 or xyxyxyxy.shape[1] != 4 or xyxyxyxy.shape[2] != 2:
raise ValueError("Oriented bounding boxes must be shaped (N, 4, 2)")
# Shoelace formula
x1, y1, x2, y2, x3, y3, x4, y4 = xyxyxyxy.T
x = xyxyxyxy[:, :, 0]
y = xyxyxyxy[:, :, 1]
x1, x2, x3, x4 = x.T
y1, y2, y3, y4 = y.T
areas = 0.5 * np.abs(
(x1 * y2 + x2 * y3 + x3 * y4 + x4 * y1)
- (x2 * y1 + x3 * y2 + x4 * y3 + x1 * y4)
-61
View File
@@ -1,9 +1,3 @@
from typing import List, Tuple
import numpy as np
import numpy.typing as npt
def ensure_pandas_installed():
try:
import pandas # noqa
@@ -13,58 +7,3 @@ def ensure_pandas_installed():
" Run `pip install 'supervision[metrics]'` or"
" `poetry add supervision -E metrics`"
)
def pad_mask(mask: npt.NDArray, new_shape: Tuple[int, int]) -> npt.NDArray:
"""Pad a mask to a new shape, inserting zeros on the right and bottom."""
if len(mask.shape) != 3:
raise ValueError(f"Invalid mask shape: {mask.shape}. Expected: (N, H, W)")
new_mask = np.pad(
mask,
(
(0, 0),
(0, new_shape[0] - mask.shape[1]),
(0, new_shape[1] - mask.shape[2]),
),
mode="constant",
constant_values=0,
)
return new_mask
def unify_pad_masks_shape(
*masks: npt.NDArray[np.bool_],
) -> List[npt.NDArray[np.bool_]]:
"""
Given any number of (N, H, W) mask objects, return copies of the
same (H, W), padded to the largest dimensions.
Args:
*masks (np.ndarray): The mask arrays to unify. Each shaped (_, H, W),
Returns:
List[np.ndarray]: The masks, padded to the largest dimensions.
Each list element shaped (_, H_max, W_max)
"""
new_h = 0
new_w = 0
for mask in masks:
if len(mask.shape) != 3:
raise ValueError(f"Invalid mask shape: {mask.shape}. Expected: (N, H, W)")
_, h, w = mask.shape
new_h = max(new_h, h)
new_w = max(new_w, w)
results = []
for mask in masks:
results.append(pad_mask(mask, (new_h, new_w)))
return results
def len0_like(data: npt.NDArray) -> npt.NDArray:
"""Create an empty array with the same shape as input, but with 0 rows."""
return np.empty((0, *data.shape[1:]), dtype=data.dtype)
+3 -3
View File
@@ -1,5 +1,5 @@
from contextlib import ExitStack as DoesNotRaise
from typing import Dict, List, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union
import numpy as np
import pytest
@@ -21,7 +21,7 @@ def mock_coco_annotation(
category_id: int = 0,
bbox: Tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0),
area: float = 0.0,
segmentation: Union[List[list], Dict] = None,
segmentation: Optional[Union[List[list], Dict]] = None,
iscrowd: bool = False,
) -> dict:
if not segmentation:
@@ -308,7 +308,7 @@ def test_group_coco_annotations_by_image_id(
),
),
DoesNotRaise(),
), # two image annotations with mask, one mask as polygon ans second as RLE
), # two image annotations with mask, one mask as polygon and second as RLE
(
[
mock_coco_annotation(
+20 -48
View File
@@ -1,7 +1,6 @@
from contextlib import ExitStack as DoesNotRaise
from typing import List, Optional
import numpy as np
import pytest
from supervision import DetectionDataset
@@ -13,29 +12,29 @@ from test.test_utils import mock_detections
[
(
[],
DetectionDataset(classes=[], images={}, annotations={}),
DetectionDataset(classes=[], images=[], annotations={}),
DoesNotRaise(),
), # empty dataset list
(
[DetectionDataset(classes=[], images={}, annotations={})],
DetectionDataset(classes=[], images={}, annotations={}),
[DetectionDataset(classes=[], images=[], annotations={})],
DetectionDataset(classes=[], images=[], annotations={}),
DoesNotRaise(),
), # single empty dataset
(
[
DetectionDataset(classes=["dog", "person"], images={}, annotations={}),
DetectionDataset(classes=["dog", "person"], images={}, annotations={}),
DetectionDataset(classes=["dog", "person"], images=[], annotations={}),
DetectionDataset(classes=["dog", "person"], images=[], annotations={}),
],
DetectionDataset(classes=["dog", "person"], images={}, annotations={}),
DetectionDataset(classes=["dog", "person"], images=[], annotations={}),
DoesNotRaise(),
), # two datasets; no images and annotations, the same classes
(
[
DetectionDataset(classes=["dog", "person"], images={}, annotations={}),
DetectionDataset(classes=["cat"], images={}, annotations={}),
DetectionDataset(classes=["dog", "person"], images=[], annotations={}),
DetectionDataset(classes=["cat"], images=[], annotations={}),
],
DetectionDataset(
classes=["cat", "dog", "person"], images={}, annotations={}
classes=["cat", "dog", "person"], images=[], annotations={}
),
DoesNotRaise(),
), # two datasets; no images and annotations, different classes
@@ -43,10 +42,7 @@ from test.test_utils import mock_detections
[
DetectionDataset(
classes=["dog", "person"],
images={
"image-1.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-1.png", "image-2.png"],
annotations={
"image-1.png": mock_detections(
xyxy=[[0, 0, 10, 10]], class_id=[0]
@@ -56,14 +52,11 @@ from test.test_utils import mock_detections
),
},
),
DetectionDataset(classes=[], images={}, annotations={}),
DetectionDataset(classes=[], images=[], annotations={}),
],
DetectionDataset(
classes=["dog", "person"],
images={
"image-1.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-1.png", "image-2.png"],
annotations={
"image-1.png": mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[0]),
"image-2.png": mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[1]),
@@ -75,10 +68,7 @@ from test.test_utils import mock_detections
[
DetectionDataset(
classes=["dog", "person"],
images={
"image-1.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-1.png", "image-2.png"],
annotations={
"image-1.png": mock_detections(
xyxy=[[0, 0, 10, 10]], class_id=[0]
@@ -88,14 +78,11 @@ from test.test_utils import mock_detections
),
},
),
DetectionDataset(classes=["cat"], images={}, annotations={}),
DetectionDataset(classes=["cat"], images=[], annotations={}),
],
DetectionDataset(
classes=["cat", "dog", "person"],
images={
"image-1.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-1.png", "image-2.png"],
annotations={
"image-1.png": mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[1]),
"image-2.png": mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[2]),
@@ -107,10 +94,7 @@ from test.test_utils import mock_detections
[
DetectionDataset(
classes=["dog", "person"],
images={
"image-1.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-1.png", "image-2.png"],
annotations={
"image-1.png": mock_detections(
xyxy=[[0, 0, 10, 10]], class_id=[0]
@@ -122,9 +106,7 @@ from test.test_utils import mock_detections
),
DetectionDataset(
classes=["cat"],
images={
"image-3.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-3.png"],
annotations={
"image-3.png": mock_detections(
xyxy=[[0, 0, 10, 10]], class_id=[0]
@@ -134,11 +116,7 @@ from test.test_utils import mock_detections
],
DetectionDataset(
classes=["cat", "dog", "person"],
images={
"image-1.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-3.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-1.png", "image-2.png", "image-3.png"],
annotations={
"image-1.png": mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[1]),
"image-2.png": mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[2]),
@@ -151,10 +129,7 @@ from test.test_utils import mock_detections
[
DetectionDataset(
classes=["dog", "person"],
images={
"image-1.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-1.png", "image-2.png"],
annotations={
"image-1.png": mock_detections(
xyxy=[[0, 0, 10, 10]], class_id=[0]
@@ -166,10 +141,7 @@ from test.test_utils import mock_detections
),
DetectionDataset(
classes=["dog", "person"],
images={
"image-2.png": np.zeros((100, 100, 3), dtype=np.uint8),
"image-3.png": np.zeros((100, 100, 3), dtype=np.uint8),
},
images=["image-2.png", "image-3.png"],
annotations={
"image-2.png": mock_detections(
xyxy=[[0, 0, 10, 10]], class_id=[0]
+1 -1
View File
@@ -278,7 +278,7 @@ def test_map_detections_class_id(
np.array([[[]]]).astype(bool),
None,
pytest.raises(AssertionError),
), # raises AssertionError because mask dimentionality is not 2D
), # raises AssertionError because mask dimensionality is not 2D
(
np.array([[]]).astype(bool),
None,
@@ -0,0 +1,60 @@
import numpy as np
import pytest
import supervision as sv
COLOR = sv.Color(r=255, g=0, b=0)
THICKNESS = 2
POLYGON = np.array([[100, 100], [200, 100], [200, 200], [100, 200]])
SCENE = np.random.randint(0, 255, (1000, 1000, 3), dtype=np.uint8)
ANNOTATED_SCENE_NO_OPACITY = sv.draw_polygon(
scene=SCENE.copy(),
polygon=POLYGON,
color=COLOR,
thickness=THICKNESS,
)
ANNOTATED_SCENE_0_5_OPACITY = sv.draw_filled_polygon(
scene=ANNOTATED_SCENE_NO_OPACITY.copy(),
polygon=POLYGON,
color=COLOR,
opacity=0.5,
)
@pytest.mark.parametrize(
"scene, polygon_zone_annotator, expected_results",
[
(
SCENE,
sv.PolygonZoneAnnotator(
zone=sv.PolygonZone(
POLYGON,
),
color=COLOR,
thickness=THICKNESS,
display_in_zone_count=False,
),
ANNOTATED_SCENE_NO_OPACITY,
), # Test no opacity (default)
(
SCENE,
sv.PolygonZoneAnnotator(
zone=sv.PolygonZone(
POLYGON,
),
color=COLOR,
thickness=THICKNESS,
display_in_zone_count=False,
opacity=0.5,
),
ANNOTATED_SCENE_0_5_OPACITY,
), # Test 10% opacity
],
)
def test_polygon_zone_annotator(
scene: np.ndarray,
polygon_zone_annotator: sv.PolygonZoneAnnotator,
expected_results: np.ndarray,
) -> None:
annotated_scene = polygon_zone_annotator.annotate(scene=scene)
assert np.all(annotated_scene == expected_results)
+1 -6
View File
@@ -26,7 +26,6 @@ DETECTIONS = mock_detections(
)
POLYGON = np.array([[100, 100], [200, 100], [200, 200], [100, 200]])
FRAME_RESOLUTION = (300, 300)
@pytest.mark.parametrize(
@@ -36,7 +35,6 @@ FRAME_RESOLUTION = (300, 300)
DETECTIONS,
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
triggering_anchors=(
sv.Position.TOP_LEFT,
sv.Position.TOP_RIGHT,
@@ -53,7 +51,6 @@ FRAME_RESOLUTION = (300, 300)
DETECTIONS,
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
),
np.array(
[False, False, True, True, True, True, False, False, False], dtype=bool
@@ -64,7 +61,6 @@ FRAME_RESOLUTION = (300, 300)
DETECTIONS,
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
triggering_anchors=[sv.Position.BOTTOM_CENTER],
),
np.array(
@@ -76,7 +72,6 @@ FRAME_RESOLUTION = (300, 300)
sv.Detections.empty(),
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
),
np.array([], dtype=bool),
DoesNotRaise(),
@@ -107,4 +102,4 @@ def test_polygon_zone_trigger(
)
def test_polygon_zone_initialization(polygon, triggering_anchors, exception):
with exception:
sv.PolygonZone(polygon, FRAME_RESOLUTION, triggering_anchors=triggering_anchors)
sv.PolygonZone(polygon, triggering_anchors=triggering_anchors)
+2 -2
View File
@@ -975,7 +975,7 @@ def test_get_data_item(
),
False,
DoesNotRaise(),
), # foreground object in 2 seperate elements
), # foreground object in 2 separate elements
(
np.array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]).astype(
bool
@@ -1032,7 +1032,7 @@ def test_contains_holes(
4,
True,
DoesNotRaise(),
), # foreground object in 2 seperate elements
), # foreground object in 2 separate elements
(
np.array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]).astype(
bool
@@ -0,0 +1,192 @@
from contextlib import ExitStack as DoesNotRaise
from typing import Optional, Tuple
import numpy as np
import pytest
from supervision.detection.core import Detections
from supervision.detection.overlap_filter import OverlapFilter
from supervision.detection.tools.inference_slicer import InferenceSlicer
@pytest.fixture
def mock_callback():
"""Mock callback function for testing."""
def callback(_: np.ndarray) -> Detections:
return Detections(xyxy=np.array([[0, 0, 10, 10]]))
return callback
@pytest.mark.parametrize(
"slice_wh, overlap_ratio_wh, overlap_wh, expected_overlap, exception",
[
# Valid case: overlap_ratio_wh provided, overlap calculated from the ratio
((128, 128), (0.2, 0.2), None, None, DoesNotRaise()),
# Valid case: overlap_wh in pixels, no ratio provided
((128, 128), None, (20, 20), (20, 20), DoesNotRaise()),
# Invalid case: overlap_ratio_wh greater than 1, should raise ValueError
((128, 128), (1.1, 0.5), None, None, pytest.raises(ValueError)),
# Invalid case: negative overlap_wh, should raise ValueError
((128, 128), None, (-10, 20), None, pytest.raises(ValueError)),
# Invalid case:
# overlap_ratio_wh and overlap_wh provided, should raise ValueError
((128, 128), (0.5, 0.5), (20, 20), (20, 20), pytest.raises(ValueError)),
# Valid case: no overlap_ratio_wh, overlap_wh = 50 pixels
((256, 256), None, (50, 50), (50, 50), DoesNotRaise()),
# Valid case: overlap_ratio_wh provided, overlap calculated from (0.3, 0.3)
((200, 200), (0.3, 0.3), None, None, DoesNotRaise()),
# Valid case: small overlap_ratio_wh values
((100, 100), (0.1, 0.1), None, None, DoesNotRaise()),
# Invalid case: negative overlap_ratio_wh value, should raise ValueError
((128, 128), (-0.1, 0.2), None, None, pytest.raises(ValueError)),
# Invalid case: negative overlap_ratio_wh with overlap_wh provided
((128, 128), (-0.1, 0.2), (30, 30), None, pytest.raises(ValueError)),
# Invalid case: overlap_wh greater than slice size, should raise ValueError
((128, 128), None, (150, 150), (150, 150), DoesNotRaise()),
# Valid case: overlap_ratio_wh is 0, no overlap
((128, 128), (0.0, 0.0), None, None, DoesNotRaise()),
# Invalid case: no overlaps defined, no overlap
((128, 128), None, None, None, pytest.raises(ValueError)),
],
)
def test_inference_slicer_overlap(
mock_callback,
slice_wh: Tuple[int, int],
overlap_ratio_wh: Optional[Tuple[float, float]],
overlap_wh: Optional[Tuple[int, int]],
expected_overlap: Optional[Tuple[int, int]],
exception: Exception,
) -> None:
with exception:
slicer = InferenceSlicer(
callback=mock_callback,
slice_wh=slice_wh,
overlap_ratio_wh=overlap_ratio_wh,
overlap_wh=overlap_wh,
overlap_filter=OverlapFilter.NONE,
)
assert slicer.overlap_wh == expected_overlap
@pytest.mark.parametrize(
"resolution_wh, slice_wh, overlap_wh, expected_offsets",
[
# Case 1: No overlap, exact slices fit within image dimensions
(
(256, 256),
(128, 128),
(0, 0),
np.array(
[
[0, 0, 128, 128],
[128, 0, 256, 128],
[0, 128, 128, 256],
[128, 128, 256, 256],
]
),
),
# Case 2: Overlap of 64 pixels in both directions
(
(256, 256),
(128, 128),
(64, 64),
np.array(
[
[0, 0, 128, 128],
[64, 0, 192, 128],
[128, 0, 256, 128],
[192, 0, 256, 128],
[0, 64, 128, 192],
[64, 64, 192, 192],
[128, 64, 256, 192],
[192, 64, 256, 192],
[0, 128, 128, 256],
[64, 128, 192, 256],
[128, 128, 256, 256],
[192, 128, 256, 256],
[0, 192, 128, 256],
[64, 192, 192, 256],
[128, 192, 256, 256],
[192, 192, 256, 256],
]
),
),
# Case 3: Image not perfectly divisible by slice size (no overlap)
(
(300, 300),
(128, 128),
(0, 0),
np.array(
[
[0, 0, 128, 128],
[128, 0, 256, 128],
[256, 0, 300, 128],
[0, 128, 128, 256],
[128, 128, 256, 256],
[256, 128, 300, 256],
[0, 256, 128, 300],
[128, 256, 256, 300],
[256, 256, 300, 300],
]
),
),
# Case 4: Overlap of 32 pixels, image not perfectly divisible by slice size
(
(300, 300),
(128, 128),
(32, 32),
np.array(
[
[0, 0, 128, 128],
[96, 0, 224, 128],
[192, 0, 300, 128],
[288, 0, 300, 128],
[0, 96, 128, 224],
[96, 96, 224, 224],
[192, 96, 300, 224],
[288, 96, 300, 224],
[0, 192, 128, 300],
[96, 192, 224, 300],
[192, 192, 300, 300],
[288, 192, 300, 300],
[0, 288, 128, 300],
[96, 288, 224, 300],
[192, 288, 300, 300],
[288, 288, 300, 300],
]
),
),
# Case 5: Image smaller than slice size (no overlap)
(
(100, 100),
(128, 128),
(0, 0),
np.array(
[
[0, 0, 100, 100],
]
),
),
# Case 6: Overlap_wh is greater than the slice size
((256, 256), (128, 128), (150, 150), np.array([]).reshape(0, 4)),
],
)
def test_generate_offset(
resolution_wh: Tuple[int, int],
slice_wh: Tuple[int, int],
overlap_wh: Optional[Tuple[int, int]],
expected_offsets: np.ndarray,
) -> None:
offsets = InferenceSlicer._generate_offset(
resolution_wh=resolution_wh,
slice_wh=slice_wh,
overlap_ratio_wh=None,
overlap_wh=overlap_wh,
)
# Verify that the generated offsets match the expected offsets
assert np.array_equal(
offsets, expected_offsets
), f"Expected {expected_offsets}, got {offsets}"