Compare commits

...
870 Commits
Author SHA1 Message Date
LinasKo 27c68f293d Merge pull request #1486 from roboflow/develop
`supervision-0.23.0` release
2024-08-28 20:32:59 +03:00
LinasKo ee3d1768e3 Bump supervision version to 0.23.0 2024-08-28 20:26:41 +03:00
LinasKo f1124e04df Merge pull request #1442 from roboflow/feat/metrics-module-initial
Core system for Metrics
2024-08-28 20:19:42 +03:00
LinasKo 55c8c07b90 Rename legacy metrics doc to metrics, preserve url 2024-08-28 20:16:44 +03:00
LinasKo 6bb5929e09 Remove IoU metric for now - unclear external API 2024-08-28 20:12:04 +03:00
LinasKo 6ac0f84e27 fix small object area calculation 2024-08-28 16:53:42 +03:00
LinasKo 162f9ed6d2 fix mAP for small/medium/large objects 2024-08-28 14:18:39 +03:00
Onuralp SEZER 7600bef8e7 Merge pull request #1488 from roboflow/dependabot/pip/notebook-7.2.2
⬆️ Bump notebook from 7.2.1 to 7.2.2
2024-08-28 04:18:22 +03:00
dependabot[bot] 9df96572ac ⬆️ Bump notebook from 7.2.1 to 7.2.2
Bumps [notebook](https://github.com/jupyter/notebook) from 7.2.1 to 7.2.2.
- [Release notes](https://github.com/jupyter/notebook/releases)
- [Changelog](https://github.com/jupyter/notebook/blob/@jupyter-notebook/tree@7.2.2/CHANGELOG.md)
- [Commits](https://github.com/jupyter/notebook/compare/@jupyter-notebook/tree@7.2.1...@jupyter-notebook/tree@7.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 00:44:02 +00:00
LinasKo 8ff0d31e57 Removed internal store from mAP 2024-08-27 21:28:13 +03:00
LinasKo 5160d20fbc Merge pull request #1487 from roboflow/feat/0.23.0-deprecations
0.23.0 deprecations, changelog updates
2024-08-27 17:35:17 +03:00
LinasKo 9f730b9fb5 Add "new" tags 2024-08-27 17:18:44 +03:00
LinasKo a7ecc57432 Remove deprecations, make changelog 2024-08-27 17:01:04 +03:00
LinasKo ec5790a90c Removed print statement internal_data_store 2024-08-27 15:58:25 +03:00
LinasKo 57e5c108de Merge branch 'develop' into feat/metrics-module-initial 2024-08-27 14:52:29 +03:00
LinasKo 84bb44d222 Metrics docs overhaul 2024-08-27 14:49:40 +03:00
LinasKo 026a243f81 Give descriptive names to data_1, data_2 in mAP 2024-08-27 14:49:40 +03:00
LinasKo d1e5e031e3 bugfix: incorrect indexing into 0-sized data in data stores 2024-08-27 14:49:40 +03:00
LinasKo a5bb93ddec bugfix: hstack confidence, class_id 2024-08-27 14:49:40 +03:00
LinasKo fe27e44081 supervision metrics module 2024-08-27 14:48:17 +03:00
LinasKo d21a6b1ee8 Merge pull request #1485 from roboflow/fix/poetry-lock
fix: 🐞 poetry lock file
2024-08-27 14:42:04 +03:00
Onuralp SEZER 395d90b5a6 fix: 🐞 poetry lock file
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-27 14:25:08 +03:00
LinasKo 635272b892 Merge pull request #1473 from roboflow/feat/formatter-improvements
formatter: 🔧 improvements for pre-commit and ruff also apply minor mypy/pep fixes
2024-08-27 14:17:06 +03:00
Onuralp SEZER 0edb590bb5 Merge branch 'develop' into feat/formatter-improvements 2024-08-27 14:12:14 +03:00
LinasKo 5aa4216c86 Merge pull request #1459 from roboflow/doc/ci-cookbooks-fixes
docs: cookbook visual fix for cards and small ci doc dev changes
2024-08-27 14:09:06 +03:00
LinasKo 1ae8fc798e Merge pull request #1448 from roboflow/feat/annotators-cleanup
Annotator types and docs: MyPy, consistent 'optional', small tidy-ups
2024-08-27 14:07:04 +03:00
LinasKo e8ec271f0b Merge pull request #930 from dfreilich/dfreilich/add-icon-annotator
[IconAnnotator] - Add IconAnnotator to Mark Objects with Custom Icons/Images
2024-08-27 13:51:35 +03:00
Onuralp SEZER 9e3a150196 Merge branch 'develop' into doc/ci-cookbooks-fixes 2024-08-27 01:36:04 +03:00
Onuralp SEZER acd23bf662 fix: 🐞 typos fixed and extra package removed
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-27 01:34:23 +03:00
Onuralp SEZER 5532731910 fix: 🐞 complete missing package for dev/doc installation
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-27 01:31:56 +03:00
Onuralp SEZER 029abbd614 fix: 🐞 cookbook cards are now clickable entirely without losing ability to click other elements
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-27 01:31:56 +03:00
Onuralp SEZER 46ecb808f7 ci(refactor): 👷 GitHub Actions workflow to consolidate dependency installation
- Combine multiple pip install commands into a single step
- Simplify the workflow by reducing redundancy
- Ensure all necessary dependencies are installed in one go

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-27 01:31:31 +03:00
LinasKo 5befed7332 Merge pull request #1481 from roboflow/fix/rename-manual-seek-to-iterative-seek
rename manual_seek to iterative_seek
2024-08-27 01:26:06 +03:00
LinasKo 15a192c63a Icon annotator example 2024-08-26 23:53:54 +03:00
LinasKo 317485b122 Transparency in letterbox & overlay image; image list for icon annotator 2024-08-26 23:15:32 +03:00
Onuralp SEZER 0f23c6d425 Merge pull request #1482 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-08-26 21:14:21 +03:00
pre-commit-ci[bot] 44d531fdb8 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2)
2024-08-26 17:54:38 +00:00
LinasKo a3262a8f9c Add types to dataset split func, add Optional to annotator 2024-08-26 14:56:59 +03:00
pre-commit-ci[bot] 7e9dca632b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-26 11:42:02 +00:00
LinasKo cc7de0edb7 rename manual_seek to iterative_seek 2024-08-26 14:40:43 +03:00
Onuralp SEZER 808814bce7 Merge pull request #1478 from roboflow/dependabot/pip/mypy-1.11.2
⬆️ Bump mypy from 1.11.1 to 1.11.2
2024-08-26 04:14:59 +03:00
Onuralp SEZER 89000c1c33 Merge pull request #1479 from roboflow/dependabot/pip/mkdocs-material-9.5.33
⬆️ Bump mkdocs-material from 9.5.32 to 9.5.33
2024-08-26 04:14:47 +03:00
dependabot[bot] 71cbd82ef4 ⬆️ Bump mkdocs-material from 9.5.32 to 9.5.33
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.32 to 9.5.33.
- [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.32...9.5.33)

---
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-08-26 00:43:30 +00:00
dependabot[bot] dff15cb8d6 ⬆️ Bump mypy from 1.11.1 to 1.11.2
Bumps [mypy](https://github.com/python/mypy) from 1.11.1 to 1.11.2.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.1...v1.11.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 00:41:49 +00:00
Onuralp SEZER befa12bb23 Merge pull request #1476 from J4BEZ/docs/match-title-content
match the titles and the contents
2024-08-23 20:20:35 +03:00
Ju Hoon Park f86a2d3a82 match the titles and the contents
Edit to the title and the content to match each other
2024-08-24 01:48:40 +09:00
Onuralp SEZER 3d7c1ba1ec feat(mypy): ✨ mypy type check libraries added as optional
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-23 19:45:38 +03:00
Onuralp SEZER a64a2fc243 fix(formatter): apply fixes from ruff and fix minor mypy and pep corrections
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-23 19:45:38 +03:00
Onuralp SEZER b65d2cc73f ci: 👷 retire isort and enable ruff's isort
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-23 19:45:18 +03:00
LinasKo 207260aca1 Merge pull request #1348 from roboflow/fix/no-frames-generated-for-some-videos
Change how frames are skipped in the generator
2024-08-23 19:16:50 +03:00
Onuralp SEZER 81b27340e5 Merge pull request #1474 from roboflow/dependabot/pip/ruff-0.6.2
⬆️ Bump ruff from 0.6.1 to 0.6.2
2024-08-23 04:24:18 +03:00
Onuralp SEZER a92665e85c Merge pull request #1475 from roboflow/dependabot/pip/ipywidgets-8.1.5
⬆️ Bump ipywidgets from 8.1.3 to 8.1.5
2024-08-23 04:23:34 +03:00
dependabot[bot] e7bf00aa95 ⬆️ Bump ipywidgets from 8.1.3 to 8.1.5
Bumps [ipywidgets](https://github.com/jupyter-widgets/ipywidgets) from 8.1.3 to 8.1.5.
- [Release notes](https://github.com/jupyter-widgets/ipywidgets/releases)
- [Commits](https://github.com/jupyter-widgets/ipywidgets/compare/8.1.3...8.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-23 01:05:47 +00:00
dependabot[bot] 392c6930bb ⬆️ Bump ruff from 0.6.1 to 0.6.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.1 to 0.6.2.
- [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.1...0.6.2)

---
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-23 01:05:03 +00:00
LinasKo ed253fef99 Merge pull request #1469 from roboflow/fix/doc-cherry-pick
documentation url typo fix
2024-08-20 17:40:26 +03:00
Ju Hoon Park cef25cc601 fix url
fix typo of url 'how_to/detect_and_annotate' to '/how_to/detect_and_annotate'
2024-08-20 17:22:53 +03:00
Onuralp SEZER e1b13e369c Merge pull request #1464 from roboflow/dependabot/pip/mkdocs-material-9.5.32
⬆️ Bump mkdocs-material from 9.5.31 to 9.5.32
2024-08-20 13:40:17 +03:00
Onuralp SEZER ed09f05424 Merge pull request #1465 from roboflow/dependabot/pip/mkdocs-git-revision-date-localized-plugin-1.2.7
⬆️ Bump mkdocs-git-revision-date-localized-plugin from 1.2.6 to 1.2.7
2024-08-20 13:39:56 +03:00
Onuralp SEZER 369831eb76 Merge pull request #1466 from venkatram-dev/fix_colab_serialize_detections_link
fix_colab_serialize_detections_link
2024-08-20 13:33:53 +03:00
pre-commit-ci[bot] 0d17a8e6b5 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-20 02:21:51 +00:00
venkatram-dev 2e4f05e8ec fix_colab_serialize_detections_link 2024-08-19 19:17:23 -07:00
dependabot[bot] 1891b2edde ⬆️ 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.6 to 1.2.7.
- [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.6...v1.2.7)

---
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-08-20 00:23:47 +00:00
dependabot[bot] 477faee1c7 ⬆️ Bump mkdocs-material from 9.5.31 to 9.5.32
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.31 to 9.5.32.
- [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.31...9.5.32)

---
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-08-20 00:22:22 +00:00
Onuralp SEZER 5957b250f0 Merge pull request #1462 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-08-19 21:52:14 +03:00
pre-commit-ci[bot] 7d27e67966 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.0 → v0.6.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.0...v0.6.1)
2024-08-19 17:48:34 +00:00
Onuralp SEZER c31d96dabe Merge branch 'develop' into feat/metrics-module-initial 2024-08-19 11:24:22 +03:00
Onuralp SEZER 4ab9ae5a71 Merge pull request #1460 from roboflow/dependabot/pip/ruff-0.6.1
⬆️ Bump ruff from 0.6.0 to 0.6.1
2024-08-19 11:19:04 +03:00
dependabot[bot] 888da4e208 ⬆️ Bump ruff from 0.6.0 to 0.6.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.0 to 0.6.1.
- [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.0...0.6.1)

---
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-19 01:08:15 +00:00
LinasKo 8f480ead6a Fix regression: OBB annotator 2024-08-19 00:52:58 +03:00
LinasKo cdc3b4d6b1 Typo in keypoint docstring 2024-08-19 00:33:16 +03:00
LinasKo bae1797e83 Remove "mypy type hint" from annotator scene assertions 2024-08-19 00:20:04 +03:00
LinasKo 7319a026ab Merge pull request #1458 from Kadermiyanyedi/add-outline-color-to-triangle-and-dot-annotator
[TriangleAnnotator, DotAnnotator] - allow customization of the contour color #1403
2024-08-18 23:31:41 +03:00
LinasKo 509b6da063 Indent docstring for outline_colors
* It would render the docs incorrectly
2024-08-18 23:23:54 +03:00
Kader Miyanyedi 10ea37b732 fix(TriangleAnnotator, DotAnnotator): fix tests 2024-08-16 17:15:35 +03:00
Kader Miyanyedi 6ce126d6d8 feat(TriangleAnnotator, DotAnnotator): add outline color to triangle and dot annotator 2024-08-16 16:38:30 +03:00
LinasKo ab76a47eb8 Refactored metrics store, removed typing extensions
* This commit is in a messy state - tests were not adapted to new metrics.core
* MeanAveragePrecision is incomplete
2024-08-16 09:59:43 +03:00
Onuralp SEZER 5261efebc3 Merge pull request #1455 from roboflow/dependabot/pip/ruff-0.6.0
⬆️ Bump ruff from 0.5.7 to 0.6.0
2024-08-16 03:43:32 +03:00
dependabot[bot] ca5486dc5a ⬆️ Bump ruff from 0.5.7 to 0.6.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.7 to 0.6.0.
- [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.5.7...0.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-16 00:34:32 +00:00
Onuralp SEZER eecc0dea91 Merge pull request #1454 from xaristeidou/add-ultralytics-SAM-support
Add ultralytics SAM support when using `bboxes` and `points` arguments in `SAM().predict()`
2024-08-15 22:57:40 +03:00
Onuralp SEZER cf7c95e700 fix: 🐞add missing import mask_to_xyxy
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-15 22:52:26 +03:00
Onuralp SEZER 366bc21d1e fix: pre-commit fix attempt for ruff
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-15 22:49:47 +03:00
Onuralp SEZER 526ddacd2f Merge branch 'develop' into add-ultralytics-SAM-support 2024-08-15 22:48:22 +03:00
Onuralp SEZER ab45b19e7e feat: ✨ add mask_to_xyxy for make boundingbox possible
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-08-15 22:05:54 +03:00
Christoforos Aristeidou 6289d9cab0 modified for dynamic array shape 2024-08-15 18:59:49 +03:00
LinasKo 85e0a544fe Return dataclass for IoU. plot + as_pandas in result. 2024-08-15 18:46:58 +03:00
LinasKo 6f47cd318a Add to_pandas to IoU 2024-08-15 15:10:36 +03:00
LinasKo 66758448cb Simplified core metrics
* Refactored code
* Removed None returns from InternalDataStore
* Removed support for np.ndarray inputs in IntersectionOverUnion
2024-08-15 14:48:04 +03:00
LinasKo c8673fe517 Fix opencv resizing, move offset to constructor 2024-08-15 13:30:18 +03:00
LinasKo b5a958745b Modernize, clean up IconAnnotator 2024-08-15 13:08:26 +03:00
LinasKo cfd079bbcb Merge branch 'develop' into dfreilich/add-icon-annotator 2024-08-15 11:38:13 +03:00
LinasKo 0e8365ff80 Add dependency: typing-extensions 2024-08-14 17:22:28 +03:00
LinasKo 758b9d60bd Expose class_id, add missing docs 2024-08-14 16:00:29 +03:00
LinasKo b40cddd144 Validate class IDs together 2024-08-14 15:50:45 +03:00
LinasKo dabd18c0de Remove duplicate box_iou_batch method 2024-08-14 15:43:10 +03:00
LinasKo e1ffd0e8a9 Address some review comments 2024-08-14 15:38:50 +03:00
LinasKo 731d580a83 Correct sizes in keypoint docstrings 2024-08-14 14:37:00 +03:00
LinasKo d4f95eca45 Minor: Insert missing sv. call in examples 2024-08-14 14:29:13 +03:00
LinasKo 0477499b77 Add error handling, clean up mypy confusion 2024-08-14 14:07:10 +03:00
LinasKo 29de4d245e Add annotator assertions to clarify ImageType
* Because we're generating docs from type annotations of function signatures, I can't find any other way to tell mypy that scene is np.ndarray
2024-08-14 13:05:01 +03:00
LinasKo 2012ec7bcb Unify how optional values are marked in docstings
* No longer means 'has default value'. Removed where it meant that.
* `Optional[datatype]` is now used instead of `datatype, optional`
* Fixed a handful of incorrect type annotations
2024-08-14 12:46:16 +03:00
LinasKo 5b3ce80a34 Change classproperty so sv.Color.RED retain type signature
* Previously sv.Color.RED would be seen as having type 'classproperty'
* Setting the type info during construvtion passes it to the return type
* Previously unhandled case where the class was missing is now explicitly checked for
* Broke down the inheritance from 'property', as it was marking the result as 'classproperty' instead of e.g. Color, but only until type was provided (but without mypy confusion).
2024-08-14 12:31:00 +03:00
LinasKo c3641de724 np.array -> np.ndarray 2024-08-14 12:14:26 +03:00
LinasKo 822dd4d555 Merge pull request #1409 from Bhavay-2001/VertexLabelAnnotator
Added text_color parameter for VertexLabelAnnotator
2024-08-14 10:02:15 +03:00
Onuralp SEZER 09e88da71a Merge pull request #1447 from roboflow/dependabot/pip/mike-2.1.3
⬆️ Bump mike from 2.1.2 to 2.1.3
2024-08-14 03:33:26 +03:00
Onuralp SEZER 31a2b26144 Merge pull request #1446 from roboflow/dependabot/pip/tox-4.18.0
⬆️ Bump tox from 4.17.1 to 4.18.0
2024-08-14 03:30:36 +03:00
dependabot[bot] f7713a32aa ⬆️ Bump mike from 2.1.2 to 2.1.3
Bumps [mike](https://github.com/jimporter/mike) from 2.1.2 to 2.1.3.
- [Release notes](https://github.com/jimporter/mike/releases)
- [Changelog](https://github.com/jimporter/mike/blob/master/CHANGES.md)
- [Commits](https://github.com/jimporter/mike/compare/v2.1.2...v2.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-14 00:29:23 +00:00
dependabot[bot] c97a885f9e ⬆️ Bump tox from 4.17.1 to 4.18.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.17.1 to 4.18.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.17.1...4.18.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-08-14 00:27:42 +00:00
Bhavay Malhotra b1cc5c3564 Changed rgb to bgr 2024-08-13 23:46:50 +05:30
LinasKo 91d8691bc4 docs polish 2024-08-13 16:15:15 +03:00
pre-commit-ci[bot] 072072e9b2 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-13 13:11:57 +00:00
LinasKo 148bb63aa8 Revert slow frame seek, add manual seek mode 2024-08-13 16:08:26 +03:00
Bhavay Malhotra d60443751a Merge branch 'VertexLabelAnnotator' of https://github.com/Bhavay-2001/roboflow-supervision into VertexLabelAnnotator 2024-08-13 17:48:52 +05:30
Bhavay Malhotra 1421f2c743 Removed image files 2024-08-13 17:48:42 +05:30
pre-commit-ci[bot] 751ea27263 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-13 12:11:27 +00:00
Bhavay Malhotra 78b6020ea4 Made the changes 2024-08-13 17:41:05 +05:30
Onuralp SEZER d08d22dec6 Merge pull request #1443 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-08-12 21:13:42 +03:00
pre-commit-ci[bot] ee1f15eebe chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.5.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.6...v0.5.7)
2024-08-12 17:47:10 +00:00
Christoforos Aristeidou d755bca7c8 added check and support for SAM ultralytics results 2024-08-12 15:27:04 +03:00
LinasKo 5565241ef5 Basic mask tests 2024-08-12 15:20:52 +03:00
pre-commit-ci[bot] a4cf2604ee fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-12 11:10:56 +00:00
LinasKo 3e860a5449 Add invalid value test 2024-08-12 14:09:14 +03:00
LinasKo 0305ceed45 Simplified tests 2024-08-12 13:31:44 +03:00
pre-commit-ci[bot] 4add8c871e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-12 09:24:05 +00:00
LinasKo 6f566b5518 Remove incorrect test 2024-08-12 12:23:36 +03:00
pre-commit-ci[bot] d6d71a0366 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-12 08:17:22 +00:00
LinasKo 4c903cf7f2 Metrics changes + tests 2024-08-12 11:16:50 +03:00
pre-commit-ci[bot] b061f4fd84 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-12 08:05:43 +00:00
Bhavay Malhotra 2b73aea438 Delete temp.py 2024-08-09 23:20:05 +05:30
Onuralp SEZER db958d9df8 Merge pull request #1439 from roboflow/dependabot/pip/ruff-0.5.7
⬆️ Bump ruff from 0.5.6 to 0.5.7
2024-08-09 08:21:49 +03:00
dependabot[bot] f699f087db ⬆️ Bump ruff from 0.5.6 to 0.5.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.6 to 0.5.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.5.6...0.5.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-08-09 00:26:37 +00:00
LinasKo 7d7e5f17a5 Initial attempt at definining a metric 2024-08-08 19:26:04 +03:00
Piotr Skalski d486dd1083 Merge pull request #1434 from roboflow/feature/refactor_inference_slicer_overlap_definition
refactor `InferenceSlicer` overlap logic
2024-08-08 12:39:58 +02:00
SkalskiP 5123085037 after review 2024-08-08 12:05:23 +02:00
Piotr Skalski 43041c26c5 Merge pull request #1385 from roboflow/add-backgroundcolor-annotator
Add BackgroundColorAnnotator
2024-08-08 11:40:48 +02:00
Onuralp SEZER ca2902cc46 Merge pull request #1436 from roboflow/dependabot/pip/tox-4.17.1
⬆️ Bump tox from 4.17.0 to 4.17.1
2024-08-08 03:12:26 +03:00
dependabot[bot] 2365ed47e1 ⬆️ Bump tox from 4.17.0 to 4.17.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.17.0 to 4.17.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.17.0...4.17.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-08-08 00:10:14 +00:00
pre-commit-ci[bot] 78689590e6 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-07 17:20:12 +00:00
Bhavay Malhotra d73187f3dd Merge branch 'VertexLabelAnnotator' of https://github.com/Bhavay-2001/roboflow-supervision into VertexLabelAnnotator 2024-08-07 22:49:07 +05:30
Bhavay Malhotra d1df96a953 Changed printing statement 2024-08-07 22:47:50 +05:30
pre-commit-ci[bot] c1650d3770 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-07 17:10:47 +00:00
Bhavay Malhotra a72c5044f4 Merge branch 'VertexLabelAnnotator' of https://github.com/Bhavay-2001/roboflow-supervision into VertexLabelAnnotator 2024-08-07 22:39:45 +05:30
Bhavay Malhotra 4e7f69a502 Changed text_color param 2024-08-07 22:39:23 +05:30
pre-commit-ci[bot] 6d719fcd60 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-07 17:02:40 +00:00
Bhavay Malhotra ac09b58620 Changed text_color param 2024-08-07 22:32:15 +05:30
LinasKo b9cc02ecb9 Fix annotator carousel image 2024-08-07 17:29:01 +03:00
LinasKo 00b00d2f9a Rename to BackgoundOverlayAnnotator, fix return after copy 2024-08-07 16:39:22 +03:00
pre-commit-ci[bot] 5a9e85a60f fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-07 10:19:07 +00:00
James fea77d86a7 update return statement 2024-08-07 11:17:35 +01:00
James 11466ff261 fix rgb channel order 2024-08-07 09:57:57 +01:00
James 80549933fc fix rgb channel order 2024-08-07 09:55:06 +01:00
pre-commit-ci[bot] a428fac98d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-07 08:48:45 +00:00
James a109b264ef fix return type 2024-08-07 09:48:28 +01:00
James 389b8968c2 add BackgroundColorAnnotator docs 2024-08-07 09:21:16 +01:00
James b000d162e7 respond to feedback 2024-08-07 09:19:17 +01:00
Onuralp SEZER 66e1672f37 Merge pull request #1435 from roboflow/dependabot/pip/pyyaml-6.0.2
⬆️ Bump pyyaml from 6.0.1 to 6.0.2
2024-08-07 07:13:51 +03:00
dependabot[bot] 52e7fe1d30 ⬆️ Bump pyyaml from 6.0.1 to 6.0.2
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/main/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/6.0.1...6.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 00:49:09 +00:00
SkalskiP b61c5f23c6 Merge remote-tracking branch 'origin/feature/refactor_inference_slicer_overlap_definition' into feature/refactor_inference_slicer_overlap_definition 2024-08-06 19:11:50 +02:00
SkalskiP 0285bfa50a after review 2024-08-06 19:11:42 +02:00
pre-commit-ci[bot] 0069d6b289 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-06 14:37:00 +00:00
SkalskiP ebba78528e after review 2024-08-06 16:36:40 +02:00
SkalskiP da2bc575e0 Merge remote-tracking branch 'origin/feature/refactor_inference_slicer_overlap_definition' into feature/refactor_inference_slicer_overlap_definition 2024-08-06 15:51:18 +02:00
SkalskiP 25ea63042d small bug fix 2024-08-06 15:51:06 +02:00
pre-commit-ci[bot] 24debfe377 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-06 13:34:51 +00:00
SkalskiP 414ee9166d small bug fix 2024-08-06 15:34:13 +02:00
pre-commit-ci[bot] edc4dd33b0 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-06 13:26:54 +00:00
SkalskiP 22b970e4d4 ready for test 2024-08-06 15:24:21 +02:00
Onuralp SEZER e367e307fb Merge pull request #1431 from roboflow/dependabot/pip/tox-4.17.0
⬆️ Bump tox from 4.16.0 to 4.17.0
2024-08-06 06:45:40 +03:00
dependabot[bot] 69ffca50d5 ⬆️ Bump tox from 4.16.0 to 4.17.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.16.0 to 4.17.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.16.0...4.17.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-08-06 00:15:53 +00:00
Onuralp SEZER 01fd82c43b Merge pull request #1430 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-08-05 21:50:24 +03:00
pre-commit-ci[bot] f04bdb2491 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.5...v0.5.6)
2024-08-05 18:02:54 +00:00
Piotr Skalski aa6c673316 Merge pull request #1386 from shaddu/develop
Support for transformers v5 post_process functions
2024-08-05 15:57:41 +02:00
pre-commit-ci[bot] b2752ac6c6 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-05 13:46:54 +00:00
SkalskiP 7c038f1d45 ready for tests 2024-08-05 15:46:35 +02:00
pre-commit-ci[bot] b6547ba58e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-05 13:03:08 +00:00
SkalskiP fde9cd4735 Merge remote-tracking branch 'origin/develop' into develop 2024-08-05 15:02:51 +02:00
SkalskiP 1d3e423617 small refactor and docs improvement 2024-08-05 15:02:41 +02:00
pre-commit-ci[bot] 9fc66563dd fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-05 12:23:53 +00:00
SkalskiP 8a82f4a516 small refactor and docs improvement 2024-08-05 14:23:34 +02:00
Onuralp SEZER 17d1934ed5 Merge pull request #1427 from roboflow/dependabot/pip/wheel-0.44.0
⬆️ Bump wheel from 0.43.0 to 0.44.0
2024-08-05 04:50:27 +03:00
dependabot[bot] 21fcf1bd04 ⬆️ Bump wheel from 0.43.0 to 0.44.0
Bumps [wheel](https://github.com/pypa/wheel) from 0.43.0 to 0.44.0.
- [Release notes](https://github.com/pypa/wheel/releases)
- [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst)
- [Commits](https://github.com/pypa/wheel/compare/0.43.0...0.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 01:38:46 +00:00
Onuralp SEZER ebd606ff00 Merge pull request #1428 from roboflow/dependabot/pip/mkdocs-material-9.5.31
⬆️ Bump mkdocs-material from 9.5.30 to 9.5.31
2024-08-05 04:35:26 +03:00
Onuralp SEZER f1241e4fe8 Merge pull request #1426 from roboflow/dependabot/pip/ruff-0.5.6
⬆️ Bump ruff from 0.5.5 to 0.5.6
2024-08-05 04:34:44 +03:00
Onuralp SEZER 9de14b5c67 Merge pull request #1425 from roboflow/dependabot/pip/tqdm-4.66.5
⬆️ Bump tqdm from 4.66.4 to 4.66.5
2024-08-05 04:34:31 +03:00
dependabot[bot] 706c2d19b9 ⬆️ Bump mkdocs-material from 9.5.30 to 9.5.31
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.30 to 9.5.31.
- [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.30...9.5.31)

---
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-08-05 01:06:36 +00:00
dependabot[bot] 7a81c13543 ⬆️ Bump ruff from 0.5.5 to 0.5.6
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.5 to 0.5.6.
- [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.5.5...0.5.6)

---
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-05 01:04:33 +00:00
dependabot[bot] 4648276cc6 ⬆️ Bump tqdm from 4.66.4 to 4.66.5
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.4 to 4.66.5.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.4...v4.66.5)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 01:03:29 +00:00
Shadab 2c235dfce9 support of optional mask in convert_to_detections 2024-08-03 13:38:15 +01:00
pre-commit-ci[bot] dacd8f5455 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-02 23:37:34 +00:00
Shadab da261ff8cf cyclic dependency fix + code refactor 2024-08-03 00:36:36 +01:00
shaddu a5ae72d7ae Merge branch 'roboflow:develop' into develop 2024-08-01 23:58:59 +01:00
pre-commit-ci[bot] fd568e2419 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-08-01 22:22:03 +00:00
Shadab 7c7c3d83ef code refactor for transformers v5 support 2024-08-01 23:21:47 +01:00
Onuralp SEZER 03dba9c7a2 Merge pull request #1420 from roboflow/dependabot/pip/jupytext-1.16.4
⬆️ Bump jupytext from 1.16.3 to 1.16.4
2024-08-02 00:09:03 +03:00
Onuralp SEZER 1d0616640e Merge pull request #1414 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-08-01 23:50:10 +03:00
shaddu f6437ab778 Merge branch 'roboflow:develop' into develop 2024-08-01 21:32:39 +01:00
LinasKo 7ad22c73a8 Merge pull request #1424 from roboflow/docs/docstring-for-plot-image-inches
specify that plot_image size is in inches
2024-08-01 17:09:36 +03:00
LinasKo 54903b1cc0 specify that plot_image size is in inches 2024-08-01 17:03:36 +03:00
dependabot[bot] e566ac9eb3 ⬆️ Bump jupytext from 1.16.3 to 1.16.4
Bumps [jupytext](https://github.com/mwouts/jupytext) from 1.16.3 to 1.16.4.
- [Release notes](https://github.com/mwouts/jupytext/releases)
- [Changelog](https://github.com/mwouts/jupytext/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mwouts/jupytext/compare/v1.16.3...v1.16.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 00:27:25 +00:00
shaddu f95f81eaf3 Merge branch 'roboflow:develop' into develop 2024-07-31 20:51:21 +01:00
Onuralp SEZER f2ede43d0e Merge pull request #1416 from roboflow/dependabot/pip/mypy-1.11.1
⬆️ Bump mypy from 1.11.0 to 1.11.1
2024-07-31 08:43:56 +03:00
dependabot[bot] f2ef9f817c ⬆️ Bump mypy from 1.11.0 to 1.11.1
Bumps [mypy](https://github.com/python/mypy) from 1.11.0 to 1.11.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11...v1.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 00:52:03 +00:00
shaddu 7bba367f98 Merge branch 'roboflow:develop' into develop 2024-07-29 21:09:22 +01:00
pre-commit-ci[bot] 977892ad1f chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5)
2024-07-29 17:53:45 +00:00
Linas Kondrackis 2ca696e064 Merge pull request #1410 from arthurcerveira/refactor/lazy-loading-for-benchmarks
Refactor the metric benchmarks to use dataset lazy loading
2024-07-29 10:40:08 +03:00
Shadab 3bb384d789 E501 Line too long (95 > 88) fix 2024-07-28 17:01:10 +01:00
pre-commit-ci[bot] ac1fa2edca fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-27 22:24:41 +00:00
Shadab 4ce9d76b65 support added for post_process_panoptic + post_process_panoptic_segmentation
+ post_process_instance_segmentation
2024-07-27 23:24:25 +01:00
shaddu 1a1a2d27ce Merge branch 'roboflow:develop' into develop 2024-07-27 23:17:06 +01:00
arthurcerveira c9598ef732 refactor: use image lazy loading for benchmarks 2024-07-26 11:16:23 -03:00
pre-commit-ci[bot] 4c01eb4a3d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-26 13:18:22 +00:00
Bhavay Malhotra f9471184d7 Added text_color parameter 2024-07-26 18:45:26 +05:30
Piotr Skalski bb0e4ec059 Merge pull request #1387 from Bhavay-2001/text_color_param
Added Union[Color, ColorPalette] to text_color parameter
2024-07-26 09:27:40 +02:00
Onuralp SEZER 7f8b6d3c5e Merge pull request #1406 from roboflow/dependabot/pip/mkdocstrings-0.25.2
⬆️ Bump mkdocstrings from 0.25.1 to 0.25.2
2024-07-26 04:53:39 +03:00
Onuralp SEZER c6a756352a Merge pull request #1405 from roboflow/dependabot/pip/pytest-8.3.2
⬆️ Bump pytest from 8.3.1 to 8.3.2
2024-07-26 04:53:25 +03:00
Onuralp SEZER 66456a893c Merge pull request #1407 from roboflow/dependabot/pip/ruff-0.5.5
⬆️ Bump ruff from 0.5.4 to 0.5.5
2024-07-26 04:53:01 +03:00
dependabot[bot] 8704a46e96 ⬆️ Bump ruff from 0.5.4 to 0.5.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.4 to 0.5.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.5.4...0.5.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-07-26 00:52:26 +00:00
dependabot[bot] 3de8ca609f ⬆️ Bump mkdocstrings from 0.25.1 to 0.25.2
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.25.1 to 0.25.2.
- [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.1...0.25.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-26 00:51:21 +00:00
dependabot[bot] 586387ab3a ⬆️ Bump pytest from 8.3.1 to 8.3.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.1 to 8.3.2.
- [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.1...8.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-26 00:49:58 +00:00
Bhavay Malhotra 37a988fd9f Removed text_color_lookup 2024-07-25 22:03:44 +05:30
Piotr Skalski cb6045b026 Add snyk.io badge README.md 2024-07-25 17:32:08 +02:00
Bhavay Malhotra 9956d5dd82 Merge branch 'text_color_param' of https://github.com/Bhavay-2001/roboflow-supervision into text_color_param 2024-07-25 14:02:57 +05:30
Bhavay Malhotra b103ce4eb7 Added text_color param to RichLabelAnnotator 2024-07-25 14:02:43 +05:30
Onuralp SEZER 22df9a5252 Merge pull request #1400 from Gk-rohan/develop
Fix typo in RichLabelAnnotator docs
2024-07-24 18:05:31 +03:00
Onuralp SEZER b030d1dc9f Merge pull request #1398 from roboflow/dependabot/pip/mkdocs-material-9.5.30
⬆️ Bump mkdocs-material from 9.5.29 to 9.5.30
2024-07-24 17:16:20 +03:00
Rohan Gupta 387a759614 Fix typo in RichLabelAnnotator docs 2024-07-24 17:37:00 +05:30
Piotr Skalski 5dd8910e02 Merge pull request #1399 from roboflow/fix/vertex_label_annotator_text_position
fix `VertexLabelAnnotator` text position within text box
2024-07-24 13:28:38 +02:00
SkalskiP b19c812f58 ready for test 2024-07-24 13:17:03 +02:00
SkalskiP e58939235f ready for test 2024-07-24 13:12:39 +02:00
pre-commit-ci[bot] b8e1612511 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-24 07:54:27 +00:00
Bhavay Malhotra a0abf2dee7 Updated RichLabelAnnotator 2024-07-24 13:24:06 +05:30
dependabot[bot] f5bced4510 ⬆️ Bump mkdocs-material from 9.5.29 to 9.5.30
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.29 to 9.5.30.
- [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.29...9.5.30)

---
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-07-24 00:36:11 +00:00
Onuralp SEZER a8d91b0ae5 Merge pull request #1393 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-07-22 20:53:36 +03:00
pre-commit-ci[bot] 307821ca25 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.2 → v0.5.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.2...v0.5.4)
2024-07-22 17:51:12 +00:00
Bhavay Malhotra c5d052ed16 text_color from DEFAULT to LEGACY 2024-07-22 14:36:55 +05:30
Bhavay Malhotra e2f33faa67 Merge branch 'text_color_param' of https://github.com/Bhavay-2001/roboflow-supervision into text_color_param 2024-07-22 13:10:13 +05:30
Bhavay Malhotra 1f6694ba50 Changed formatting 2024-07-22 13:10:04 +05:30
pre-commit-ci[bot] 01922f849e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-22 07:38:00 +00:00
Bhavay Malhotra 3fb6a7ba6e :wq
Merge branch 'text_color_param' of https://github.com/Bhavay-2001/roboflow-supervision into text_color_param
2024-07-22 13:06:48 +05:30
Bhavay Malhotra 16b8102900 Added custom text color lookup 2024-07-22 13:06:38 +05:30
Onuralp SEZER acb5dd0dd4 Merge pull request #1389 from roboflow/dependabot/pip/ruff-0.5.4
⬆️ Bump ruff from 0.5.3 to 0.5.4
2024-07-22 03:27:08 +03:00
Onuralp SEZER b325eebe00 Merge pull request #1390 from roboflow/dependabot/pip/mypy-1.11.0
⬆️ Bump mypy from 1.10.1 to 1.11.0
2024-07-22 03:26:47 +03:00
Onuralp SEZER eb0041d275 Merge pull request #1391 from roboflow/dependabot/pip/pytest-8.3.1
⬆️ Bump pytest from 8.2.2 to 8.3.1
2024-07-22 03:26:18 +03:00
dependabot[bot] 3ccd74d93e ⬆️ Bump pytest from 8.2.2 to 8.3.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.2 to 8.3.1.
- [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.2.2...8.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-22 00:24:56 +00:00
dependabot[bot] e0d06f9a64 ⬆️ Bump mypy from 1.10.1 to 1.11.0
Bumps [mypy](https://github.com/python/mypy) from 1.10.1 to 1.11.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.1...v1.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-22 00:23:56 +00:00
dependabot[bot] d63e7b4677 ⬆️ Bump ruff from 0.5.3 to 0.5.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.3 to 0.5.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.5.3...0.5.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-07-22 00:23:14 +00:00
pre-commit-ci[bot] 0210f51944 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-20 12:14:33 +00:00
Bhavay Malhotra 073500bf75 Reduced length of the line 2024-07-20 17:44:13 +05:30
Bhavay Malhotra 60d33ca140 Updated the text_color parameter 2024-07-20 17:30:18 +05:30
pre-commit-ci[bot] e65892c80d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-20 01:23:08 +00:00
shaddu 24320405b4 Merge branch 'roboflow:develop' into develop 2024-07-20 02:10:43 +01:00
Shadab b5ea2cd1cb Support for post_process_semantic_segmentation added 2024-07-20 02:08:37 +01:00
James c26814da87 remove debug statement 2024-07-19 16:08:59 -07:00
pre-commit-ci[bot] da8761bd96 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-19 23:00:19 +00:00
James b8a2fb50d4 add BackgroundColorAnnotator 2024-07-19 15:45:26 -07:00
Piotr Skalski 310b566c9b Merge pull request #520 from roboflow/add-dataset-processing-docs
Add dataset processing documentation
2024-07-19 13:50:45 +02:00
pre-commit-ci[bot] 43c31f933c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-19 11:47:21 +00:00
SkalskiP 3a54afe506 Merge remote-tracking branch 'origin/add-dataset-processing-docs' into add-dataset-processing-docs
# Conflicts:
#	docs/how_to/process_datasets.md
2024-07-19 13:45:53 +02:00
SkalskiP 891b8523f0 ready to merge 2024-07-19 13:44:48 +02:00
Onuralp SEZER bc12bd38b4 Merge pull request #1382 from roboflow/dependabot/pip/ruff-0.5.3
⬆️ Bump ruff from 0.5.2 to 0.5.3
2024-07-19 10:54:46 +03:00
dependabot[bot] 0cb66e5551 ⬆️ Bump ruff from 0.5.2 to 0.5.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.2 to 0.5.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.5.2...0.5.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-07-19 00:54:29 +00:00
Piotr Skalski b81e92ca94 Merge pull request #1380 from onuralpszr/bug/poetry-numpy 2024-07-18 20:02:02 +02:00
Piotr Skalski 7ca5b01a75 Merge pull request #1350 from eric220/develop
Move OBB boxes
2024-07-18 15:45:17 +02:00
Onuralp SEZER 44753536f3 fix: 🐞 poetry numpy version has been set based on python version
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-07-18 09:54:58 +03:00
Piotr Skalski d774c91d7b Merge pull request #1379 from roboflow/fix/scale-factor-float 2024-07-18 07:32:52 +02:00
Brad Dwyer 2abb76e167 Change CropAnnotator scale_factor from int to float 2024-07-17 17:15:51 -07:00
Piotr Skalski 75d4b9f20a Merge pull request #1375 from Bhavay-2001/LabelAnnotator_text_color
Changed as_rgb() to as_bgr() in LabelAnnotator
2024-07-17 17:58:02 +02:00
Piotr Skalski 056401a858 Merge pull request #1377 from roboflow/fix/add_open_in_colab_button_to_quickstart
add `open in colab` button to quickstart notebook
2024-07-17 17:57:20 +02:00
pre-commit-ci[bot] b678ac929e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-17 15:50:30 +00:00
SkalskiP b7b19b0357 add open in colab button to quickstart notebook 2024-07-17 17:47:39 +02:00
Bhavay Malhotra 46ac1812d8 Changed rgb to bgr 2024-07-17 20:13:24 +05:30
Eric Criteser b21a478528 eliminate alias obc, rename function to move_oriented_boxes, return using broadcasting, change docstring 2024-07-17 08:25:44 -05:00
Piotr Skalski 65f2bbba57 Merge pull request #1374 from roboflow/fix/incorrent_annotators_docstring
fix incorrect annotators docstring
2024-07-17 11:46:25 +02:00
SkalskiP d0111bf41d fix incorrect annotators docstring 2024-07-17 11:36:23 +02:00
Piotr Skalski 31db574e63 Merge pull request #1372 from roboflow/fix/from_matplotlib_when_colormap_does_not_provide_colors_attribute
fix `from_matplotlib` when Matplotlib colormaps does not provide `colors` property
2024-07-17 10:03:11 +02:00
pre-commit-ci[bot] c930dd06ec fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-17 07:45:12 +00:00
SkalskiP 83dd92726e fix from_matplotlib when Matplotlib colormaps does not provide colors property 2024-07-17 09:42:37 +02:00
pre-commit-ci[bot] 59f3e19d2d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-17 06:54:59 +00:00
SkalskiP 73ba5f039b updates 2024-07-17 08:54:41 +02:00
Onuralp SEZER e76dec7e15 Merge pull request #1370 from roboflow/dependabot/github_actions/actions/checkout-4
⬆️ Bump actions/checkout from 2 to 4
2024-07-17 03:42:21 +03:00
dependabot[bot] 8a67ca073a ⬆️ Bump actions/checkout from 2 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 00:39:28 +00:00
Onuralp SEZER 2b7cd6ab23 Merge pull request #1302 from onuralpszr/notebook-bot
ci: 👷 check notebook and automatically create colab link and show better difference in reviewNB
2024-07-16 19:24:36 +03:00
Eric Criteser 9b0568c5e7 change import config to import config.OREIENTED_BOX_COORDINATES 2024-07-16 10:00:00 -05:00
pre-commit-ci[bot] 48f6c8d3c1 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-16 13:55:25 +00:00
Eric Criteser 2544d7293d update entire methods: move_obb_boxes and move detections 2024-07-16 08:54:55 -05:00
Eric Criteser c71d9458c8 added email to the push 2024-07-16 08:47:10 -05:00
Onuralp SEZER c28c9abd5b Merge pull request #1354 from roboflow/docs/release-process
Update release_process.md
2024-07-16 11:52:31 +03:00
Onuralp SEZER c69ee8046a docs: 📝 numbering order fix 2024-07-15 21:27:11 +03:00
Onuralp SEZER 65894c9e39 docs: 📝 document publish repo revert to main 2024-07-15 21:26:17 +03:00
Onuralp SEZER 55fd9fca1f Merge pull request #1363 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-07-15 20:55:56 +03:00
pre-commit-ci[bot] f24e846272 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.2)
2024-07-15 17:50:22 +00:00
SkalskiP d91113c9e7 merge develop into task branch 2024-07-15 12:32:31 +02:00
SkalskiP 94a9b73117 Merge branch 'develop' into add-dataset-processing-docs
# Conflicts:
#	mkdocs.yml
2024-07-15 12:31:55 +02:00
Piotr Skalski 4ef027462f Update README.md 2024-07-15 11:07:05 +02:00
Onuralp SEZER a788623ea3 Merge pull request #1358 from roboflow/dependabot/pip/ruff-0.5.2
⬆️ Bump ruff from 0.5.1 to 0.5.2
2024-07-15 03:38:59 +03:00
Onuralp SEZER 94d3f3829b Merge pull request #1357 from roboflow/dependabot/pip/mkdocs-material-9.5.29
⬆️ Bump mkdocs-material from 9.5.28 to 9.5.29
2024-07-15 03:38:28 +03:00
dependabot[bot] 321e01157a ⬆️ Bump ruff from 0.5.1 to 0.5.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.1 to 0.5.2.
- [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.5.1...0.5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 00:37:30 +00:00
dependabot[bot] 97b2ba2d6a ⬆️ Bump mkdocs-material from 9.5.28 to 9.5.29
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.28 to 9.5.29.
- [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.28...9.5.29)

---
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-07-15 00:36:31 +00:00
pre-commit-ci[bot] 5ee8fcfea2 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-13 13:19:23 +00:00
Eric Criteser c9d4f2b82e commit under new branch 2024-07-13 08:18:47 -05:00
Linas Kondrackis aa003d0161 Update release_process.md 2024-07-12 20:23:44 +03:00
Linas Kondrackis 7decfc7d1e Merge pull request #1352 from roboflow/main
Merge pull request #1351 from roboflow/develop
2024-07-12 20:01:49 +03:00
Linas Kondrackis 93c1b947fc Merge pull request #1351 from roboflow/develop
`supervision-0.22.0` release
2024-07-12 19:59:08 +03:00
SkalskiP 66d1f4c67d bump version from 0.22.0rc1 to 0.22.0 2024-07-12 18:53:53 +02:00
Linas Kondrackis 70a13fbf1e Merge pull request #1342 from roboflow/release/supervision-0.22.0-deprecations
Deprecations, changelog, notebook cleanup, 'new' markers
2024-07-12 19:49:17 +03:00
SkalskiP 1db8e12e1b small changes 2024-07-12 18:47:41 +02:00
LinasKo 8853321058 Release process: push to 'main', not 'master' 2024-07-12 19:10:25 +03:00
LinasKo 0a5e830159 Add release process doc 2024-07-12 18:59:06 +03:00
pre-commit-ci[bot] fb041f9b6c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-12 15:20:00 +00:00
LinasKo 28f83b18f9 Deprecate BoundingBoxAnnotator, rename to BoxAnnotator 2024-07-12 18:18:50 +03:00
Eric Criteser cea854edf5 small change to comments, added email to submission 2024-07-12 09:34:26 -05:00
Eric Criteser 9a61b3c41b added email to the push 2024-07-12 09:26:27 -05:00
Eric Criteser 113d4b697a change move_obb_boxes to accept multiple detections 2024-07-12 08:51:46 -05:00
LinasKo feb779bae9 Extended code examples in changelog 2024-07-12 15:48:07 +03:00
LinasKo 521dc57b76 Brief early mention that Dataset.images is deprecated 2024-07-12 15:18:20 +03:00
LinasKo a0775544df Fix typo: future deprecation will be removed, not 'were' removed 2024-07-12 15:11:17 +03:00
LinasKo 14a0e299a1 Changelog, reorder deprecations 2024-07-12 15:08:11 +03:00
LinasKo 1e783b623b Wording: <TTF_FONT_PATH> 2024-07-12 15:05:09 +03:00
LinasKo c7a8654390 deprections text: five subsequent release 2024-07-12 15:03:24 +03:00
Piotr Skalski a7f8aee2c7 Merge pull request #1332 from roboflow/feat/cheatsheet-links
Add cheatsheet links in supervision docs, readme
2024-07-12 13:25:15 +02:00
Piotr Skalski 5ed03491f9 Merge pull request #1327 from roboflow/fix/florence2-region-xyxy
Fix bug where florence2 returns normalized xyxy regions
2024-07-12 12:17:18 +02:00
LinasKo 2260cdd3de Change how frames are skipped in the generator 2024-07-12 12:12:12 +03:00
Eric Criteser 377ca27cc9 change move_obb_boxesto accept array of arrays 2024-07-11 18:00:46 -05:00
LinasKo 5d979df2f9 Add CropAnnotator PR link 2024-07-12 01:19:46 +03:00
Eric Criteser 9d5a477064 correct check to detections.data 2024-07-11 16:44:23 -05:00
LinasKo 5006f8c2f7 Add heading to deprecations page 2024-07-12 00:19:46 +03:00
LinasKo 8570ce68e2 'new' marker on Detections/Utils 2024-07-12 00:18:21 +03:00
LinasKo 3d3bbb4095 Add recently removed deprecations 2024-07-12 00:08:12 +03:00
LinasKo a1e3543897 Redo demo.ipynb, quickstart.ipynb images 2024-07-12 00:04:48 +03:00
LinasKo 4b82679b00 Add to changelog: KeyPoints.from_detectron2, xcycwh_to_xyxy 2024-07-11 23:17:18 +03:00
Eric Criteser 7fa7907219 Run pre-commit and fix issues 2024-07-11 14:42:13 -05:00
Eric Criteser 60d5660286 added functionality for oriented bounding boxes 2024-07-11 13:38:58 -05:00
Linas Kondrackis 5d4abe6e0d Merge pull request #1310 from onuralpszr/detectron2-keypoint
feat: 🚀 Keypoint support for detectron2
2024-07-11 14:58:21 +03:00
Piotr Skalski 0405c74cf3 Merge pull request #1340 from roboflow/feature/box_utils
`xywh_to_xyxy` and `xcycwh_to_xyxy` box utils
2024-07-11 11:20:19 +02:00
Onuralp SEZER 13fe6e0942 fix: 🐞 add check if keypoints size is 0 return empty keypoint result
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-07-11 04:41:29 +03:00
Onuralp SEZER 03c92f7652 Merge pull request #1341 from roboflow/dependabot/pip/jupytext-1.16.3
⬆️ Bump jupytext from 1.16.2 to 1.16.3
2024-07-11 04:14:37 +03:00
LinasKo 084926ded3 Deprecations, changelog, notebook cleanup, 'new' markers 2024-07-11 03:21:41 +03:00
dependabot[bot] 382ed8e609 ⬆️ Bump jupytext from 1.16.2 to 1.16.3
Bumps [jupytext](https://github.com/mwouts/jupytext) from 1.16.2 to 1.16.3.
- [Release notes](https://github.com/mwouts/jupytext/releases)
- [Changelog](https://github.com/mwouts/jupytext/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mwouts/jupytext/compare/v1.16.2...v1.16.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-11 00:17:30 +00:00
pre-commit-ci[bot] 2ed2a793cd fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-10 10:15:01 +00:00
SkalskiP e0fb73f18a xywh_to_xyxy and xcycwh_to_xyxy box utils 2024-07-10 12:13:07 +02:00
Piotr Skalski de896189b8 Merge pull request #1326 from roboflow/feat/dataset-lazy-loading
Lazy Dataset Loading
2024-07-10 11:52:57 +02:00
Onuralp SEZER fbfd078795 Merge pull request #1336 from cdragos/docs/revise-contributing-guidelines
docs: revise and restructure CONTRIBUTING.md
2024-07-09 21:34:43 +03:00
Dragos Catarahia e31aad242a docs: revise and restructure CONTRIBUTING.md 2024-07-10 01:22:23 +09:00
LinasKo 93c5c39f8f as_pascal_voc now loads image paths 2024-07-09 15:30:19 +03:00
LinasKo 810148550b Bugfix: XML parse in object_to_pascal_voc 2024-07-09 15:23:48 +03:00
LinasKo 172877a3cf Fix accidental changes, rearrange utils, rename methods 2024-07-09 14:56:30 +03:00
pre-commit-ci[bot] a91fc1e4f3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-09 11:31:00 +00:00
LinasKo f4fd595783 Fix image poaths in save_dataset, copy images if lazy 2024-07-09 14:30:07 +03:00
LinasKo e690b92688 Move back save_images to utils 2024-07-09 14:17:04 +03:00
LinasKo c8f771ec69 Move coco and yolo loaders back into utils 2024-07-09 14:04:11 +03:00
LinasKo 6b09e1d67c ClassificationDataset, moved coco save function into dataset 2024-07-09 11:57:42 +03:00
LinasKo 12195705f5 LazyLoaded DetectionDataset 2024-07-09 02:13:56 +03:00
Onuralp SEZER 9f7871b4be Merge pull request #1334 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-07-08 22:26:54 +03:00
pre-commit-ci[bot] 44890dc712 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1)
2024-07-08 17:39:23 +00:00
LinasKo 57ae7467b9 Add cheatsheet links in supervision docs, readme 2024-07-08 13:57:12 +03:00
Iuri de Silvio 1e5e25c54a Merge pull request #1331 from roboflow/dependabot/pip/ruff-0.5.1
⬆️ Bump ruff from 0.5.0 to 0.5.1
2024-07-08 10:17:18 +02:00
dependabot[bot] ba7c4f4404 ⬆️ Bump ruff from 0.5.0 to 0.5.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.0 to 0.5.1.
- [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.5.0...0.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 01:13:42 +00:00
Onuralp SEZER f3b52c08a7 Merge pull request #1329 from roboflow/dependabot/pip/certifi-2024.7.4
⬆️ Bump certifi from 2024.6.2 to 2024.7.4
2024-07-06 11:39:25 +03:00
dependabot[bot] 1a36842407 ⬆️ Bump certifi from 2024.6.2 to 2024.7.4
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-06 02:32:58 +00:00
LinasKo a8b12914c8 Fix florence 2 tests 2024-07-05 13:21:59 +03:00
LinasKo d3db15fcd0 Fix bug where florence2 returns normalized xyxy regions 2024-07-05 13:14:27 +03:00
Linas Kondrackis c40734fb8d Merge pull request #1316 from David-rn/feat/facemesh_from_mediapipe
Facemesh from mediapipe
2024-07-05 12:56:50 +03:00
LinasKo 55a9cef270 Minor edit: add Python label to dosctring example 2024-07-05 12:41:02 +03:00
LinasKo 4b9b70fbb2 LazyDataset Loading - accept List[str] of images
* Unfinished
* Untested
* Committing to show Piotr a bit earlier
2024-07-04 14:21:45 +03:00
Onuralp SEZER e61a1074ac Merge pull request #1324 from roboflow/dependabot/pip/tox-4.16.0
⬆️ Bump tox from 4.15.1 to 4.16.0
2024-07-04 13:44:27 +03:00
dependabot[bot] e6c7f970f0 ⬆️ Bump tox from 4.15.1 to 4.16.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.15.1 to 4.16.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.15.1...4.16.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-07-04 01:08:33 +00:00
Piotr Skalski f06dcf73a5 Merge pull request #1277 from roboflow/feat/patch-rich-label-annotator
Patch RichLabelAnnotator, fix in-place functionality of other annotators
2024-07-03 13:39:10 +02:00
SkalskiP 0bb25fdd91 bump version from 0.21.0 to 0.22.0rc1 2024-07-03 13:15:49 +02:00
Onuralp SEZER 9501a0c72e Merge pull request #1323 from roboflow/dependabot/pip/mkdocs-jupyter-0.24.8
⬆️ Bump mkdocs-jupyter from 0.24.7 to 0.24.8
2024-07-03 13:05:22 +03:00
Onuralp SEZER d9b9143f07 Merge pull request #1322 from roboflow/dependabot/pip/mkdocs-material-9.5.28
⬆️ Bump mkdocs-material from 9.5.27 to 9.5.28
2024-07-03 13:04:46 +03:00
dependabot[bot] b7e6bcc15f ⬆️ Bump mkdocs-jupyter from 0.24.7 to 0.24.8
Bumps [mkdocs-jupyter](https://github.com/danielfrg/mkdocs-jupyter) from 0.24.7 to 0.24.8.
- [Changelog](https://github.com/danielfrg/mkdocs-jupyter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/danielfrg/mkdocs-jupyter/compare/0.24.7...0.24.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-03 00:44:18 +00:00
dependabot[bot] 08fbcbd8ea ⬆️ Bump mkdocs-material from 9.5.27 to 9.5.28
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.27 to 9.5.28.
- [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.27...9.5.28)

---
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-07-03 00:43:11 +00:00
Onuralp SEZER e5114a233c Merge pull request #1321 from roboflow/dependabot/pip/pillow-10.4.0
⬆️ Bump pillow from 10.3.0 to 10.4.0
2024-07-02 04:06:01 +03:00
dependabot[bot] dea7f83e50 ⬆️ Bump pillow from 10.3.0 to 10.4.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.3.0 to 10.4.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.3.0...10.4.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 00:58:12 +00:00
Onuralp SEZER 034d3eaaac Merge pull request #1319 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-07-01 20:58:13 +03:00
pre-commit-ci[bot] 7397d8a240 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.0)
2024-07-01 17:57:15 +00:00
David Redo a023259445 Updated from_mediapipe docstring with FaceLandmaker 2024-07-01 19:30:17 +02:00
Linas Kondrackis 09a06e10a1 Merge pull request #1312 from Bhavay-2001/overlay_image
Updated overlay_image function in CropAnnotator
2024-07-01 14:32:31 +03:00
Linas Kondrackis 8d26a9291b Merge pull request #1317 from roboflow/fix/poetry/dep
fix: 🐞 skip broken docutils package, revert numpy version lock
2024-07-01 11:12:07 +03:00
Onuralp SEZER 28d219c86d fix: 🐞 skip broken docutils and remove numpy version lock
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-29 00:42:11 +03:00
Onuralp SEZER 89ef841955 Merge pull request #1314 from roboflow/dependabot/pip/ruff-0.5.0
⬆️ Bump ruff from 0.4.10 to 0.5.0
2024-06-28 23:21:12 +03:00
David Redo b164ef1111 Added check for FaceMesh legacy object when no faces are detected 2024-06-28 15:30:07 +02:00
dependabot[bot] 2f39a2c3c6 ⬆️ Bump ruff from 0.4.10 to 0.5.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.10 to 0.5.0.
- [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/v0.4.10...0.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-28 00:45:48 +00:00
Onuralp SEZER d8f990231c Merge pull request #1300 from kirilman/develop
detectron2 mask convert to detection
2024-06-27 22:21:51 +03:00
David Redo 74d19dd069 Added FaceMesh support in from_mediapipe KeyPoints fuction 2024-06-27 17:21:30 +02:00
pre-commit-ci[bot] 552c8dbede fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-27 12:21:29 +00:00
Bhavay Malhotra e421af494f Changed the parameters of overlay_image function 2024-06-27 17:43:10 +05:30
Onuralp SEZER 5b3fc12aea Merge pull request #1311 from roboflow/dependabot/pip/twine-5.1.1
⬆️ Bump twine from 5.1.0 to 5.1.1
2024-06-27 09:22:41 +03:00
dependabot[bot] 854fb18b34 ⬆️ Bump twine from 5.1.0 to 5.1.1
Bumps [twine](https://github.com/pypa/twine) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/pypa/twine/releases)
- [Changelog](https://github.com/pypa/twine/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/twine/compare/5.1.0...v5.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 01:03:10 +00:00
Onuralp SEZER 185ae462a4 fix: 🐞 confidence shape size fix for validation
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 16:09:10 +03:00
Onuralp SEZER 3676f07168 fix: 🐞 doc and validation typo fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 15:51:32 +03:00
Onuralp SEZER 099258baa6 feat: ✨ check pred_keypoints is exist if not return empty
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 15:44:32 +03:00
Onuralp SEZER 0bb3839d29 feat: ✨ Keypoints for detectron2 added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 15:38:31 +03:00
Onuralp SEZER f66444bc91 fix: 🐞 add if condition for check existince of 'pred_masks' for detectron2
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-25 16:25:28 +03:00
Onuralp SEZER 6de21048d2 Merge pull request #1307 from roboflow/docs/suppress-mkdocs-relative-link-messages
Suppress mkdocs relative link INFO messages
2024-06-25 10:40:15 +03:00
Onuralp SEZER db9d6c623b Merge pull request #1305 from roboflow/dependabot/pip/mypy-1.10.1
⬆️ Bump mypy from 1.10.0 to 1.10.1
2024-06-25 10:15:08 +03:00
Onuralp SEZER fb7ecd73d9 Merge pull request #1304 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-06-25 10:14:55 +03:00
Onuralp SEZER 2bc19d69af Merge pull request #1306 from roboflow/dependabot/pip/mike-2.1.2
⬆️ Bump mike from 2.1.1 to 2.1.2
2024-06-25 10:14:39 +03:00
LinasKo a377b54a60 Merge branch 'develop' into feat/supervision-cleanup 2024-06-25 09:57:13 +03:00
dependabot[bot] db1998c155 ⬆️ Bump mike from 2.1.1 to 2.1.2
Bumps [mike](https://github.com/jimporter/mike) from 2.1.1 to 2.1.2.
- [Release notes](https://github.com/jimporter/mike/releases)
- [Changelog](https://github.com/jimporter/mike/blob/master/CHANGES.md)
- [Commits](https://github.com/jimporter/mike/compare/v2.1.1...v2.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 01:04:21 +00:00
dependabot[bot] ef6369b93d ⬆️ Bump mypy from 1.10.0 to 1.10.1
Bumps [mypy](https://github.com/python/mypy) from 1.10.0 to 1.10.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.0...v1.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 01:02:06 +00:00
pre-commit-ci[bot] a21a17ba29 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.4.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.9...v0.4.10)
2024-06-24 17:47:27 +00:00
Onuralp SEZER c973647cb8 Merge pull request #975 from roboflow/cookbooks/json-csv-sinks
docs(notebooks): 📝 json and csv sink cookbooks are added
2024-06-24 15:53:08 +03:00
LinasKo 71bdb01547 Sinks cookbooks: add 'person' label, min_consectutive_frames 2024-06-24 15:35:21 +03:00
LinasKo 92a5a931a8 Merge branch 'develop' into cookbooks/json-csv-sinks 2024-06-24 15:08:16 +03:00
LinasKo 0ecd88d4d1 Sink cookbooks: Fix cell output error 2024-06-24 14:36:03 +03:00
LinasKo 4d12853b95 Sink cookbooks fix: _to_detections unpacks into List[Detections]
* 1 sv.Detections per frame
2024-06-24 14:31:59 +03:00
LinasKo 9333460ee9 Tidy up Sink cookbook text 2024-06-24 13:31:35 +03:00
Onuralp SEZER 94b4c22594 ci: 👷 check notebook and automatically create colab link and show better difference in reviewNB
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-21 22:29:18 +03:00
Piotr Skalski d5cebd4fd2 Merge pull request #1296 from LinasKo/feat/florence-2-support
Add Florence 2 support
2024-06-21 11:40:00 -07:00
LinasKo 61579af25a Make ruff happy 2024-06-21 17:49:43 +03:00
pre-commit-ci[bot] bcd3e1be17 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-21 13:59:41 +00:00
LinasKo 9ef697cd10 Florence 2 units tests 2024-06-21 16:59:01 +03:00
LinasKo 19bf417810 Special case for REGION_TO_DESCRIPTION when nothing is detected 2024-06-21 15:38:21 +03:00
pre-commit-ci[bot] 3df33f0f9d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-21 11:24:24 +00:00
kirilman b8b31f6f00 detectron2 mask convert to detection 2024-06-21 16:15:34 +05:00
LinasKo fc11f4d7af Masks are now bool 2024-06-21 12:56:14 +03:00
LinasKo ec517124f8 florence_2: Clean up task selector 2024-06-21 12:34:09 +03:00
Onuralp SEZER f00dcede1a Merge pull request #1299 from roboflow/dependabot/pip/ruff-0.4.10
⬆️ Bump ruff from 0.4.9 to 0.4.10
2024-06-21 09:23:46 +03:00
dependabot[bot] 6fb90f6b75 ⬆️ Bump ruff from 0.4.9 to 0.4.10
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.9 to 0.4.10.
- [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/v0.4.9...v0.4.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-21 01:01:46 +00:00
LinasKo 23e63501eb fix, from_florence_2: extra dimension for xyxy, REGION_TO_CATEGORY 2024-06-20 21:35:49 +03:00
Onuralp SEZER 0df56b1d66 docs(notebook): code clean up and table of content fixes made for mkdocs version
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-20 21:26:56 +03:00
LinasKo ec75a86a83 Fix, from_florence_2: polygon_to_mask expects np.int32 2024-06-20 21:26:54 +03:00
LinasKo 4599548eeb Fix, from_florence_2: polygons must be shaped (-1, 2) 2024-06-20 21:22:15 +03:00
LinasKo bb513f880a Add segmentation and region methods 2024-06-20 21:08:42 +03:00
Onuralp SEZER fbce6e45d4 Merge pull request #1298 from roboflow/fix/readme-broken-links
Readme: fix broken links
2024-06-20 16:23:45 +03:00
LinasKo a00e2d22ba Readme: fix broken links 2024-06-20 16:12:35 +03:00
LinasKo 04c5f18da0 Bugfix: florence 2, handle case with no detections 2024-06-20 15:49:13 +03:00
Onuralp SEZER 8b933f640e Merge pull request #1297 from roboflow/readme/hackfest-banner
Readme: change hackfest banner link to project page
2024-06-20 15:23:12 +03:00
LinasKo f01ca33511 Readme: change hackfest banner link to project page 2024-06-20 15:14:33 +03:00
LinasKo 2f88bb2b8e Add Florence 2 support 2024-06-20 14:50:10 +03:00
Onuralp SEZERandpre-commit-ci[bot] b2b251a424 feat: 🚀 Triangle and Dot annotators outline thickness level and triangle and circle shapes added (#1294)
* feat: 🚀 Triangle_annotator and Dot_annotator outline thickness value added
* feat: 🚀 Triangle_annotator and Dot_annotator outline triangle and circle added
* docs: 📝 outline_thickness py docs added
* refactor: remove else statement for thickness in circle not needed 
* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-20 12:49:36 +03:00
Onuralp SEZER eec3615250 refactor: remove else statement for thickness in circle not needed
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-20 12:45:55 +03:00
pre-commit-ci[bot] 272a9cb35e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-19 18:43:35 +00:00
Dripto Saha 0d0328ef22 Updated the Triangle_annotator and the Dot_annotator in the annotator-core.py file 2024-06-20 00:12:42 +05:30
Dripto Saha d821fca2af Updated the Triangle_annotator and the Dot_annotator in the annotator-core.py 2024-06-19 23:59:27 +05:30
pre-commit-ci[bot] ff9a63a68b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-19 18:15:45 +00:00
Dripto Saha a6222a065c Updated the Triangle_annotator and the Dot_annotator in the annotator-core.py 2024-06-19 23:44:30 +05:30
Onuralp SEZER 1489f82e0f docs: 📝 outline_thickness py docs added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-19 20:43:55 +03:00
pre-commit-ci[bot] 12722df65b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-19 17:38:49 +00:00
Dripto Saha 900952acae Modified annotators-core.py 2024-06-19 23:08:36 +05:30
pre-commit-ci[bot] 95308126c7 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-19 17:28:09 +00:00
Dripto Saha e7de6859e9 Updated annotation-core.py 2024-06-19 22:57:52 +05:30
pre-commit-ci[bot] feeea38e6e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-19 14:11:46 +00:00
Dripto Saha 6ea07a1c2a Updated dot-annotator-core.py 2024-06-19 19:11:52 +05:30
Linas Kondrackis fbb770a486 Merge pull request #1227 from Bhavay-2001/OBB
Adding support for Yolo-OBB (Oriented Bounding Boxes) format
2024-06-19 12:10:23 +03:00
Linas Kondrackis 09f663139c Merge pull request #1290 from roboflow/expose_file_script
file utilities ret-type updated and extended function to work with Pathlib
2024-06-19 12:02:11 +03:00
hardikdava 9efd04925c Merge remote-tracking branch 'origin/expose_file_script' into expose_file_script 2024-06-19 09:46:46 +02:00
hardikdava 14ce0799ce revert global import 2024-06-19 09:46:38 +02:00
LinasKo a81f24e08d Explain OBB format in docstring 2024-06-19 01:06:39 +03:00
LinasKo 257e73fb37 Change uncommon np.int0 type to its alias np.intp 2024-06-19 00:36:27 +03:00
LinasKo dcfb122816 Fix xyxyxyxy shape
* Ultralytics return (-1, 4, 2), and that's waht our annotators expect
2024-06-19 00:33:00 +03:00
LinasKo 79ea168fd4 Merge branch 'develop' into OBB 2024-06-18 23:59:36 +03:00
LinasKo d35830ddeb Include tests 2024-06-18 22:19:36 +03:00
LinasKo c5ff58910d Reduce number of copy operations 2024-06-18 21:32:40 +03:00
Linas Kondrackis 15d9b75225 Merge pull request #1287 from roboflow/dependabot/pip/mkdocs-material-9.5.27
⬆️ Bump mkdocs-material from 9.5.26 to 9.5.27
2024-06-18 16:12:04 +03:00
Linas Kondrackis 49318ffe31 Merge pull request #1289 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-06-18 16:05:56 +03:00
Linas Kondrackis b52a0297f1 Merge pull request #1291 from roboflow/dependabot/pip/urllib3-2.2.2
⬆️ Bump urllib3 from 2.2.1 to 2.2.2
2024-06-18 15:12:13 +03:00
dependabot[bot] eceebb33e3 ⬆️ Bump urllib3 from 2.2.1 to 2.2.2
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.2.1...2.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 23:02:23 +00:00
pre-commit-ci[bot] fa7821b1c4 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-17 18:54:25 +00:00
hardikdava a56a16937f missing rettype added 2024-06-17 20:54:08 +02:00
hardikdava 0c95a6126d converting paths to string 2024-06-17 20:50:40 +02:00
hardikdava c14642accf docstrings updated 2024-06-17 20:47:36 +02:00
hardikdava 8c0e2fb362 exposing some file utilities 2024-06-17 20:44:48 +02:00
pre-commit-ci[bot] e5fb43aea6 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/PyCQA/bandit: 1.7.8 → 1.7.9](https://github.com/PyCQA/bandit/compare/1.7.8...1.7.9)
- [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.8...v0.4.9)
2024-06-17 17:46:44 +00:00
Onuralp SEZER 32b70c5d37 Merge pull request #1286 from roboflow/dependabot/pip/ruff-0.4.9
⬆️ Bump ruff from 0.4.8 to 0.4.9
2024-06-17 18:07:47 +03:00
dependabot[bot] b9e98a5e20 ⬆️ Bump ruff from 0.4.8 to 0.4.9
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.8 to 0.4.9.
- [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/v0.4.8...v0.4.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 15:05:59 +00:00
Onuralp SEZER 246b8a720c Merge pull request #1288 from roboflow/feat/disalllow-numpy-v2
Numpy upper limit: `<2.0.0`
2024-06-17 18:03:07 +03:00
LinasKo a43970fb65 Pin numpy to <2.0.0 2024-06-17 16:46:47 +03:00
Dripto Saha ff5709def2 Updated annotators-core.py 2024-06-17 16:46:41 +05:30
Dripto Saha 2805f41675 Updated annotators-core.py 2024-06-17 16:08:12 +05:30
dependabot[bot] 4eaa897f2c ⬆️ Bump mkdocs-material from 9.5.26 to 9.5.27
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.26 to 9.5.27.
- [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.26...9.5.27)

---
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-06-17 00:44:28 +00:00
Piotr Skalski f96e7766f3 Merge pull request #1232 from David-rn/feat/keypoints-from-mediapipe
Feat/keypoints from mediapipe
2024-06-13 14:09:56 -04:00
pre-commit-ci[bot] 07cb0d8ccf fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-13 17:50:45 +00:00
SkalskiP 3725beb2db docs improvements 2024-06-13 13:50:27 -04:00
Bhavay Malhotra d5f8b94d65 Merge branch 'OBB' of https://github.com/Bhavay-2001/roboflow-supervision into OBB 2024-06-13 20:22:36 +05:30
Bhavay Malhotra 6f7f728044 Changed the position of a line 2024-06-13 20:22:26 +05:30
LinasKo aad3c4fce4 Defensive default font loading 2024-06-12 15:48:53 +03:00
LinasKo 52724ca673 RichLabelAnnotator: Minor docstring styling 2024-06-12 15:38:52 +03:00
LinasKo e3fc238c70 Revert RichLabelAnnotator font size 2024-06-12 15:33:04 +03:00
LinasKo cfe29cd05f Bugfix: MaskAnnotator and HaloAnnotator now work in-place 2024-06-12 15:09:50 +03:00
LinasKo 0f76f731ee bugfix: ColorAnnotator is now in-place 2024-06-12 14:58:48 +03:00
LinasKo c239291541 Bugfix: PIL exposes font size param, but complains if set 2024-06-12 11:08:30 +03:00
LinasKo a446ffd021 Bugfix: decorators for annotators now work in-place 2024-06-12 10:59:25 +03:00
LinasKo c2084c4d0f Add missing base classes for annotators 2024-06-11 17:34:51 +03:00
LinasKo e3cb417129 RichLabelAnnotator fix: convert back to numpy 2024-06-11 16:15:23 +03:00
LinasKo 27de665940 Docs: Remove INFO about absolute links when building 2024-06-11 15:19:52 +03:00
LinasKo 93b21a8d2d minor improvement to code clarity 2024-06-11 14:02:52 +03:00
LinasKo a4bdfc2062 minor tidy-up for mediapipe 2024-06-11 13:51:23 +03:00
LinasKo fd1a07cb6b Adjust long lines 2024-06-11 13:14:38 +03:00
LinasKo edfb814fc4 Merge branch 'develop' into feat/keypoints-from-mediapipe 2024-06-11 13:06:40 +03:00
Onuralp SEZER 3d5437b960 Merge pull request #1273 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-06-10 20:51:46 +03:00
pre-commit-ci[bot] 68c2504d61 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.4.8)
2024-06-10 17:50:26 +00:00
Onuralp SEZER 6984d72f4a Merge pull request #1272 from roboflow/dependabot/pip/notebook-7.2.1
⬆️ Bump notebook from 7.2.0 to 7.2.1
2024-06-10 09:09:23 +03:00
dependabot[bot] 6f5b477ac0 ⬆️ Bump notebook from 7.2.0 to 7.2.1
Bumps [notebook](https://github.com/jupyter/notebook) from 7.2.0 to 7.2.1.
- [Release notes](https://github.com/jupyter/notebook/releases)
- [Changelog](https://github.com/jupyter/notebook/blob/@jupyter-notebook/tree@7.2.1/CHANGELOG.md)
- [Commits](https://github.com/jupyter/notebook/compare/@jupyter-notebook/tree@7.2.0...@jupyter-notebook/tree@7.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 01:16:42 +00:00
Onuralp SEZER b19511968d Merge pull request #1271 from mqasim41/develop
Fix Grammatical Mistake In CONTRIBUTING.md
2024-06-08 19:45:24 +03:00
mqasim41 165c77d830 Fix Grammatical Mistake In CONTRIBUTING.md 2024-06-07 18:03:12 +05:00
Onuralp SEZER 558581943b Merge pull request #1268 from roboflow/dependabot/pip/mkdocs-material-9.5.26
⬆️ Bump mkdocs-material from 9.5.25 to 9.5.26
2024-06-07 11:43:01 +03:00
Onuralp SEZER 8acbc422c9 Merge pull request #1267 from roboflow/dependabot/pip/tornado-6.4.1
⬆️ Bump tornado from 6.4 to 6.4.1
2024-06-07 11:37:20 +03:00
dependabot[bot] 9995771604 ⬆️ Bump mkdocs-material from 9.5.25 to 9.5.26
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.25 to 9.5.26.
- [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.25...9.5.26)

---
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-06-07 01:07:22 +00:00
dependabot[bot] bcc1bc1ff2 ⬆️ Bump tornado from 6.4 to 6.4.1
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4 to 6.4.1.
- [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.0...v6.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-06 23:39:07 +00:00
pre-commit-ci[bot] cce69d893e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-06 16:57:06 +00:00
Bhavay Malhotra 3e2fe835db Merge branch 'OBB' of https://github.com/Bhavay-2001/roboflow-supervision into OBB 2024-06-06 22:24:29 +05:30
Bhavay Malhotra 47f1c44e20 Updated criteria for xyxyxyxy 2024-06-06 22:22:54 +05:30
pre-commit-ci[bot] 420543534d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-06 16:45:31 +00:00
Bhavay Malhotra 1ea9345845 Merge branch 'OBB' of https://github.com/Bhavay-2001/roboflow-supervision into OBB 2024-06-06 22:14:11 +05:30
Bhavay Malhotra 6ca09ccbfc Added default value for is_obb parameter 2024-06-06 22:14:02 +05:30
Onuralp SEZER 7f78f93407 Merge pull request #1263 from roboflow/dependabot/pip/ruff-0.4.8
⬆️ Bump ruff from 0.4.7 to 0.4.8
2024-06-06 11:03:11 +03:00
Onuralp SEZER 05ceca70ef Merge pull request #1262 from roboflow/dependabot/pip/tox-4.15.1
⬆️ Bump tox from 4.15.0 to 4.15.1
2024-06-06 11:02:56 +03:00
Piotr Skalski e8f8c38f39 Merge pull request #1264 from roboflow/main
Merge pull request #1258 from roboflow/develop
2024-06-06 08:13:34 +02:00
Piotr Skalski e50c761b83 Merge pull request #1258 from roboflow/develop
`supervision-0.21.0` release
2024-06-06 08:11:08 +02:00
dependabot[bot] 0739e24b25 ⬆️ Bump ruff from 0.4.7 to 0.4.8
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.7 to 0.4.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/v0.4.7...v0.4.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-06-06 01:33:50 +00:00
dependabot[bot] 560760993a ⬆️ Bump tox from 4.15.0 to 4.15.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.15.0 to 4.15.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.15.0...4.15.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-06-06 01:31:57 +00:00
pre-commit-ci[bot] 859f0258bc fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-05 17:34:09 +00:00
Bhavay Malhotra 2f644c52e3 Merge branch 'OBB' of https://github.com/Bhavay-2001/roboflow-supervision into OBB 2024-06-05 23:03:33 +05:30
Bhavay Malhotra a82c37c7fa Updated yolo.py for OBB support 2024-06-05 23:03:24 +05:30
pre-commit-ci[bot] fc2710c9c9 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-05 17:31:11 +00:00
Bhavay Malhotra 29161a485e Merge branch 'OBB' of https://github.com/Bhavay-2001/roboflow-supervision into OBB 2024-06-05 23:00:45 +05:30
Bhavay Malhotra 523d218dd4 Made few changes to add OBB support 2024-06-05 22:59:04 +05:30
SkalskiP 0200fd307b mark InferenceSlicer with new status 2024-06-05 18:00:54 +02:00
Piotr Skalski 6a199c5d7e Merge pull request #1260 from roboflow/final-changes-before-supervision-0.21.0-release
final changes before `supervision-0.21.0`
2024-06-05 17:55:51 +02:00
Piotr Skalski d19c44c991 Merge pull request #1261 from roboflow/docs/new-status-tags-for-0.21.0
Update "New" tags in docs, fixed typo in one heading
2024-06-05 17:55:32 +02:00
SkalskiP 2d5de66a9b code snippets updates 2024-06-05 17:53:38 +02:00
LinasKo 13b46dda67 Update "New" tags in docs, fix typo in one heading 2024-06-05 17:44:25 +02:00
SkalskiP a3f299ceb0 update docs headers 2024-06-05 17:40:39 +02:00
SkalskiP 9a42372ff6 changelog updated 2024-06-05 17:37:01 +02:00
SkalskiP 47b9ab8f96 Merge remote-tracking branch 'origin/final-changes-before-supervision-0.21.0-release' into final-changes-before-supervision-0.21.0-release 2024-06-05 16:52:27 +02:00
SkalskiP b476acc1f3 bump version from 0.21.0rc5 to 0.21.0 2024-06-05 16:52:18 +02:00
pre-commit-ci[bot] 3cee266f71 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-05 14:33:09 +00:00
Piotr Skalski 111d4c6c91 Merge pull request #1206 from tc360950/line-zone-unit-tests
Add LineZone unit tests
2024-06-05 16:30:20 +02:00
LinasKo a77a2ba8aa LineZone: uncomment unit tests, set expected results 2024-06-05 16:27:53 +02:00
Piotr Skalski fb9024bc75 Merge pull request #1255 from roboflow/fix/detections-merge-revert-and-filter-out-empty
Revert flexible merging, Filter out empty detections
2024-06-05 16:01:38 +02:00
LinasKo ceecb8bbd7 Cleanup: Remove print statements from tests 2024-06-05 15:42:00 +02:00
SkalskiP c28c93bd4f non-max-merging visualization + from_lmm updates 2024-06-05 15:40:28 +02:00
pre-commit-ci[bot] 46bd267079 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-05 12:17:11 +00:00
Bhavay Malhotra d025791805 Merge branch 'OBB' of https://github.com/Bhavay-2001/roboflow-supervision into OBB 2024-06-05 17:46:12 +05:30
Bhavay Malhotra 7e4fc8a485 Made few changes to add OBB support 2024-06-05 17:43:58 +05:30
LinasKo ca482967c4 Add note that merge ignores empty detections 2024-06-05 12:28:09 +02:00
LinasKo 57fa637eea Move is_empty into Detections 2024-06-05 12:18:57 +02:00
Linas Kondrackis 59c5ab5252 Merge pull request #1228 from tc360950/vectorize-line-zone
Vectorize line zone
2024-06-05 10:26:23 +02:00
LinasKo c27a28235e LineZone: Remove comment 2024-06-05 10:24:25 +02:00
Onuralp SEZER cfbb6681cc Merge pull request #1257 from roboflow/dependabot/pip/pytest-8.2.2
⬆️ Bump pytest from 8.2.1 to 8.2.2
2024-06-05 10:44:46 +03:00
dependabot[bot] 24b1db9e25 ⬆️ Bump pytest from 8.2.1 to 8.2.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.1 to 8.2.2.
- [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.2.1...8.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-05 00:43:32 +00:00
pre-commit-ci[bot] 8c92072ecd fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-04 19:07:59 +00:00
tc360950 38d442c176 PR fixes 2024-06-04 21:06:51 +02:00
Onuralp SEZER 2f09e51458 Merge pull request #1254 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-06-03 23:25:56 +03:00
pre-commit-ci[bot] 56eb8e1698 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.5 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.5...v0.4.7)
2024-06-03 17:47:16 +00:00
LinasKo 5bfd51fdec is_empty check that includes data
* We need to decide if this or from_inference data appended to empty Detections is correct
2024-06-03 14:01:03 +02:00
LinasKo a5d4612402 Revert Detections.merge to 0.20.0, but filter out empty detections 2024-06-03 10:43:31 +02:00
Onuralp SEZER 941b7e30aa Merge pull request #1253 from roboflow/dependabot/pip/ruff-0.4.7
⬆️ Bump ruff from 0.4.6 to 0.4.7
2024-06-03 05:16:00 +03:00
Onuralp SEZER f4f2626b44 Merge pull request #1252 from roboflow/dependabot/pip/mkdocs-git-revision-date-localized-plugin-1.2.6
⬆️ Bump mkdocs-git-revision-date-localized-plugin from 1.2.5 to 1.2.6
2024-06-03 05:15:44 +03:00
dependabot[bot] 8f198e80cd ⬆️ Bump ruff from 0.4.6 to 0.4.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.6 to 0.4.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/v0.4.6...v0.4.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-06-03 01:14:18 +00:00
dependabot[bot] 612ac6145a ⬆️ 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.5 to 1.2.6.
- [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.5...v1.2.6)

---
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-06-03 01:05:47 +00:00
pre-commit-ci[bot] 26d70219bf fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-02 15:34:54 +00:00
David Redo daf13442f3 Updated from_mediapipe method to support two results types 2024-06-02 17:34:36 +02:00
LinasKo 2b5d430382 LineZone - does not work with movement to or from zones outside limits 2024-06-01 15:44:20 +02:00
LinasKo b6ff9242fc LineZone tests: add extreme coordinate test 2024-06-01 16:19:09 +03:00
LinasKo d2f169bcfa Line zone tests - replace multi detection tests 2024-06-01 15:59:46 +03:00
Linas Kondrackis 33e10ff722 Add unit tests covering most single-detection cases
* Add Colab for visuals: https://colab.research.google.com/drive/179sq8joJ-7JSYMqYNBQlMIPYEClq1PDi?usp=sharing
* Need to visualise other cases - not sure if all are necessary
2024-05-31 20:13:13 +03:00
Linas Kondrackis 883a6bed3c Merge pull request #1236 from roboflow/feat/slicer-select-nms-or-nmm
Select overlap filtering strategy
2024-05-30 14:06:00 +03:00
Linas Kondrackis f7bd7011fe OverlapFilter: Add and validate docs, rename 2024-05-30 13:45:12 +03:00
Onuralp SEZER 6193128774 Merge pull request #1245 from roboflow/dependabot/pip/requests-2.32.3
⬆️ Bump requests from 2.32.2 to 2.32.3
2024-05-30 11:49:13 +03:00
dependabot[bot] bdc10f83fe ⬆️ Bump requests from 2.32.2 to 2.32.3
Bumps [requests](https://github.com/psf/requests) from 2.32.2 to 2.32.3.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-30 01:05:20 +00:00
tc360950 dc1249969f Improve variable naming 2024-05-29 18:34:35 +02:00
Linas Kondrackis 34353aac54 Add overlap handling example image :) 2024-05-29 18:39:41 +03:00
Linas Kondrackis 6ad32406af fix: change doc path for overlap_handling, add box_non_max_merge 2024-05-29 16:31:29 +03:00
Linas Kondrackis 315c4295ff Move NMS and NMM related methods from utils into overlap_handling.py 2024-05-29 16:17:20 +03:00
Linas Kondrackis 4dc001d9bf Renamed overlap strategy, added into InferenceSlicer docs page 2024-05-29 15:53:04 +03:00
Linas Kondrackis 9d60b2009f Merge pull request #1237 from roboflow/feat/get-class-member-vars
Dynamically select Detections member variables
2024-05-29 11:12:39 +03:00
Linas Kondrackis c98698d442 get_instance_variables: test dataclass fields 2024-05-29 11:08:17 +03:00
Onuralp SEZER 3bda0bfc11 Merge pull request #1241 from roboflow/dependabot/pip/ruff-0.4.6
⬆️ Bump ruff from 0.4.5 to 0.4.6
2024-05-29 04:28:02 +03:00
Onuralp SEZER 0e19c581e4 Merge pull request #1240 from roboflow/dependabot/pip/ipywidgets-8.1.3
⬆️ Bump ipywidgets from 8.1.2 to 8.1.3
2024-05-29 04:27:52 +03:00
dependabot[bot] 8fb843ec8e ⬆️ Bump ruff from 0.4.5 to 0.4.6
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.5 to 0.4.6.
- [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/v0.4.5...v0.4.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-29 01:10:56 +00:00
dependabot[bot] ba2b60ffb4 ⬆️ Bump ipywidgets from 8.1.2 to 8.1.3
Bumps [ipywidgets](https://github.com/jupyter-widgets/ipywidgets) from 8.1.2 to 8.1.3.
- [Release notes](https://github.com/jupyter-widgets/ipywidgets/releases)
- [Commits](https://github.com/jupyter-widgets/ipywidgets/compare/8.1.2...8.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-29 01:04:41 +00:00
Onuralp SEZER a6b985ff4b refactor: 📦 supervision updated to 0.21.0
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-05-28 22:53:07 +03:00
LinasKo d59467b4f4 Add anchor check and tests to polygon zone 2024-05-28 15:12:48 +03:00
LinasKo c0486b7f94 Remove docstrings
The class names were descriptive enough
2024-05-28 14:46:55 +03:00
Linas Kondrackis f24c6f38a0 get_instance_variables only accepts instance, more tests 2024-05-28 14:09:38 +03:00
Linas Kondrackis a6c995c9a9 More tests for get_instance_variables 2024-05-28 11:14:29 +03:00
pre-commit-ci[bot] 3c3a0792f3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-28 07:42:33 +00:00
Linas Kondrackis 1d13397503 Dynamically select Detections fields, not hardcoded 2024-05-28 10:38:54 +03:00
Linas Kondrackis f0e88b1982 Select overlap filtering strategy 2024-05-28 09:52:46 +03:00
Onuralp SEZER 9bc2907c49 Merge pull request #1235 from roboflow/dependabot/pip/mkdocs-material-9.5.25
⬆️ Bump mkdocs-material from 9.5.24 to 9.5.25
2024-05-28 03:43:10 +03:00
dependabot[bot] e7dfc04fd3 ⬆️ Bump mkdocs-material from 9.5.24 to 9.5.25
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.24 to 9.5.25.
- [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.24...9.5.25)

---
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-05-28 00:31:55 +00:00
Piotr Skalski a0d0d45890 Merge pull request #500 from mario-dg/add_nmm_to_detections
Add Non-Maximum Merging (NMM) to Detections
2024-05-27 21:34:49 +02:00
SkalskiP 4a365fb907 add LMM to __init__.py 2024-05-27 21:31:25 +02:00
Onuralp SEZER a60cfe08e2 Merge pull request #1233 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-05-27 20:50:48 +03:00
pre-commit-ci[bot] c1ebb81d47 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.4.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.4.5)
2024-05-27 17:42:51 +00:00
Linas Kondrackis 2ee9e08446 Renamed to group_overlapping_boxes 2024-05-27 16:21:40 +03:00
Linas Kondrackis 530e1d01e1 Address comments 2024-05-27 16:17:27 +03:00
David Redo 4fea0031ba Updated docstring comment due to line too large 2024-05-26 21:45:59 +02:00
David Redo 490a341d5d add method in KeyPoints class 2024-05-26 20:14:57 +02:00
David Redo 2c3fa27929 add GHUM Skeleton enum 2024-05-26 20:14:09 +02:00
tc360950 404be16126 Code reformatting 2024-05-24 18:14:18 +02:00
tc360950 72d677ad89 Merge branch 'develop' of https://github.com/tc360950/supervision into line-zone-unit-tests 2024-05-24 18:11:17 +02:00
tc360950 d6ae86e97b Merge remote-tracking branch 'origin/vectorize-line-zone' into vectorize-line-zone 2024-05-24 18:04:54 +02:00
tc360950 ed5c26527a Code reformatting 2024-05-24 18:04:43 +02:00
pre-commit-ci[bot] d23cc008f8 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-24 16:02:31 +00:00
tc360950 9d54d91791 Pull changes from develop 2024-05-24 18:02:13 +02:00
tc360950 80feb2b12f Add comments 2024-05-24 17:43:02 +02:00
pre-commit-ci[bot] 51c7ebdf60 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-24 14:56:03 +00:00
Bhavay Malhotra 2653f5a090 Adding support for Yolo-OBB format 2024-05-24 20:23:17 +05:30
tc360950 fb30bab0fd Vectorize line zone 2024-05-24 16:50:42 +02:00
tc360950 a09b6fac6f Merge remote-tracking branch 'origin/line-zone-unit-tests' into line-zone-unit-tests
# Conflicts:
#	test/detection/test_line_counter.py
2024-05-24 16:43:03 +02:00
tc360950 4e2eb0afc2 Code reformatting 2024-05-24 16:42:40 +02:00
pre-commit-ci[bot] 1a021d5792 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-24 14:40:32 +00:00
tc360950 909ae2a8ff Merge remote-tracking branch 'origin/line-zone-unit-tests' into line-zone-unit-tests 2024-05-24 16:39:43 +02:00
tc360950 7d0488efc0 Add unit tests for negative coordinates and empty anchors 2024-05-24 16:39:30 +02:00
Piotr Skalski 2723735b01 Merge pull request #1221 from roboflow/from_paligemma_support
initial commit adding support for `from_lmm` and specifically for Pal…
2024-05-24 15:09:40 +02:00
Onuralp SEZER 7757d86abc Merge pull request #1225 from roboflow/dependabot/pip/ruff-0.4.5
⬆️ Bump ruff from 0.4.4 to 0.4.5
2024-05-24 13:55:40 +03:00
Piotr Skalski f40dd38b1a Merge pull request #1214 from roboflow/fix/remove-none-tracker-id-contents-checks
tracker_id contents are never `None`
2024-05-24 09:17:47 +02:00
Piotr Skalski ad7e643c83 Merge pull request #1180 from rolson24/fix_NaN_values_in_ByteTrack
Fix Cost Matrix containing NaN Values in ByteTrack
2024-05-24 09:01:12 +02:00
Piotr Skalski eae4040b17 Merge pull request #1224 from roboflow/docs/how-to-track-rename-title
Docs: shorter title for object tracking how-to
2024-05-24 07:59:41 +02:00
dependabot[bot] 9afca17854 ⬆️ Bump ruff from 0.4.4 to 0.4.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.4 to 0.4.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/v0.4.4...v0.4.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-05-24 00:13:26 +00:00
Linas Kondrackis 19296a73f6 ious: Replace 0-area check with nan conversion 2024-05-23 18:45:23 +03:00
Linas Kondrackis 0721bc289b Remove _set_at_index 2024-05-23 16:21:54 +03:00
pre-commit-ci[bot] db1b4737fe fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-23 13:04:11 +00:00
Linas Kondrackis 6fbca8333e Address review comments, simplify merge
* Reintroduced iou check before response - necessary for algorithm
2024-05-23 16:01:34 +03:00
pre-commit-ci[bot] ad2220bc1d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-23 07:12:19 +00:00
SkalskiP 0115ef8b7d small fix when mask is empty 2024-05-23 09:11:47 +02:00
Onuralp SEZER d4541e1469 Merge pull request #1222 from roboflow/fix/yolo-obb
fix: 🐞 yolo obb condition check set or for newer and older version
2024-05-23 01:37:08 +03:00
Linas Kondrackis c9c2dad316 Docs: shorter title for object tracking how-to 2024-05-22 23:47:45 +03:00
Onuralp SEZER e59e2822d8 Merge pull request #1223 from roboflow/docs/how-to-track-rename-title
minor docs change: rename How To - Track Objects
2024-05-22 23:32:10 +03:00
Linas Kondrackis 99854a5b66 minor docs change: rename How To - Track Objects 2024-05-22 23:14:17 +03:00
Onuralp SEZER 251185ba67 fix: 🐞 yolo obb hasstr added into condition
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-05-22 23:08:04 +03:00
Linas Kondrackis 8b4884ec9e tracker, smoother: SupervisionWarnings 2024-05-22 21:33:34 +03:00
SkalskiP 07c36c6223 make linter happy 2024-05-22 17:47:52 +02:00
pre-commit-ci[bot] bf43d6566b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-22 14:29:23 +00:00
SkalskiP b59d17fdae Merge remote-tracking branch 'origin/from_paligemma_support' into from_paligemma_support
# Conflicts:
#	supervision/detection/lmm.py
#	test/detection/test_lmm.py
2024-05-22 16:29:04 +02:00
SkalskiP b81c5e8475 update to allow multi-word class names 2024-05-22 16:28:24 +02:00
pre-commit-ci[bot] 7eb918282c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-22 12:27:56 +00:00
SkalskiP 36a73f7d49 clean up 2024-05-22 14:26:13 +02:00
SkalskiP a2195aa378 initial commit adding support for from_lmm and specifically for PaliGemma 2024-05-22 14:24:52 +02:00
Onuralp SEZER 2bd4348e5d Merge pull request #1220 from roboflow/dependabot/pip/requests-2.32.2
⬆️ Bump requests from 2.32.1 to 2.32.2
2024-05-22 04:00:05 +03:00
dependabot[bot] 6b7861d4d1 ---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-22 00:29:25 +00:00
pre-commit-ci[bot] 8a2f5d4726 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-21 19:19:16 +00:00
tc360950 dcfa916c75 Add docstrings with test description for LineZone tests 2024-05-21 21:18:51 +02:00
pre-commit-ci[bot] 54a0422b58 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-21 19:08:36 +00:00
tc360950 089916e26f Improve variable naming in LineZone unit tests, remove redundant test for empty tracker_id 2024-05-21 21:08:18 +02:00
Piotr Skalski 1bbe03ca27 Merge pull request #1163 from emSko/1114_RLE_support_for_COCO
1114 rle support for coco
2024-05-21 13:29:16 +02:00
pre-commit-ci[bot] 5840889818 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-21 11:08:22 +00:00
SkalskiP c3855c9828 small refactor + docs improvements 2024-05-21 13:08:02 +02:00
Linas Kondrackis 40b537c0be tracker_id contents are never None 2024-05-21 13:58:20 +03:00
SkalskiP 1d43042c55 small refactor + docs improvements 2024-05-21 12:01:05 +02:00
pre-commit-ci[bot] 996b0a3af3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-21 09:43:30 +00:00
SkalskiP f2716252d5 small refactor + docs improvements 2024-05-21 11:43:09 +02:00
Linas Kondrackis b916dc547c Merge pull request #1212 from roboflow/dependabot/pip/mkdocs-material-9.5.24
⬆️ Bump mkdocs-material from 9.5.23 to 9.5.24
2024-05-21 11:31:54 +03:00
Linas Kondrackis e1be566861 Merge pull request #1211 from roboflow/dependabot/pip/requests-2.32.1
⬆️ Bump requests from 2.31.0 to 2.32.1
2024-05-21 10:21:28 +03:00
dependabot[bot] 32f773548e ---
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-05-21 01:02:35 +00:00
dependabot[bot] 414d3e9afb ---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 01:01:12 +00:00
magda skoczen 6dbf3f61e3 fix for unit tests for test_mask_has_holes 2024-05-20 21:47:04 +02:00
pre-commit-ci[bot] ab55e81087 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-20 19:41:35 +00:00
magda_sk 7b42d8381b Merge branch 'develop' into 1114_RLE_support_for_COCO 2024-05-20 21:41:22 +02:00
magda skoczen dfac30cba7 change docu for mask_has_multiple_segments and mask_has_holes and add to global __init__.py 2024-05-20 20:45:17 +02:00
magda skoczen c26eb1a70f unit tests for test_mask_has_multiple_segments 2024-05-20 19:52:48 +02:00
magda skoczen f0fc76c8c7 Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervision into 1114_RLE_support_for_COCO 2024-05-20 19:23:29 +02:00
magda skoczen 68f07e18f3 tests for mask_has_holes 2024-05-20 19:23:22 +02:00
pre-commit-ci[bot] 7ee84c48e0 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-20 16:56:49 +00:00
magda skoczen 179d00bead merge 2024-05-20 18:56:25 +02:00
magda skoczen 2626263ff3 move has_holes and mask_has_multiple_segments to detections utils 2024-05-20 18:54:10 +02:00
magda skoczen a40bf78bb2 coco mock method refactoring 2024-05-20 18:17:03 +02:00
tc360950 41ca70aab6 Add missing type hints and consistent variable naming 2024-05-20 17:01:46 +02:00
Linas Kondrackis 3fbce7b6bb Merge pull request #1208 from roboflow/dependabot/pip/pytest-8.2.1
⬆️ Bump pytest from 8.2.0 to 8.2.1
2024-05-20 10:23:42 +03:00
dependabot[bot] b761a5c32b ⬆️ Bump pytest from 8.2.0 to 8.2.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.0 to 8.2.1.
- [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.2.0...8.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 00:53:40 +00:00
tc360950 12a455eafa Replace unnecessary generator expressions with explicit lists 2024-05-18 17:06:28 +02:00
pre-commit-ci[bot] 6c9f8302a5 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-17 14:19:52 +00:00
tc360950 da904ad2da Add LineZone unit tests 2024-05-17 16:17:31 +02:00
pre-commit-ci[bot] 8d432c78b1 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-17 09:16:59 +00:00
magda skoczen 0cd2c9d146 documentation change for as_coco 2024-05-17 11:16:37 +02:00
magda skoczen aad5e2a212 merge seg fault fix 2024-05-17 11:11:40 +02:00
Linas Kondrackis ab775d9f76 Attempt to fix SegFault
* https://github.com/opencv/opencv-python/issues/706
2024-05-17 11:45:00 +03:00
Onuralp SEZER 1779014fd9 Merge pull request #1201 from roboflow/dependabot/pip/notebook-7.2.0
⬆️ Bump notebook from 7.1.3 to 7.2.0
2024-05-17 11:41:39 +03:00
Onuralp SEZER 84d8d98619 Merge pull request #1204 from roboflow/docs/docs-incorrect-optimal-scale-entry
docs: calculate_optimal_text_scale, not calculate_optimal_font_scale
2024-05-17 11:37:51 +03:00
magda skoczen 879ae9416a test polygon mask when mask in single component and no holes 2024-05-17 10:34:53 +02:00
Linas Kondrackis 9024396f6c Add npt.NDarray[x] types, remove resolution_wh default val 2024-05-17 10:58:45 +03:00
Linas Kondrackis de5349ad6d docs: calculate_optimal_text_scale, not calculate_optimal_font_scale
* Latter does not exist
2024-05-17 09:17:38 +03:00
Linas Kondrackis d391376912 Merge pull request #1202 from roboflow/dependabot/pip/twine-5.1.0
⬆️ Bump twine from 5.0.0 to 5.1.0
2024-05-17 09:06:37 +03:00
dependabot[bot] eb84d41c61 ⬆️ Bump twine from 5.0.0 to 5.1.0
Bumps [twine](https://github.com/pypa/twine) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/pypa/twine/releases)
- [Changelog](https://github.com/pypa/twine/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/twine/compare/5.0.0...5.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-17 01:00:50 +00:00
dependabot[bot] 0252cf210c ⬆️ Bump notebook from 7.1.3 to 7.2.0
Bumps [notebook](https://github.com/jupyter/notebook) from 7.1.3 to 7.2.0.
- [Release notes](https://github.com/jupyter/notebook/releases)
- [Changelog](https://github.com/jupyter/notebook/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter/notebook/compare/@jupyter-notebook/tree@7.1.3...@jupyter-notebook/tree@7.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-17 00:59:54 +00:00
pre-commit-ci[bot] b3b745508b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-16 07:23:55 +00:00
magda skoczen 511f0ab9b2 check for empty mask 2024-05-16 09:23:22 +02:00
magda skoczen 3ee72e38c3 check for empty mask 2024-05-16 09:20:26 +02:00
Piotr Skalski 5acfb9531e Merge pull request #1200 from roboflow/docs/missing-return-in-slicer-callback-2
Missing return in another slicer callback
2024-05-16 09:18:37 +02:00
Linas Kondrackis 2c769e2f72 Missing return in another slicer callback 2024-05-16 10:11:03 +03:00
pre-commit-ci[bot] ce05a0c544 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-16 06:57:26 +00:00
magda skoczen 1e8ee47db2 craete copies of mask in _has_holes and _mask_has_multiple_segments 2024-05-16 08:56:58 +02:00
pre-commit-ci[bot] eefa05cf01 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-16 06:40:58 +00:00
magda skoczen d8679d9c46 automatic RLE for masks with holes or in multiple pieces 2024-05-16 08:40:28 +02:00
Onuralp SEZER 25ceb46ed4 Merge pull request #1199 from roboflow/dependabot/pip/mkdocs-material-9.5.23
⬆️ Bump mkdocs-material from 9.5.22 to 9.5.23
2024-05-16 04:16:20 +03:00
dependabot[bot] a615dfccb6 ⬆️ Bump mkdocs-material from 9.5.22 to 9.5.23
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.22 to 9.5.23.
- [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.22...9.5.23)

---
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-05-16 00:44:29 +00:00
Onuralp SEZER 1776bf1670 Merge pull request #1116 from jeslinpjames/RichLabelAnnotator
Adds support for unicode labels with new `RichLabelAnnotator` class
2024-05-15 19:07:51 +03:00
pre-commit-ci[bot] d983177c3a fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-15 19:01:16 +03:00
Onuralp SEZER 7b259145bb Merge branch 'develop' into RichLabelAnnotator 2024-05-15 18:53:25 +03:00
Onuralp SEZER 24d1840ea1 docs: 📝 RichLabelAnnotator documentation added into docs and minor var fixes
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-05-15 18:35:36 +03:00
Linas Kondrackis f8f3647a98 Removed suprplus NMM code for 20% speedup 2024-05-15 15:32:26 +03:00
Linas Kondrackis 559ef90d83 Simplify box_non_max_merge 2024-05-15 12:14:24 +03:00
Linas Kondrackis 0e2eec08c8 Revert np.bool comparisons with is
* Ruff complains when `== True` is used
* Different behaviour with `is True`
2024-05-15 11:48:48 +03:00
Linas Kondrackis 53f345e916 Minor renaming 2024-05-15 11:35:59 +03:00
Linas Kondrackis 6c40935266 box_non_max_merge: use our functions to compute iou 2024-05-15 11:32:30 +03:00
Linas Kondrackis 145b5fe56c Rename to batch_box_non_max_merge to box_non_max_merge_batch 2024-05-15 10:46:56 +03:00
Onuralp SEZER aed9221307 Merge pull request #1198 from xaristeidou/develop
Correct sentence spacing in `annotators/utils.py`
2024-05-15 10:44:37 +03:00
pre-commit-ci[bot] f08404eb7d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-15 06:56:08 +00:00
magda skoczen aab861c2db test_coco_annotations_to_detections result matrices defined in place 2024-05-15 08:55:47 +02:00
Christoforos Aristeidou 9c0d647fdf Merge branch 'correct-spacing' into develop 2024-05-15 09:53:16 +03:00
Christoforos Aristeidou 5ef934e780 fixed sentence spacing 2024-05-15 09:51:55 +03:00
Linas Kondrackis 2d740bdcb6 fix: merge_object_detection_pair 2024-05-15 09:38:58 +03:00
Linas Kondrackis d2d50fbe46 renamed bbox -> xyxy 2024-05-15 09:26:18 +03:00
Linas Kondrackis 26bafec8f7 Test box_non_max_merge, rename threshold,to __init__ 2024-05-15 09:23:34 +03:00
Linas Kondrackis 8014e88944 Test box_non_max_merge 2024-05-14 17:42:47 +03:00
Linas Kondrackis c3b77d05c0 Rename, remove functions, unit-test & change merge_object_detection_pair 2024-05-14 17:13:33 +03:00
LinasKo 2eb0c7c242 Merge remote-tracking branch 'upstream/develop' into add_nmm_to_detections 2024-05-14 09:46:11 +03:00
Onuralp SEZER 41a08b87be Merge pull request #1193 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-05-13 21:14:50 +03:00
pre-commit-ci[bot] 2bcd81e5fd chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.3 → v0.4.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.3...v0.4.4)
2024-05-13 17:41:06 +00:00
SkalskiP 19396771f1 bump package version from 0.21.0rc4 to 0.21.0rc5 2024-05-13 15:58:22 +02:00
Onuralp SEZER f5324897a3 Merge pull request #1189 from roboflow/dependabot/pip/mkdocs-material-9.5.22
⬆️ Bump mkdocs-material from 9.5.21 to 9.5.22
2024-05-13 16:56:57 +03:00
Piotr Skalski cd8a2be04b Merge pull request #1178 from roboflow/feat/inference-slicer-segmentation
Feat/inference slicer segmentation
2024-05-13 15:55:02 +02:00
Linas Kondrackis 8901192b92 ruff 2024-05-13 16:53:06 +03:00
Linas Kondrackis a9044e4fed Merge branch 'develop' into feat/inference-slicer-segmentation 2024-05-13 16:52:50 +03:00
Linas Kondrackis 85e4c0ec65 Slicer: highlight & mask docstring 2024-05-13 16:51:59 +03:00
Linas Kondrackis 93398108ee Slier docs: remove transfomer example - has issues. 2024-05-13 16:26:35 +03:00
Piotr Skalski f41adca384 Merge pull request #1173 from roboflow/vertex_label_annotator
`VertexLabelAnnotator`
2024-05-13 15:23:51 +02:00
SkalskiP d64bb3879c Merge remote-tracking branch 'origin/vertex_label_annotator' into vertex_label_annotator 2024-05-13 15:21:35 +02:00
SkalskiP 452e1f1ea4 more VertexLabelAnnotator docs 2024-05-13 15:21:25 +02:00
Linas Kondrackis 45e5f49c5a Infrence slicer docs: fix callbacks in transfomers 2024-05-13 16:20:05 +03:00
LinasKo 4c36b39f94 Untested: Small object segmentationation 2024-05-13 15:41:43 +03:00
pre-commit-ci[bot] e13c7dc22c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-13 12:31:37 +00:00
SkalskiP 713719a582 Merge remote-tracking branch 'origin/vertex_label_annotator' into vertex_label_annotator 2024-05-13 14:31:12 +02:00
SkalskiP 051fe5178b VertexLabelAnnotator plugged into docs 2024-05-13 14:30:55 +02:00
pre-commit-ci[bot] 1ebccebf4c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-13 12:01:26 +00:00
SkalskiP f56d7a1733 changes after code review 2024-05-13 14:01:07 +02:00
LinasKo 82ab6c57cb infrenceSlicer: resolution_wh 2024-05-13 14:14:42 +03:00
pre-commit-ci[bot] cccacab621 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-13 10:25:48 +00:00
SkalskiP 59b273ba87 small refactor 2024-05-13 12:25:25 +02:00
pre-commit-ci[bot] 9a1c11218c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-13 10:06:57 +00:00
SkalskiP d21c98a4d2 docs updated; rle_to_mask and mask_to_rle added to __init__.py 2024-05-13 12:06:38 +02:00
Linas Kondrackis 816aecb51b PR comments: tuples, docs, optional 2024-05-13 11:35:23 +03:00
dependabot[bot] 3c8995758d ⬆️ Bump mkdocs-material from 9.5.21 to 9.5.22
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.21 to 9.5.22.
- [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.21...9.5.22)

---
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-05-13 01:21:27 +00:00
pre-commit-ci[bot] 14b57345a7 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-12 21:31:18 +00:00
magda skoczen d292cbf80e Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervision into 1114_RLE_support_for_COCO 2024-05-12 23:02:45 +02:00
magda skoczen 8c857dd9a6 speed up rle to mask 2024-05-12 23:02:22 +02:00
Onuralp SEZER acfabc247f Merge pull request #1185 from roboflow/dependabot/pip/ruff-0.4.4
⬆️ Bump ruff from 0.4.3 to 0.4.4
2024-05-11 23:54:29 +03:00
Onuralp SEZER 61a63c83d9 Merge pull request #1188 from roboflow/docs/missing-return-in-slicer-callback
Minor docs update: missing return in inference slicer callback
2024-05-11 21:56:13 +03:00
Linas Kondrackis 528c960587 Minor docs update: missing return in inference slicer callback 2024-05-11 13:38:59 +03:00
Linas Kondrackis 6c6171b5d5 Simpify passing of shape 2024-05-10 18:25:02 +03:00
Linas Kondrackis b8efa25e93 Merge branch 'develop' into feat/inference-slicer-segmentation 2024-05-10 17:18:07 +03:00
SkalskiP 17c8e41fb3 bump version from 0.21.0rc3 to 0.21.0rc4 2024-05-10 15:50:03 +02:00
Piotr Skalski 0c3594432c Merge pull request #1177 from roboflow/fix/detections-merge-flexibility
More flexibility in Detections.merge
2024-05-10 15:40:06 +02:00
SkalskiP a8c44cfaff ready for merge 2024-05-10 15:36:44 +02:00
pre-commit-ci[bot] 3d0c3d9182 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-10 13:08:35 +00:00
SkalskiP 2364abf481 small error message update + few more test cases for merge_data 2024-05-10 15:08:18 +02:00
pre-commit-ci[bot] 7bd92b096f fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-10 08:18:38 +00:00
magda skoczen 39477feaf6 Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervision into 1114_RLE_support_for_COCO 2024-05-10 10:17:13 +02:00
magda skoczen e4e66f8a9a faster mask to rle 2024-05-10 10:16:34 +02:00
dependabot[bot] 3b61ee674e ⬆️ Bump ruff from 0.4.3 to 0.4.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.3 to 0.4.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/v0.4.3...v0.4.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-05-10 00:28:34 +00:00
Linas Kondrackis 01f7eb5be6 Retain type info by not excluding empty detections 2024-05-09 18:19:51 +03:00
pre-commit-ci[bot] 4cd1fbcef9 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-09 14:15:31 +00:00
Linas Kondrackis b6a55694f2 Move type resolution logic to loop where it's used 2024-05-09 17:12:53 +03:00
Linas Kondrackis 8c58ebe30d Roll back flex-merge on data, merge when key missing 2024-05-09 17:06:51 +03:00
Piotr Skalski 77034700dd Merge pull request #767 from DrawingProcess/mmdetectionmask
Mmdetection mask
2024-05-09 13:19:00 +02:00
Linas Kondrackis 1ebbe3a8d1 Removed comments, deindented, removed unused var 2024-05-09 09:17:37 +03:00
Onuralp SEZER 617bd30c5f Merge pull request #1175 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-05-08 22:50:20 +03:00
Onuralp SEZER 2e8720f156 Merge pull request #1176 from roboflow/dependabot/pip/jinja2-3.1.4
⬆️ Bump jinja2 from 3.1.3 to 3.1.4
2024-05-08 22:49:59 +03:00
Raif Olson c47b0ce990 filter out detections with zero area. 2024-05-08 11:07:14 -04:00
Linas Kondrackis 45563dfddf InferenceSlicer: Now works with segmentation 2024-05-08 14:07:37 +03:00
pre-commit-ci[bot] 008711aac4 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-08 10:50:32 +00:00
magda skoczen 649e27323d fix the line lengths 2024-05-08 12:49:25 +02:00
pre-commit-ci[bot] 36a301e7fc fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-07 16:01:37 +00:00
magda skoczen aac1d8862a assertion for valid mask in mask_to_rle function 2024-05-07 18:01:14 +02:00
pre-commit-ci[bot] d59ec0f223 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-07 15:36:47 +00:00
magda skoczen 3618ac35ec merge 2024-05-07 17:36:26 +02:00
magda skoczen a9f821c28a Assertion error when number of pixel in RLE does not match the nuber of pixels computed as width*height 2024-05-07 17:34:13 +02:00
Linas Kondrackis 7bb94ce966 Detections.merge merges None and []
* Detections.merge is much friendlier now. If there's a None
  or an empty array, it will merge it happily rather than
  complaining that everything needs to be either None or [].
* Data merge follows suit.
2024-05-07 17:30:12 +03:00
Linas Kondrackis fb49be8197 Tests for the new merge function 2024-05-07 16:09:05 +03:00
Piotr Skalski d7b6af84e6 Update README.md
fix errors in example commands
2024-05-07 12:25:41 +02:00
Piotr Skalski d3b845fea5 Merge pull request #1172 from ManzarIMalik/develop
Update README.md
2024-05-07 12:24:35 +02:00
Piotr Skalski 330eae4411 Merge pull request #1166 from tc360950/fix-inreasing-memory-consumption-in-bytetrack
Fix ever increasing memory consumption in ByteTrack
2024-05-07 12:20:18 +02:00
pre-commit-ci[bot] 6b5dacd8e3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 22:22:49 +00:00
magda skoczen 88a43cc795 unit tests for rle_to_mask and mask_to_rle functions 2024-05-07 00:22:24 +02:00
pre-commit-ci[bot] a941583bb0 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 21:07:51 +00:00
magda skoczen 2727152465 merge with upstream 2024-05-06 23:07:27 +02:00
dependabot[bot] 4d25437459 ⬆️ Bump jinja2 from 3.1.3 to 3.1.4
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 20:43:41 +00:00
magda skoczen 3ade7f060a fix order caused error with mask generation in coco_annotations_to_detections 2024-05-06 22:42:26 +02:00
magda skoczen 18a790e9ea typing chanches and doc strings for rle_to_mask and mask_to_rle functions 2024-05-06 20:21:02 +02:00
pre-commit-ci[bot] 478fbd5751 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.2 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.2...v0.4.3)
2024-05-06 17:48:31 +00:00
pre-commit-ci[bot] 7823e0de89 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 16:16:39 +00:00
SkalskiP ab0882dd55 initial version of VertexLabelAnnotator 2024-05-06 18:15:05 +02:00
SkalskiP ec6864ff04 roll back new draw_rounded_rectangle implementation 2024-05-06 18:00:13 +02:00
SkalskiP 81786ff9d3 Merge remote-tracking branch 'origin/vertex_label_annotator' into vertex_label_annotator 2024-05-06 17:40:43 +02:00
SkalskiP 95c243c058 better draw_rounded_rectangle implementation 2024-05-06 17:40:33 +02:00
pre-commit-ci[bot] 204669b08c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 14:25:15 +00:00
Linas Kondrackis 97c407101a NMM: Move detections merge into Detections class.
* No other changes!
2024-05-06 17:24:41 +03:00
pre-commit-ci[bot] bee3252110 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 14:22:34 +00:00
Linas Kondrackis d7e52bee26 NMM: Add None-checks, fix area normalization, style 2024-05-06 17:22:04 +03:00
Linas Kondrackis b1598733c2 Merge remote-tracking branch 'upstream/develop' into add_nmm_to_detections 2024-05-06 17:01:04 +03:00
pre-commit-ci[bot] 5c539c3bda fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 13:49:24 +00:00
SkalskiP 5d38120743 added from_xyxy and as_xyxy_int_tuple methods to Rect. added draw_rounded_rectangle function. 2024-05-06 15:39:59 +02:00
Manzar Iqbal Malik aaf5df2f6a Update README.md
Aligned draw zones file name for both examples, fixed file name for ultralytics example
2024-05-06 12:15:38 +01:00
Onuralp SEZER 447ef41fc4 Merge pull request #1171 from roboflow/dependabot/pip/ruff-0.4.3
⬆️ Bump ruff from 0.4.2 to 0.4.3
2024-05-06 12:41:10 +03:00
dependabot[bot] edd944fff8 ⬆️ Bump ruff from 0.4.2 to 0.4.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.2 to 0.4.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/v0.4.2...v0.4.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-05-06 01:18:43 +00:00
Onuralp SEZER a75efbbf73 Merge pull request #1170 from roboflow/dependabot/pip/mkdocstrings-0.25.1
⬆️ Bump mkdocstrings from 0.25.0 to 0.25.1
2024-05-06 04:16:35 +03:00
Onuralp SEZER e9c8a80823 Merge pull request #1167 from roboflow/dependabot/pip/mike-2.1.1
⬆️ Bump mike from 2.1.0 to 2.1.1
2024-05-06 04:16:01 +03:00
Onuralp SEZER 0b92417948 Merge pull request #1168 from roboflow/dependabot/pip/mkdocs-material-9.5.21
⬆️ Bump mkdocs-material from 9.5.20 to 9.5.21
2024-05-06 04:15:20 +03:00
dependabot[bot] aebbd01830 ⬆️ Bump mkdocstrings from 0.25.0 to 0.25.1
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.25.0 to 0.25.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.25.0...0.25.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-05-06 01:15:08 +00:00
Onuralp SEZER c3ebccabe8 Merge pull request #1169 from roboflow/dependabot/pip/jupytext-1.16.2
⬆️ Bump jupytext from 1.16.1 to 1.16.2
2024-05-06 04:14:43 +03:00
dependabot[bot] ff52352b00 ⬆️ Bump jupytext from 1.16.1 to 1.16.2
Bumps [jupytext](https://github.com/mwouts/jupytext) from 1.16.1 to 1.16.2.
- [Release notes](https://github.com/mwouts/jupytext/releases)
- [Changelog](https://github.com/mwouts/jupytext/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mwouts/jupytext/compare/v1.16.1...v1.16.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 01:12:45 +00:00
dependabot[bot] f7d21acd3a ⬆️ Bump mkdocs-material from 9.5.20 to 9.5.21
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.20 to 9.5.21.
- [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.20...9.5.21)

---
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-05-06 01:09:12 +00:00
dependabot[bot] 92d56eda15 ⬆️ Bump mike from 2.1.0 to 2.1.1
Bumps [mike](https://github.com/jimporter/mike) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/jimporter/mike/releases)
- [Changelog](https://github.com/jimporter/mike/blob/master/CHANGES.md)
- [Commits](https://github.com/jimporter/mike/compare/v2.1.0...v2.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 01:06:46 +00:00
tc360950 d37a5f90a4 Remove old tracklets from ByteTrack.removed_tracks collection 2024-05-05 17:16:05 +02:00
pre-commit-ci[bot] 869204d42d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-05 11:59:23 +00:00
magda skoczen dc1ce02860 move rle encode decode functions to dataset/utils.py 2024-05-04 10:34:08 +02:00
Onuralp SEZER 34a5b34c88 Merge pull request #1162 from roboflow/dependabot/pip/examples/speed_estimation/tqdm-4.66.3
Bump tqdm from 4.66.1 to 4.66.3 in /examples/speed_estimation
2024-05-04 01:26:51 +03:00
dependabot[bot] f72d06b05e Bump tqdm from 4.66.1 to 4.66.3 in /examples/speed_estimation
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.1 to 4.66.3.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.3)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-03 21:51:32 +00:00
magda skoczen 4a068ff8d7 binary mask to RL encoding 2024-05-03 09:16:13 +02:00
Onuralp SEZER 044a8fac06 Merge pull request #1160 from roboflow/dependabot/pip/mike-2.1.0
⬆️ Bump mike from 2.0.0 to 2.1.0
2024-05-03 04:04:46 +03:00
Onuralp SEZER a88089ced7 Merge pull request #1159 from roboflow/dependabot/pip/tqdm-4.66.4
⬆️ Bump tqdm from 4.66.2 to 4.66.4
2024-05-03 04:03:58 +03:00
dependabot[bot] 29b0c514cf ⬆️ Bump mike from 2.0.0 to 2.1.0
Bumps [mike](https://github.com/jimporter/mike) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/jimporter/mike/releases)
- [Changelog](https://github.com/jimporter/mike/blob/master/CHANGES.md)
- [Commits](https://github.com/jimporter/mike/compare/v2.0.0...v2.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-03 00:36:24 +00:00
dependabot[bot] 7d5a0bce45 ⬆️ Bump tqdm from 4.66.2 to 4.66.4
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.2 to 4.66.4.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.2...v4.66.4)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-03 00:33:20 +00:00
magda skoczen 6913ecf54d 2 annotations with segmentation, one polygon one RLE 2024-05-03 01:19:01 +02:00
magda skoczen 7f114cba4c RLE decoding 2024-05-03 00:24:46 +02:00
magda skoczen cc5e72dd5b test for RLE format 2024-05-02 22:08:08 +02:00
Piotr Skalski 177ab6ad4d Merge pull request #1138 from roboflow/feat/keypoints-from-yolo-nas
Implemented `from_yolo_nas` for keypoints
2024-05-02 16:11:09 +02:00
pre-commit-ci[bot] 7e79fa3d07 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-02 13:34:14 +00:00
Linas Kondrackis 347b75935b Ruff 2024-05-02 16:31:48 +03:00
Linas Kondrackis 079008cd17 from_yolo_nas - None class_id & names if not found 2024-05-02 16:30:01 +03:00
magda skoczen e30d4a9abb add test for generating annotation with mask 2024-05-02 12:49:31 +02:00
pre-commit-ci[bot] aab3ed2ba9 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-01 12:23:54 +00:00
Linas Kondrackis ba30244add Remove hardcoding of class_names, class_id.
* Still hardcoded ID to -1 and name to "" if not provided - this lets us
  stick to (n,) shape.
* Found no way to add class_names to pose tracker, so used the response
  of YOLO NAS detection instead, to check what the response looks like.
2024-05-01 15:20:50 +03:00
Onuralp SEZER 55e8f16029 Merge pull request #1158 from roboflow/dependabot/pip/mkdocs-git-revision-date-localized-plugin-1.2.5
⬆️ Bump mkdocs-git-revision-date-localized-plugin from 1.2.4 to 1.2.5
2024-05-01 03:46:57 +03:00
dependabot[bot] 33e69b2da3 ⬆️ 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.4 to 1.2.5.
- [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.4...v1.2.5)

---
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-05-01 00:35:55 +00:00
Piotr Skalski 9fe3170ead Merge pull request #1157 from roboflow/fix/pin_inference_version_in_exampels_to_9.17.0
pin inference version to 0.9.17
2024-04-30 16:09:07 +02:00
SkalskiP ffda94b1e5 pin inference version to 0.9.17 2024-04-30 16:06:02 +02:00
SkalskiP 7fd5d80a3b bump version from 0.21.0rc2 to 0.21.0rc3 2024-04-30 12:30:44 +02:00
Onuralp SEZER b67d9b475f Merge pull request #1155 from roboflow/dependabot/pip/nbconvert-7.16.4
⬆️ Bump nbconvert from 7.16.3 to 7.16.4
2024-04-30 13:24:17 +03:00
Onuralp SEZER d859842fa3 Merge pull request #1156 from roboflow/dependabot/pip/mkdocs-material-9.5.20
⬆️ Bump mkdocs-material from 9.5.19 to 9.5.20
2024-04-30 13:24:03 +03:00
Onuralp SEZER 131c6ffefb Merge pull request #1153 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-04-30 13:23:49 +03:00
dependabot[bot] 71fd3178cc ⬆️ Bump mkdocs-material from 9.5.19 to 9.5.20
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.19 to 9.5.20.
- [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.19...9.5.20)

---
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-04-30 00:35:39 +00:00
dependabot[bot] 71919f1d6f ⬆️ Bump nbconvert from 7.16.3 to 7.16.4
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.16.3 to 7.16.4.
- [Release notes](https://github.com/jupyter/nbconvert/releases)
- [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter/nbconvert/compare/v7.16.3...v7.16.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-30 00:33:04 +00:00
pre-commit-ci[bot] 67160c96b0 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.1...v0.4.2)
2024-04-29 17:52:51 +00:00
pre-commit-ci[bot] 4bd593fe3c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-25 12:02:23 +00:00
Linas Kondrackis 399342513a Keypoints: rm NAS skeleton, improve docs examples 2024-04-25 15:00:01 +03:00
Linas Kondrackis c8c2ff5c45 Minor styling 2024-04-25 12:53:09 +03:00
Linas Kondrackis 18b3c17ed4 Styling 2024-04-25 12:42:33 +03:00
Linas Kondrackis cab06f2c2b Implementation of from_yolo_nas for keypoints
* Missing batch image support
2024-04-25 12:29:08 +03:00
Jeslin P James df4a6f857c added docs for RichLabelAnnotator 2024-04-13 18:58:18 +05:30
pre-commit-ci[bot] cb220479eb fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-13 13:16:16 +00:00
Jeslin P James 8bb8ce0316 changed error message and formatted code 2024-04-13 18:03:08 +05:30
Jeslin P James 8255bcb1ca Moved resolve_text_background_xyxy() function into utils.py 2024-04-13 17:43:52 +05:30
Jeslin P James 8329d155e4 Use default font when font_path isnt specified 2024-04-13 17:27:27 +05:30
Jeslin P James a41eea7c45 Merge branch 'develop' of github.com:jeslinpjames/supervision into RichLabelAnnotator 2024-04-13 13:36:26 +05:30
mario-dg 166a8da9a0 Implement Feedback 2024-04-11 12:22:44 +02:00
mario-dg 5f0dcc2cb2 Merge branch 'develop' into add_nmm_to_detections to resolve conflicts 2024-04-09 18:50:55 +02:00
Jeslin P James 9e906c78f5 annotate function added in RichLabelAnnotator 2024-04-09 22:14:36 +05:30
Jeslin P James 2417b36006 resolve_text_background_xyxy() function added to RichLabelAnnotator 2024-04-09 20:39:53 +05:30
Jeslin P James 1a7309e7c9 RichLabelAnnotator class added 2024-04-09 16:51:41 +05:30
Onuralp SEZER d309d74f37 docs(notebooks): 📝 json and csv sink cookbooks are added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-05 05:27:58 +03:00
David Freilich b85035c45e Incorporate feedback:
* Simplify code to remove unnecessary comments
* Make code style and sample code fit with other patterns in file
* Raise exception if invalid icon
2024-02-22 20:01:29 +02:00
David Freilich 687f8d9621 Remove acceptance tests
* Update docs
2024-02-22 19:52:51 +02:00
David Freilich 668f2f063f Simplify icon annotation code
* Add test for icon annotator, with sample test files to verify
2024-02-20 21:51:08 +02:00
David Freilich f93c712dca Add line 2024-02-19 20:34:26 +02:00
David Freilich 88f995803f Add icon annotator 2024-02-19 20:30:34 +02:00
pre-commit-ci[bot] 27b61ab4a3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-01-22 23:31:31 +00:00
Seongjun Choi bddb72fb28 Modify: from_mmdetection add mask. See #703 2024-01-23 15:26:02 +09:00
Seongjun Choi 823dc56057 Modify: from_mmdetection add mask. See #703 2024-01-23 15:14:56 +09:00
pre-commit-ci[bot] c5ac30c303 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-31 15:58:04 +00:00
SkalskiP 0d72846b40 Refactor in progress 2023-10-31 16:57:47 +01:00
pre-commit-ci[bot] 5d8da9633c fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-23 14:42:16 +00:00
James Gallagher 325bd46740 add dataset processing docs 2023-10-23 15:22:49 +01:00
mario-dg 6f4704625b Refactored merging of Detection attributes to better work with np.ndarrays 2023-10-19 00:05:42 +02:00
mario-dg 9f222736e1 Added standard full image inference after sliced inference to increase large object detection accuracy 2023-10-19 00:05:05 +02:00
mario-dg 57b12e6e00 Added __setitem__ to Detections and refactored the object prediction merging 2023-10-19 00:03:36 +02:00
mario-dg c78ae33e43 feat: 🚀 Added Non-Maximum Merging to Detections 2023-10-13 18:24:22 +02:00
117 changed files with 16810 additions and 4743 deletions
+64
View File
@@ -0,0 +1,64 @@
name: Notebook Check Pull Request
on:
pull_request_target:
types: [opened, reopened]
permissions:
contents: read
jobs:
comment-welcome:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Fetch pull request branch
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
- name: Fetch base develop branch
run: git fetch -u "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" develop:develop
- name: Create message
env:
HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_NUM: ${{ github.event.pull_request.number }}
run: |
# Preview links and tool usage only needed for notebook changes.
readarray -t changed_notebooks < <(git diff --name-only develop | grep '\.ipynb$' || true)
if [[ ${#changed_notebooks[@]} == 0 ]]; then
echo "No notebooks modified in this pull request."
else
msg="<h4>Preview</h4>\n"
msg+="Preview and run these notebook edits with Google Colab:\n<ul>\n"
# Link to PR branch in user's fork that is always current.
for fp in "${changed_notebooks[@]}"; do
gh_path="${HEAD_REPOSITORY}/blob/${HEAD_REF}/${fp}"
colab_url="https://colab.research.google.com/github/${gh_path}"
msg+="<li><a href='${colab_url}'>${fp}</a></li>\n"
done
msg+="</ul>\n"
reviewnb_url="https://app.reviewnb.com/${GITHUB_REPOSITORY}/pull/${PR_NUM}/files/"
msg+="Rendered <a href='${reviewnb_url}'>notebook diffs</a> available on ReviewNB.com.\n"
msg+="If commits are added to the pull request, synchronize your local branch: <code>git pull origin $HEAD_REF</code>\n"
fi
echo "MESSAGE=$msg" >> $GITHUB_ENV
- name: Post comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_URL: ${{ github.event.pull_request.issue_url }}
run: |
# Env var defined in previous step. Escape string for JSON.
body="$(echo -n -e $MESSAGE | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
# Add comment to pull request.
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $GITHUB_TOKEN" \
"${ISSUE_URL}/comments" \
--data "{\"body\": $body}"
+5 -18
View File
@@ -14,21 +14,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: 📦 Install mkdocs-material
run: pip install "mkdocs-material[all]"
- name: 📦 Install mkdocstrings[python]
run: pip install "mkdocstrings[python]"
- name: 📦 Install mkdocs-material[imaging]
run: pip install "mkdocs-material[imaging]"
- name: 📦 Install mike
run: pip install "mike"
- name: 📦 Install mkdocs-git-revision-date-localized-plugin
run: pip install "mkdocs-git-revision-date-localized-plugin"
- name: 📦 Install JupyterLab
run: pip install jupyterlab
- name: 📦 Install mkdocs-jupyter
run: pip install mkdocs-jupyter
- name: 📦 Install mkdocs-git-committers-plugin-2
run: pip install mkdocs-git-committers-plugin-2
- name: 🧪 Test documentation build
run: mkdocs build --verbose
- name: 🏗️ Install dependencies and Test Docs Build
run: |
python -m pip install --upgrade pip
pip install "mkdocs-material" "mkdocstrings[python]" "mkdocs-material[imaging]" mike "mkdocs-git-revision-date-localized-plugin" jupyterlab mkdocs-jupyter mkdocs-git-committers-plugin-2
mkdocs build --verbose
+2 -15
View File
@@ -9,7 +9,6 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
exclude: test/.*\.py
- id: check-yaml
@@ -19,33 +18,21 @@ repos:
- id: check-case-conflict
- id: check-added-large-files
- id: detect-private-key
- id: forbid-new-submodules
- id: pretty-format-json
exclude: demo.ipynb
args: ['--autofix', '--no-sort-keys', '--indent=4']
- id: end-of-file-fixer
- id: mixed-line-ending
- repo: https://github.com/PyCQA/bandit
rev: '1.7.8'
rev: '1.7.9'
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
name: isort (python)
- id: isort
name: isort (pyi)
types: [pyi]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.1
rev: v0.6.2
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+122 -35
View File
@@ -4,6 +4,25 @@ Thank you for your interest in contributing to Supervision!
We are actively improving this library to reduce the amount of work you need to do to solve common computer vision problems.
## 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.
## Table of Contents
- [Contribution Guidelines](#contribution-guidelines)
- [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)
## Contribution Guidelines
We welcome contributions to:
@@ -32,49 +51,49 @@ First, fork this repository to your own GitHub account. Click "fork" in the top
Then, run `git clone` to download the project code to your computer.
You should also set up `roboflow/supervision` as an "upstream" remote (that is, tell git that the reference Supervision repository was the source of your fork of it):
```bash
git remote add upstream https://github.com/roboflow/supervision.git
git fetch upstream
```
Move to a new branch using the `git checkout` command:
```bash
git checkout -b <your_branch_name>
git checkout -b <scope>/<your_branch_name> upstream/develop
```
The name you choose for your branch should describe the change you want to make (i.e. `line-counter-docs`).
The name you choose for your branch should describe the change you want to make and start with an appropriate prefix:
- `feat/`: for new features (e.g., `feat/line-counter`)
- `fix/`: for bug fixes (e.g., `fix/memory-leak`)
- `docs/`: for documentation changes (e.g., `docs/update-readme`)
- `chore/`: for routine tasks, maintenance, or tooling changes (e.g., `chore/update-dependencies`)
- `test/`: for adding or modifying tests (e.g., `test/add-unit-tests`)
- `refactor/`: for code refactoring (e.g., `refactor/simplify-algorithm`)
Make any changes you want to the project code, then run the following commands to commit your changes:
```bash
git add .
git commit -m "Your commit message"
git push -u origin main
git add -A
git commit -m "feat: add line counter functionality"
git push -u origin <your_branch_name>
```
## 🎨 Code quality
Use conventional commit messages to clearly describe your changes. The format is:
### Pre-commit tool
<type>[optional scope]: <description>
This project uses the [pre-commit](https://pre-commit.com/) tool to maintain code quality and consistency. Before submitting a pull request or making any commits, it is important to run the pre-commit tool to ensure that your changes meet the project's guidelines.
Furthermore, we have integrated a pre-commit GitHub Action into our workflow. This means that with every pull request opened, the pre-commit checks will be automatically enforced, streamlining the code review process and ensuring that all contributions adhere to our quality standards.
To run the pre-commit tool, follow these steps:
1. Install pre-commit by running the following command: `poetry install`. It will not only install pre-commit but also install all the deps and dev-deps of project
2. Once pre-commit is installed, navigate to the project's root directory.
3. Run the command `pre-commit run --all-files`. This will execute the pre-commit hooks configured for this project against the modified files. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the pre-commit command until all issues are resolved.
4. You can also install pre-commit as a git hook by execute `pre-commit install`. Every time you made `git commit` pre-commit run automatically for you.
### Docstrings
All new functions and classes in `supervision` should include docstrings. This is a prerequisite for any new functions and classes to be added to the library.
`supervision` adheres to the [Google Python docstring style](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods). Please refer to the style guide while writing docstrings for your contribution.
### Type checking
So far, **there is no type checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4).
Common types include:
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- chore: Changes to the build process or auxiliary tools and libraries
Then, go back to your fork of the `supervision` repository, click "Pull Requests", and click "New Pull Request".
@@ -104,7 +123,75 @@ All pull requests will be reviewed by the maintainers of the project. We will pr
PRs must pass all tests and linting requirements before they can be merged.
## 📝 documentation
## Installation for Contributors
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.
2. Create and activate a virtual environment:
```bash
python3 -m venv .venv
source .venv/bin/activate
```
3. Install Poetry:
Using pip:
```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
```
5. Run pytest to verify the setup:
```bash
poetry run pytest
```
## 🎨 Code Style and Quality
### Pre-commit tool
This project uses the [pre-commit](https://pre-commit.com/) tool to maintain code quality and consistency. Before submitting a pull request or making any commits, it is important to run the pre-commit tool to ensure that your changes meet the project's guidelines.
Furthermore, we have integrated a pre-commit GitHub Action into our workflow. This means that with every pull request opened, the pre-commit checks will be automatically enforced, streamlining the code review process and ensuring that all contributions adhere to our quality standards.
To run the pre-commit tool, follow these steps:
1. Install pre-commit by running the following command: `poetry install --with dev`. It will not only install pre-commit but also install all the deps and dev-deps of project
2. Once pre-commit is installed, navigate to the project's root directory.
3. Run the command `pre-commit run --all-files`. This will execute the pre-commit hooks configured for this project against the modified files. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the pre-commit command until all issues are resolved.
4. You can also install pre-commit as a git hook by executing `pre-commit install`. Every time you do a `git commit` pre-commit run automatically for you.
### Docstrings
All new functions and classes in `supervision` should include docstrings. This is a prerequisite for any new functions and classes to be added to the library.
`supervision` adheres to the [Google Python docstring style](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods). Please refer to the style guide while writing docstrings for your contribution.
### Type checking
So far, **there is no type checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4).
## 📝 Documentation
The `supervision` documentation is stored in a folder called `docs`. The project documentation is built using `mkdocs`.
@@ -112,13 +199,13 @@ To run the documentation, install the project requirements with `poetry install
You can learn more about mkdocs on the [mkdocs website](https://www.mkdocs.org/).
## 🧑‍🍳 cookbooks
## 🧑‍🍳 Cookbooks
We are always looking for new examples and cookbooks to add to the `supervision`
documentation. If you have a use case that you think would be helpful to others, please
submit a PR with your example. Here are some guidelines for submitting a new example:
- Create a new notebook in the [`docs/nodebooks`](https://github.com/roboflow/supervision/tree/develop/docs/notebooks) folder.
- Create a new notebook in the [`docs/notebooks`](https://github.com/roboflow/supervision/tree/develop/docs/notebooks) folder.
- Add a link to the new notebook in [`docs/theme/cookbooks.html`](https://github.com/roboflow/supervision/blob/develop/docs/theme/cookbooks.html). Make sure to add the path to the new notebook, as well as a title, labels, author and supervision version.
- Use the [Count Objects Crossing the Line](https://supervision.roboflow.com/develop/notebooks/count-objects-crossing-the-line/) example as a template for your new example.
- Freeze the version of `supervision` you are using.
@@ -126,10 +213,10 @@ submit a PR with your example. Here are some guidelines for submitting a new exa
- Notebook should be self-contained. If you rely on external data ( videos, images, etc.) or libraries, include download and installation commands in the notebook.
- Annotate the code with appropriate comments, including links to the documentation describing each of the tools you have used.
## 🧪 tests
## 🧪 Tests
[`pytests`](https://docs.pytest.org/en/7.1.x/) is used to run our tests.
## 📄 license
## 📄 License
By contributing, you agree that your contributions will be licensed under an [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md).
+18 -14
View File
@@ -16,6 +16,7 @@
[![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)
[![snyk](https://snyk.io/advisor/python/supervision/badge.svg)](https://snyk.io/advisor/python/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)
[![colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb)
@@ -28,7 +29,7 @@
**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/10)
[![supervision-hackfest](https://github.com/roboflow/supervision/assets/26109316/c05cc954-b9a6-4ed5-9a52-d0b4b619ff65)](https://github.com/orgs/roboflow/projects)
## 💻 install
@@ -86,7 +87,7 @@ len(detections)
### annotators
Supervision offers a wide range of highly customizable [annotators](https://supervision.roboflow.com/latest/annotators/), allowing you to compose the perfect visualization for your use case.
Supervision offers a wide range of highly customizable [annotators](https://supervision.roboflow.com/latest/detection/annotators/), allowing you to compose the perfect visualization for your use case.
```python
import cv2
@@ -95,8 +96,8 @@ import supervision as sv
image = cv2.imread(...)
detections = sv.Detections(...)
bounding_box_annotator = sv.BoundingBoxAnnotator()
annotated_frame = bounding_box_annotator.annotate(
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections
)
@@ -106,22 +107,25 @@ https://github.com/roboflow/supervision/assets/26109316/691e219c-0565-4403-9218-
### datasets
Supervision provides a set of [utils](https://supervision.roboflow.com/latest/datasets/) that allow you to load, split, merge, and save datasets in one of the supported formats.
Supervision provides a set of [utils](https://supervision.roboflow.com/latest/datasets/core/) that allow you to load, split, merge, and save datasets in one of the supported formats.
```python
import supervision as sv
from roboflow import Roboflow
dataset = sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...
project = Roboflow().workspace(<WORKSPACE_ID>).project(<PROJECT_ID>)
dataset = project.version(<PROJECT_VERSION>).download("coco")
ds = sv.DetectionDataset.from_coco(
images_directory_path=f"{dataset.location}/train",
annotations_path=f"{dataset.location}/train/_annotations.coco.json",
)
dataset.classes
['dog', 'person']
path, image, annotation = ds[0]
# loads image on demand
len(dataset)
# 1000
for path, image, annotation in ds:
# loads image on demand
```
<details close>
@@ -216,7 +220,7 @@ len(dataset)
## 🎬 tutorials
Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](https://github.com/roboflow/supervision/tree/develop/examples), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!
Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](https://github.com/roboflow/supervision/tree/develop/examples), [cheatsheet](https://roboflow.github.io/cheatsheet-supervision/), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!
<br/>
Vendored
+1478 -1342
View File
File diff suppressed because one or more lines are too long
+364 -7
View File
@@ -1,3 +1,365 @@
### 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.
```python
import supervision as sv
from inference import get_model
image = <SOURCE_IMAGE_PATH>
icon_dog = <DOG_PNG_PATH>
icon_cat = <CAT_PNG_PATH>
model = get_model(model_id="yolov8n-640")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
icon_paths = []
for class_name in detections.data["class_name"]:
if class_name == "dog":
icon_paths.append(icon_dog)
elif class_name == "cat":
icon_paths.append(icon_cat)
else:
icon_paths.append("")
icon_annotator = sv.IconAnnotator()
annotated_frame = icon_annotator.annotate(
scene=image.copy(),
detections=detections,
icon_path=icon_paths
)
```
- 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.
```python
import supervision as sv
from inference import get_model
image = <SOURCE_IMAGE_PATH>
model = get_model(model_id="yolov8n-640")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
background_overlay_annotator = sv.BackgroundOverlayAnnotator()
annotated_frame = background_overlay_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
- 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`.
```python
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
transformers_results=results,
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).
```python
import supervision as sv
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)
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 [#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.
- 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.
- 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`.
```python
import supervision as sv
for frame in sv.get_video_frames_generator(
source_path=<SOURCE_VIDEO_PATH>,
start=60,
iterative_seek=True
):
...
```
- Fix [#1424](https://github.com/roboflow/supervision/pull/1424): `plot_image` function now clearly indicates that the size is in inches.
!!! failure "Removed"
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.
!!! 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.
!!! failure "Deprecated"
`overlap_filter_strategy` in `InferenceSlicer.__init__` is deprecated and will be removed in `supervision-0.27.0`. Use `overlap_strategy` instead.
!!! failure "Deprecated"
`overlap_ratio_wh` in `InferenceSlicer.__init__` is deprecated and will be removed in `supervision-0.27.0`. Use `overlap_wh` instead.
### 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).
!!! failure "Deprecated"
Constructing `DetectionDataset` with parameter `images` as `Dict[str, np.ndarray]` is deprecated and will be removed in `supervision-0.26.0`. Please pass a list of paths `List[str]` instead.
!!! failure "Deprecated"
The `DetectionDataset.images` property is deprecated and 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.
```python
import roboflow
from roboflow import Roboflow
import supervision as sv
roboflow.login()
rf = Roboflow()
project = rf.workspace(<WORKSPACE_ID>).project(<PROJECT_ID>)
dataset = project.version(<PROJECT_VERSION>).download("coco")
ds_train = sv.DetectionDataset.from_coco(
images_directory_path=f"{dataset.location}/train",
annotations_path=f"{dataset.location}/train/_annotations.coco.json",
)
path, image, annotation = ds_train[0]
# loads image on demand
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 [#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 [#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 [#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 [#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.
```python
import supervision as sv
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
import cv2
image = cv2.imread(<SOURCE_IMAGE_PATH>)
cfg = get_cfg()
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")
predictor = DefaultPredictor(cfg)
result = predictor(image)
detections = sv.Detections.from_detectron2(result)
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.
- 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
import cv2
import supervision as sv
import
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8n-640")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
rich_label_annotator = sv.RichLabelAnnotator(font_path=<TTF_FONT_PATH>)
annotated_image = rich_label_annotator.annotate(scene=image.copy(), detections=detections)
```
- Added [#1227](https://github.com/roboflow/supervision/pull/1227): Added support for loading Oriented Bounding Boxes dataset in YOLO format.
```python
import supervision as sv
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
)
_, image, detections in train_ds[0]
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).
!!! 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.
!!! 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).
- 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)
!!! 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.
!!! failure "Removed"
`Color.white()` has been removed due to deprecation. Use `color.WHITE` instead.
!!! failure "Removed"
`Color.black()` has been removed due to deprecation. Use `color.BLACK` instead.
!!! failure "Removed"
`Color.red()` has been removed due to deprecation. Use `color.RED` instead.
!!! failure "Removed"
`Color.green()` has been removed due to deprecation. Use `color.GREEN` instead.
!!! failure "Removed"
`Color.blue()` has been removed due to deprecation. Use `color.BLUE` instead.
!!! 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.
!!! 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.
### 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 [#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.
```python
import supervision as sv
paligemma_result = "<loc0256><loc0256><loc0768><loc0768> cat"
detections = sv.Detections.from_lmm(
sv.LMM.PALIGEMMA,
paligemma_result,
resolution_wh=(1000, 1000),
classes=['cat', 'dog']
)
detections.xyxy
# array([[250., 250., 750., 750.]])
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.
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
edge_annotator = sv.EdgeAnnotator(
color=sv.Color.GREEN,
thickness=5
)
annotated_frame = edge_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
- 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 [#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 [#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.
- 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 [#1178](https://github.com/roboflow/supervision/pull/1178): [`sv.InferenceSlicer`](https://supervision.roboflow.com/develop/detection/tools/inference_slicer/) adding instance segmentation model support.
```python
import cv2
import numpy as np
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8x-seg-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
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 [#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.
### 0.20.0 <small>April 24, 2024</small>
- Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.KeyPoints`](/0.20.0/keypoint/core/#supervision.keypoint.core.KeyPoints) to provide initial support for pose estimation and broader keypoint detection models.
@@ -84,8 +446,6 @@ with csv_sink:
- Added [#819](https://github.com/roboflow/supervision/pull/819): [`sv.JSONSink`](/0.19.0/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink) allowing for the straightforward saving of image, video, or stream inference results in a `.json` file.
```python
```python
import supervision as sv
from ultralytics import YOLO
@@ -133,7 +493,7 @@ annotated_frame = crop_annotator.annotate(
!!! failure "Deprecated"
The `track_buffer`, `track_thresh`, and `match_thresh` parameters in `sv.ByterTrack` are deprecated and will be removed in `supervision-0.23.0`. Use `lost_track_buffer,` `track_activation_threshold`, and `minimum_matching_threshold` instead.
The `track_buffer`, `track_thresh`, and `match_thresh` parameters in `sv.ByteTrack` are deprecated and will be removed in `supervision-0.23.0`. Use `lost_track_buffer,` `track_activation_threshold`, and `minimum_matching_threshold` instead.
- Changed [#910](https://github.com/roboflow/supervision/pull/910): [`sv.PolygonZone`](/0.19.0/detection/tools/polygon_zone/#supervision.detection.tools.polygon_zone.PolygonZone) to now accept a list of specific box anchors that must be in zone for a detection to be counted.
@@ -205,7 +565,6 @@ ColorPalette(colors=[Color(r=68, g=1, b=84), Color(r=59, g=82, b=139), ...])
`sv.ColorPalette.default()` is deprecated and will be removed in `supervision-0.22.0`. Use `sv.ColorPalette.DEFAULT` instead.
- Changed [#769](https://github.com/roboflow/supervision/pull/769): [`sv.ColorPalette.DEFAULT`](/0.18.0/draw/color/#colorpalette) value, giving users a more extensive set of annotation colors.
- Changed [#677](https://github.com/roboflow/supervision/pull/677): `sv.Detections.from_roboflow` to [`sv.Detections.from_inference`](/0.18.0/detection/core/#supervision.detection.core.Detections.from_inference) streamlining its functionality to be compatible with both the both [inference](https://github.com/roboflow/inference) pip package and the Robloflow [hosted API](https://docs.roboflow.com/deploy/hosted-api).
@@ -214,7 +573,6 @@ ColorPalette(colors=[Color(r=68, g=1, b=84), Color(r=59, g=82, b=139), ...])
`Detections.from_roboflow()` is deprecated and will be removed in `supervision-0.22.0`. Use `Detections.from_inference` instead.
- Fixed [#735](https://github.com/roboflow/supervision/pull/735): [`sv.LineZone`](/0.18.0/detection/tools/line_zone/#linezone) functionality to accurately update the counter when an object crosses a line from any direction, including from the side. This enhancement enables more precise tracking and analytics, such as calculating individual in/out counts for each lane on the road.
### 0.17.0 <small>December 06, 2023</small>
@@ -307,13 +665,12 @@ ColorPalette(colors=[Color(r=68, g=1, b=84), Color(r=59, g=82, b=139), ...])
- Fixed [#477](https://github.com/roboflow/supervision/pull/477): Poetry env definition allowing proper local installation.
- Fixed [#430](https://github.com/roboflow/supervision/pull/430): [`sv.ByteTrack`](/0.16.0/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) to return `np.array([], dtype=int)` when `svDetections` is empty.
- Fixed [#430](https://github.com/roboflow/supervision/pull/430): [`sv.ByteTrack`](/0.16.0/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) to return `np.array([], dtype=int)` when `svDetections` is empty.
!!! failure "Deprecated"
`sv.Detections.from_yolov8` and `sv.Classifications.from_yolov8` as those are now replaced by [`sv.Detections.from_ultralytics`](/0.16.0/detection/core/#supervision.detection.core.Detections.from_ultralytics) and [`sv.Classifications.from_ultralytics`](/0.16.0/classification/core/#supervision.classification.core.Classifications.from_ultralytics).
### 0.15.0 <small>October 5, 2023</small>
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.BoundingBoxAnnotator`](/0.15.0/annotators/#supervision.annotators.core.BoundingBoxAnnotator) allowing to annotate images and videos with bounding boxes.
-1
View File
@@ -1,7 +1,6 @@
---
template: cookbooks.html
comments: true
status: new
hide:
- navigation
- toc
+17
View File
@@ -0,0 +1,17 @@
---
comments: true
---
# Datasets Utils
<div class="md-typeset">
<h2><a href="#supervision.dataset.utils.rle_to_mask">rle_to_mask</a></h2>
</div>
:::supervision.dataset.utils.rle_to_mask
<div class="md-typeset">
<h2><a href="#supervision.dataset.utils.mask_to_rle">mask_to_rle</a></h2>
</div>
:::supervision.dataset.utils.mask_to_rle
+30 -13
View File
@@ -3,17 +3,34 @@ comments: true
status: deprecated
---
These features are phased out due to better alternatives or potential issues in future versions. Deprecated functionalities are supported for **three subsequent releases**, providing time for users to transition to updated methods.
# Deprecated
- [`Detections.from_froboflow`](detection/core.md/#supervision.detection.core.Detections.from_roboflow) is deprecated and will be removed in `supervision-0.22.0`. Use [`Detections.from_inference`](detection/core.md/#supervision.detection.core.Detections.from_inference) instead.
- The method `Color.white()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.WHITE` instead.
- The method `Color.black()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.BLACK` instead.
- The method `Color.red()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.RED` instead.
- The method `Color.green()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.GREEN` instead.
- The method `Color.blue()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.BLUE` instead.
- The method [`ColorPalette.default()`](draw/color.md/#supervision.draw.color.ColorPalette.default) is deprecated and will be removed in `supervision-0.22.0`. Use the constant [`ColorPalette.DEFAULT`](draw/color.md/#supervision.draw.color.ColorPalette.DEFAULT) instead.
- `BoxAnnotator` is deprecated and will be removed in `supervision-0.22.0`. Use [`BoundingBoxAnnotator`](detection/annotators.md/#supervision.annotators.core.BoundingBoxAnnotator) and [`LabelAnnotator`](detection/annotators.md/#supervision.annotators.core.LabelAnnotator) instead.
- The method [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) is deprecated and will be removed in `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead.
- The `track_buffer`, `track_thresh`, and `match_thresh` parameters in [`ByterTrack`](trackers.md/#supervision.tracker.byte_tracker.core.ByteTrack) are deprecated and will be removed in `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) is deprecated and will be removed in `supervision-0.23.0`. Use `triggering_anchors ` instead.
- The `frame_resolution_wh ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.24.0`.
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.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.
### 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.
- 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.
- `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.
+104 -7
View File
@@ -5,7 +5,7 @@ status: new
# Annotators
=== "BoundingBox"
=== "Box"
```python
import supervision as sv
@@ -13,8 +13,8 @@ status: new
image = ...
detections = sv.Detections(...)
bounding_box_annotator = sv.BoundingBoxAnnotator()
annotated_frame = bounding_box_annotator.annotate(
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections
)
@@ -285,6 +285,64 @@ status: new
</div>
=== "RichLabel"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence
in zip(detections['class_name'], detections.confidence)
]
rich_label_annotator = sv.RichLabelAnnotator(
font_path="<TTF_FONT_PATH>",
text_position=sv.Position.CENTER
)
annotated_frame = rich_label_annotator.annotate(
scene=image.copy(),
detections=detections,
labels=labels
)
```
<div class="result" markdown>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Icon"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
icon_paths = [
"<ICON_PATH>"
for _ in detections
]
icon_annotator = sv.IconAnnotator()
annotated_frame = icon_annotator.annotate(
scene=image.copy(),
detections=detections,
icon_path=icon_paths
)
```
<div class="result" markdown>
![icon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/icon-annotator-example.png){ align=center width="800" }
</div>
=== "Crop"
```python
@@ -353,7 +411,7 @@ status: new
trace_annotator = sv.TraceAnnotator()
video_info = sv.VideoInfo.from_video_path(video_path='...')
frames_generator = get_video_frames_generator(source_path='...')
frames_generator = sv.get_video_frames_generator(source_path='...')
tracker = sv.ByteTrack()
with sv.VideoSink(target_path='...', video_info=video_info) as sink:
@@ -384,7 +442,7 @@ status: new
heat_map_annotator = sv.HeatMapAnnotator()
video_info = sv.VideoInfo.from_video_path(video_path='...')
frames_generator = get_video_frames_generator(source_path='...')
frames_generator = sv.get_video_frames_generator(source_path='...')
with sv.VideoSink(target_path='...', video_info=video_info) as sink:
for frame in frames_generator:
@@ -402,11 +460,32 @@ status: new
</div>
=== "Background Color"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
background_overlay_annotator = sv.BackgroundOverlayAnnotator()
annotated_frame = background_overlay_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
<div class="result" markdown>
![background-overlay-annotator-example](https://media.roboflow.com/supervision-annotator-examples/background-color-annotator-example-purple.png)
</div>
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.BoundingBoxAnnotator">BoundingBoxAnnotator</a></h2>
<h2><a href="#supervision.annotators.core.BoxAnnotator">BoxAnnotator</a></h2>
</div>
:::supervision.annotators.core.BoundingBoxAnnotator
:::supervision.annotators.core.BoxAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.RoundBoxAnnotator">RoundBoxAnnotator</a></h2>
@@ -492,6 +571,18 @@ status: new
:::supervision.annotators.core.LabelAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.RichLabelAnnotator">RichLabelAnnotator</a></h2>
</div>
:::supervision.annotators.core.RichLabelAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.IconAnnotator">IconAnnotator</a></h2>
</div>
:::supervision.annotators.core.IconAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.BlurAnnotator">BlurAnnotator</a></h2>
</div>
@@ -516,6 +607,12 @@ status: new
:::supervision.annotators.core.CropAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.BackgroundOverlayAnnotator">BackgroundOverlayAnnotator</a></h2>
</div>
:::supervision.annotators.core.BackgroundOverlayAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.ColorLookup">ColorLookup</a></h2>
</div>
+29
View File
@@ -0,0 +1,29 @@
---
comments: true
---
# Double Detection Filter
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.OverlapFilter">OverlapFilter</a></h2>
</div>
:::supervision.detection.overlap_filter.OverlapFilter
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.box_non_max_suppression">box_non_max_suppression</a></h2>
</div>
:::supervision.detection.overlap_filter.box_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.mask_non_max_suppression">mask_non_max_suppression</a></h2>
</div>
:::supervision.detection.overlap_filter.mask_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.box_non_max_merge">box_non_max_merge</a></h2>
</div>
:::supervision.detection.overlap_filter.box_non_max_merge
+4 -1
View File
@@ -2,7 +2,10 @@
comments: true
---
# Metrics
# Legacy Metrics
Starting with `0.23.0`, a new metrics module is being introduced to supervision.
Metrics here are part of the legacy evaluation API and will be deprecated in the future.
<div class="md-typeset">
<h2><a href="#supervision.metrics.detection.ConfusionMatrix">ConfusionMatrix</a></h2>
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# InferenceSlicer
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Save Detections
+42 -13
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Detection Utils
@@ -17,18 +16,6 @@ status: new
:::supervision.detection.utils.mask_iou_batch
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.box_non_max_suppression">box_non_max_suppression</a></h2>
</div>
:::supervision.detection.utils.box_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.mask_non_max_suppression">mask_non_max_suppression</a></h2>
</div>
:::supervision.detection.utils.mask_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.polygon_to_mask">polygon_to_mask</a></h2>
</div>
@@ -65,8 +52,50 @@ status: new
:::supervision.detection.utils.move_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.move_masks">move_masks</a></h2>
</div>
:::supervision.detection.utils.move_masks
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.scale_boxes">scale_boxes</a></h2>
</div>
:::supervision.detection.utils.scale_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.clip_boxes">clip_boxes</a></h2>
</div>
:::supervision.detection.utils.clip_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.pad_boxes">pad_boxes</a></h2>
</div>
:::supervision.detection.utils.pad_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.xywh_to_xyxy">xywh_to_xyxy</a></h2>
</div>
:::supervision.detection.utils.xywh_to_xyxy
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.xcycwh_to_xyxy">xcycwh_to_xyxy</a></h2>
</div>
:::supervision.detection.utils.xcycwh_to_xyxy
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.contains_holes">contains_holes</a></h2>
</div>
:::supervision.detection.utils.contains_holes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.contains_multiple_segments">contains_multiple_segments</a></h2>
</div>
:::supervision.detection.utils.contains_multiple_segments
+14 -15
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Detect and Annotate
@@ -136,7 +135,7 @@ 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.BoundingBoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoundingBoxAnnotator) 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/annotators/#supervision.annotators.core.BoxAnnotator) and [`sv.LabelAnnotator`](/latest/annotators/#supervision.annotators.core.LabelAnnotator) classes.
=== "Inference"
@@ -150,10 +149,10 @@ Finally, we can annotate the image with the predictions. Since we are working wi
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -171,10 +170,10 @@ Finally, we can annotate the image with the predictions. Since we are working wi
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -205,10 +204,10 @@ Finally, we can annotate the image with the predictions. Since we are working wi
transformers_results=results,
id2label=model.config.id2label)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -234,7 +233,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -243,7 +242,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -261,7 +260,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -270,7 +269,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -301,7 +300,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
transformers_results=results,
id2label=model.config.id2label)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -310,7 +309,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -323,7 +322,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
If you are running the segmentation model
[`sv.MaskAnnotator`](/latest/annotators/#supervision.annotators.core.MaskAnnotator)
is a drop-in replacement for
[`sv.BoundingBoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoundingBoxAnnotator)
[`sv.BoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoxAnnotator)
that will allow you to draw masks instead of boxes.
=== "Inference"
+84 -25
View File
@@ -1,12 +1,11 @@
---
comments: true
status: new
---
# Detect Small Objects
This guide shows how to detect small objects
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 using
[`InferenceSlicer`](/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer).
@@ -32,10 +31,10 @@ size relative to the image resolution.
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -53,10 +52,10 @@ size relative to the image resolution.
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -68,10 +67,10 @@ size relative to the image resolution.
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
from transformers import DetrImageProcessor, DetrForSegmentation
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
@@ -79,13 +78,13 @@ size relative to the image resolution.
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
width, height = image_slice.size
target_size = torch.tensor([[width, height]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -94,7 +93,7 @@ size relative to the image resolution.
in detections.class_id
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -120,10 +119,10 @@ is less effective for ultra-high-resolution images (4K and above).
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -141,10 +140,10 @@ is less effective for ultra-high-resolution images (4K and above).
results = model(image, imgsz=1280)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -175,15 +174,15 @@ objects within each, and aggregating the results.
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
detections = sv.Detections.from_inference(results)
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -207,10 +206,10 @@ objects within each, and aggregating the results.
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -239,8 +238,8 @@ objects within each, and aggregating the results.
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
width, height = image_slice.size
target_size = torch.tensor([[width, height]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
return sv.Detections.from_transformers(results)
@@ -248,7 +247,7 @@ objects within each, and aggregating the results.
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -257,10 +256,70 @@ objects within each, and aggregating the results.
in detections.class_id
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
![detection-with-inference-slicer](https://media.roboflow.com/supervision_detect_small_objects_example_3.png)
## Small Object Segmentation
[`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
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8x-seg-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Ultralytics"
```{ .py hl_lines="6 16 19-20" }
import cv2
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x-seg.pt")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
result = model(image_slice)[0]
return sv.Detections.from_ultralytics(result)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
![detection-with-inference-slicer](https://media.roboflow.com/supervision-docs/inference-slicer-segmentation-example.png)
+454
View File
@@ -0,0 +1,454 @@
---
comments: true
---
With Supervision, you can load and manipulate classification, object detection, and
segmentation datasets. This tutorial will walk you through how to load, split, merge,
visualize, and augment datasets in Supervision.
## Download Dataset
In this tutorial, we will use a dataset from
[Roboflow Universe](https://universe.roboflow.com/), a public repository of
thousands of computer vision datasets. If you already have your dataset in
[COCO](https://roboflow.com/formats/coco-json),
[YOLO](https://roboflow.com/formats/yolov8-pytorch-txt),
or [Pascal VOC](https://roboflow.com/formats/pascal-voc-xml) format, you can skip this
section.
```bash
pip install roboflow
```
Next, log into your Roboflow account and download the dataset of your choice in the
COCO, YOLO, or Pascal VOC format. You can customize the following code snippet with
your workspace ID, project ID, and version number.
=== "COCO"
```python
import roboflow
roboflow.login()
rf = roboflow.Roboflow()
project = rf.workspace('<WORKSPACE_ID>').project('<PROJECT_ID>')
dataset = project.version('<PROJECT_VERSION>').download("coco")
```
=== "YOLO"
```python
import roboflow
roboflow.login()
rf = roboflow.Roboflow()
project = rf.workspace('<WORKSPACE_ID>').project('<PROJECT_ID>')
dataset = project.version('<PROJECT_VERSION>').download("yolov8")
```
=== "Pascal VOC"
```python
import roboflow
roboflow.login()
rf = roboflow.Roboflow()
project = rf.workspace('<WORKSPACE_ID>').project('<PROJECT_ID>')
dataset = project.version('<PROJECT_VERSION>').download("voc")
```
## Load Dataset
The Supervision library provides convenient functions to load datasets in various
formats. If your dataset is already split into train, test, and valid subsets, you can
load each of those as separate [`sv.DetectionDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset)
instances.
=== "COCO"
We can do so using the [`sv.DetectionDataset.from_coco`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.from_coco) to load annotations in [COCO](https://roboflow.com/formats/coco-json) format.
```python
import supervision as sv
ds_train = sv.DetectionDataset.from_coco(
images_directory_path=f'{dataset.location}/train',
annotations_path=f'{dataset.location}/train/_annotations.coco.json',
)
ds_valid = sv.DetectionDataset.from_coco(
images_directory_path=f'{dataset.location}/valid',
annotations_path=f'{dataset.location}/valid/_annotations.coco.json',
)
ds_test = sv.DetectionDataset.from_coco(
images_directory_path=f'{dataset.location}/test',
annotations_path=f'{dataset.location}/test/_annotations.coco.json',
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
```
=== "YOLO"
We can do so using the [`sv.DetectionDataset.from_yolo`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.from_yolo) to load annotations in [YOLO](https://roboflow.com/formats/yolov8-pytorch-txt) format.
```python
import supervision as sv
ds_train = sv.DetectionDataset.from_yolo(
images_directory_path=f'{dataset.location}/train/images',
annotations_directory_path=f'{dataset.location}/train/labels',
data_yaml_path=f'{dataset.location}/data.yaml'
)
ds_valid = sv.DetectionDataset.from_yolo(
images_directory_path=f'{dataset.location}/valid/images',
annotations_directory_path=f'{dataset.location}/valid/labels',
data_yaml_path=f'{dataset.location}/data.yaml'
)
ds_test = sv.DetectionDataset.from_yolo(
images_directory_path=f'{dataset.location}/test/images',
annotations_directory_path=f'{dataset.location}/test/labels',
data_yaml_path=f'{dataset.location}/data.yaml'
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
```
=== "Pascal VOC"
We can do so using the [`sv.DetectionDataset.from_pascal_voc`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.from_pascal_voc) to load annotations in [Pascal VOC](https://roboflow.com/formats/pascal-voc-xml) format.
```python
import supervision as sv
ds_train = sv.DetectionDataset.from_pascal_voc(
images_directory_path=f'{dataset.location}/train/images',
annotations_directory_path=f'{dataset.location}/train/labels'
)
ds_valid = sv.DetectionDataset.from_pascal_voc(
images_directory_path=f'{dataset.location}/valid/images',
annotations_directory_path=f'{dataset.location}/valid/labels'
)
ds_test = sv.DetectionDataset.from_pascal_voc(
images_directory_path=f'{dataset.location}/test/images',
annotations_directory_path=f'{dataset.location}/test/labels'
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
```
## Split Dataset
If your dataset is not already split into train, test, and valid subsets, you can
easily do so using the [`sv.DetectionDataset.split`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.split)
method. We can split it as follows, ensuring a random shuffle of the data.
```python
import supervision as sv
ds = sv.DetectionDataset(...)
len(ds)
# 1000
ds_train, ds = ds.split(split_ratio=0.8, shuffle=True)
ds_valid, ds_test = ds.split(split_ratio=0.5, shuffle=True)
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
```
## Merge Dataset
If you have multiple datasets that you would like to merge, you can do so using the
[`sv.DetectionDataset.merge`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.merge)
method.
=== "COCO"
```{ .py hl_lines="22-28" }
import supervision as sv
ds_train = sv.DetectionDataset.from_coco(
images_directory_path=f'{dataset.location}/train',
annotations_path=f'{dataset.location}/train/_annotations.coco.json',
)
ds_valid = sv.DetectionDataset.from_coco(
images_directory_path=f'{dataset.location}/valid',
annotations_path=f'{dataset.location}/valid/_annotations.coco.json',
)
ds_test = sv.DetectionDataset.from_coco(
images_directory_path=f'{dataset.location}/test',
annotations_path=f'{dataset.location}/test/_annotations.coco.json',
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
ds = sv.DetectionDataset.merge([ds_train, ds_valid, ds_test])
ds.classes
# ['person', 'bicycle', 'car', ...]
len(ds)
# 1000
```
=== "YOLO"
```{ .py hl_lines="25-31" }
import supervision as sv
ds_train = sv.DetectionDataset.from_yolo(
images_directory_path=f'{dataset.location}/train/images',
annotations_directory_path=f'{dataset.location}/train/labels',
data_yaml_path=f'{dataset.location}/data.yaml'
)
ds_valid = sv.DetectionDataset.from_yolo(
images_directory_path=f'{dataset.location}/valid/images',
annotations_directory_path=f'{dataset.location}/valid/labels',
data_yaml_path=f'{dataset.location}/data.yaml'
)
ds_test = sv.DetectionDataset.from_yolo(
images_directory_path=f'{dataset.location}/test/images',
annotations_directory_path=f'{dataset.location}/test/labels',
data_yaml_path=f'{dataset.location}/data.yaml'
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
ds = sv.DetectionDataset.merge([ds_train, ds_valid, ds_test])
ds.classes
# ['person', 'bicycle', 'car', ...]
len(ds)
# 1000
```
=== "Pascal VOC"
```{ .py hl_lines="22-28" }
import supervision as sv
ds_train = sv.DetectionDataset.from_pascal_voc(
images_directory_path=f'{dataset.location}/train/images',
annotations_directory_path=f'{dataset.location}/train/labels'
)
ds_valid = sv.DetectionDataset.from_pascal_voc(
images_directory_path=f'{dataset.location}/valid/images',
annotations_directory_path=f'{dataset.location}/valid/labels'
)
ds_test = sv.DetectionDataset.from_pascal_voc(
images_directory_path=f'{dataset.location}/test/images',
annotations_directory_path=f'{dataset.location}/test/labels'
)
ds_train.classes
# ['person', 'bicycle', 'car', ...]
len(ds_train), len(ds_valid), len(ds_test)
# 800, 100, 100
ds = sv.DetectionDataset.merge([ds_train, ds_valid, ds_test])
ds.classes
# ['person', 'bicycle', 'car', ...]
len(ds)
# 1000
```
## Iterate over Dataset
There are two ways to loop over a `sv.DetectionDataset`: using a direct
[for loop](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.__iter__)
called on the `sv.DetectionDataset` instance or loading `sv.DetectionDataset` entries
[by index](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.__getitem__).
```python
import supervision as sv
ds = sv.DetectionDataset(...)
# Option 1
for image_path, image, annotations in ds:
... # Process each image and its annotations
# Option 2
for idx in range(len(ds)):
image_path, image, annotations = ds[idx]
... # Process the image and annotations at index `idx`
```
## Visualize Dataset
The Supervision library provides tools for easily visualizing your detection dataset.
You can create a grid of annotated images to quickly inspect your data and labels.
First, initialize the [`sv.BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator)
and [`sv.LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator).
Then, iterate through a subset of the dataset (e.g., the first 25 images), drawing
bounding boxes and class labels on each image. Finally, combine the annotated images
into a grid for display.
```python
import supervision as sv
ds = sv.DetectionDataset(...)
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_images = []
for i in range(16):
_, image, annotations = ds[i]
labels = [ds.classes[class_id] for class_id in annotations.class_id]
annotated_image = image.copy()
annotated_image = box_annotator.annotate(annotated_image, annotations)
annotated_image = label_annotator.annotate(annotated_image, annotations, labels)
annotated_images.append(annotated_image)
grid = sv.create_tiles(
annotated_images,
grid_size=(4, 4),
single_tile_size=(400, 400),
tile_padding_color=sv.Color.WHITE,
tile_margin_color=sv.Color.WHITE
)
```
![visualize-dataset](https://media.roboflow.com/supervision-docs/visualize-dataset.png)
## Save Dataset
=== "COCO"
We can do so using the [`sv.DetectionDataset.as_coco`](https://supervision.roboflow.com/datasets/#supervision.dataset.core.DetectionDataset.as_coco) method to save annotations in [COCO](https://roboflow.com/formats/coco-json) format.
```python
import supervision as sv
ds = sv.DetectionDataset(...)
ds.as_coco(
images_directory_path='<IMAGE_DIRECTORY_PATH>',
annotations_path='<ANNOTATIONS_PATH>'
)
```
=== "YOLO"
We can do so using the [`sv.DetectionDataset.as_yolo`](https://supervision.roboflow.com/datasets/#supervision.dataset.core.DetectionDataset.as_yolo) method to save annotations in [YOLO](https://roboflow.com/formats/yolov8-pytorch-txt) format.
```python
import supervision as sv
ds = sv.DetectionDataset(...)
ds.as_yolo(
images_directory_path='<IMAGE_DIRECTORY_PATH>',
annotations_directory_path='<ANNOTATIONS_DIRECTORY_PATH>',
data_yaml_path='<DATA_YAML_PATH>'
)
```
=== "Pascal VOC"
We can do so using the [`sv.DetectionDataset.as_pascal_voc`](https://supervision.roboflow.com/datasets/#supervision.dataset.core.DetectionDataset.as_pascal_voc) method to save annotations in [Pascal VOC](https://roboflow.com/formats/pascal-voc-xml) format.
```python
import supervision as sv
ds = sv.DetectionDataset(...)
ds.as_pascal_voc(
images_directory_path='<IMAGE_DIRECTORY_PATH>',
annotations_directory_path='<ANNOTATIONS_DIRECTORY_PATH>'
)
```
## Augment Dataset
In this section, we'll explore using Supervision in combination with Albumentations to
augment our dataset. Data augmentation is a common technique in computer vision to
increase the size and diversity of training datasets, leading to improved model
performance and generalization.
```bash
pip install augmentation
```
Albumentations provides a flexible and powerful API for image augmentation. The core of
the library is the [`Compose`](https://albumentations.ai/docs/api_reference/full_reference/?h=compose#albumentations.core.composition.Compose)
class, which allows you to chain multiple image transformations together. Each
transformation is defined using a dedicated class, such as
[`HorizontalFlip`](https://albumentations.ai/docs/api_reference/full_reference/?h=horizontalflip#albumentations.augmentations.geometric.transforms.HorizontalFlip),
[`RandomBrightnessContrast`](https://albumentations.ai/docs/api_reference/full_reference/?h=horizontalflip#albumentations.augmentations.transforms.RandomBrightnessContrast),
or [`Perspective`](https://albumentations.ai/docs/api_reference/full_reference/?h=horizontalflip#albumentations.augmentations.geometric.transforms.Perspective).
```python
import albumentations as A
augmentation = A.Compose(
transforms=[
A.Perspective(p=0.1),
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.5)
],
bbox_params=A.BboxParams(
format='pascal_voc',
label_fields=['category']
),
)
```
The key is to set `format='pascal_voc'`, which corresponds to the
`[x_min, y_min, x_max, y_max]` bounding box format used in Supervision.
```python
import numpy as np
import supervision as sv
from dataclasses import replace
ds = sv.DetectionDataset(...)
_, original_image, original_annotations = ds[0]
output = augmentation(
image=original_image,
bboxes=original_annotations.xyxy,
category=original_annotations.class_id
)
augmented_image = output['image']
augmented_annotations = replace(
original_annotations,
xyxy=np.array(output['bboxes']),
class_id=np.array(output['category'])
)
```
![augment-dataset](https://media.roboflow.com/supervision-docs/augment-dataset.png)
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Save Detections
+1 -1
View File
@@ -32,7 +32,7 @@ download_assets(VideoAssets.PEOPLE_WALKING)
First, you'll need to obtain predictions from your object detection or segmentation
model. In this tutorial, we are using the YOLOv8 model as an example. However,
Supervision is versatile and compatible with various models. Check this
[link](latest/how_to/detect_and_annotate/#load-predictions-into-supervision)
[link](/latest/how_to/detect_and_annotate/#load-predictions-into-supervision)
for guidance on how to plug in other models.
We will define a `callback` function, which will process each frame of the video
+9
View File
@@ -146,4 +146,13 @@ You can install `supervision` in a
Master the techniques to selectively filter and focus on objects within a specific zone
- **Cheatsheet**
***
Access a quick reference guide to the most common `supervision` functions
[:octicons-arrow-right-24: Cheatsheet](https://roboflow.github.io/cheatsheet-supervision/)
</div>
+54 -57
View File
@@ -28,16 +28,15 @@ document.addEventListener("DOMContentLoaded", function () {
}, {});
async function renderCard(element, elementIndex) {
const url = element.getAttribute('data-url');
const name = element.getAttribute('data-name');
const labels = element.getAttribute('data-labels');
const version = element.getAttribute('data-version');
const authors = element.getAttribute('data-author');
async function renderCard(element, elementIndex) {
const name = element.getAttribute('data-name');
const labels = element.getAttribute('data-labels');
const version = element.getAttribute('data-version');
const authors = element.getAttribute('data-author');
const labelHTML = labels ? labels.split(',').filter(label => label !== '').map((label, index) => {
const color = labelToColor[label.trim()];
return `
const labelHTML = labels ? labels.split(',').filter(label => label !== '').map((label, index) => {
const color = labelToColor[label.trim()];
return `
<span
class="label non-selectable-text"
style="background-color: ${color}"
@@ -45,18 +44,18 @@ document.addEventListener("DOMContentLoaded", function () {
${label.trim()}
</span>
`;
}).join(' ') : '';
}).join(' ') : '';
const authorArray = authors.split(',');
const authorDataArray = await Promise.all(authorArray.map(async (author) => {
const response = await fetch(`https://api.github.com/users/${author.trim()}`);
return await response.json();
}));
const authorArray = authors.split(',');
const authorDataArray = await Promise.all(authorArray.map(async (author) => {
const response = await fetch(`https://api.github.com/users/${author.trim()}`);
return await response.json();
}));
let authorAvatarsHTML = authorDataArray.map((authorData, index) => {
const marginLeft = index === 0 ? '0' : '-10px';
const zIndex = 4 - index;
return `
let authorAvatarsHTML = authorDataArray.map((authorData, index) => {
const marginLeft = index === 0 ? '0' : '-10px';
const zIndex = 4 - index;
return `
<div
class="author-container"
data-login="${authorData.login}-${elementIndex}"
@@ -75,10 +74,10 @@ document.addEventListener("DOMContentLoaded", function () {
</a>
</div>
`;
}).join('');
}).join('');
let authorNamesHTML = authorDataArray.map(
authorData => `
let authorNamesHTML = authorDataArray.map(
authorData => `
<span
class="author-name"
data-login="${authorData.login}-${elementIndex}"
@@ -88,54 +87,52 @@ document.addEventListener("DOMContentLoaded", function () {
${authorData.login}
</a>
</span>`
).join(',&nbsp;');
).join(',&nbsp;');
let authorsHTML = `
let authorsHTML = `
<div class="authors">
${authorAvatarsHTML}
<div class="author-names">${authorNamesHTML}</div>
</div>
`;
`;
element.innerText = `
<a style="text-decoration: none; color: inherit;" href="${url}">
<div style="flex-direction: column; height: 100%; display: flex;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; background: ${theme.background}; font-size: 14px; line-height: 1.5; color: ${theme.color}">
<div style="display: flex; align-items: center;">
<span style="font-weight: 700; font-size: 1rem; color: ${theme.linkColor};">
${name}
</span>
</div>
${authorsHTML}
<div style="font-size: 12px; color: ${theme.color}; display: flex; flex: 0; justify-content: space-between">
element.innerText = `
<div style="flex-direction: column; height: 100%; display: flex;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; background: ${theme.background}; font-size: 14px; line-height: 1.5; color: ${theme.color}">
<div style="display: flex; align-items: center;">
<img src="/assets/supervision-lenny.png" aria-label="stars" width="20" height="20" role="img" />
&nbsp;
<span style="margin-left: 4px">${version}</span>
<span style="font-weight: 700; font-size: 1rem; color: ${theme.linkColor};">
${name}
</span>
</div>
<div style="display: flex; align-items: center; flex-wrap: wrap">
${labelHTML}
${authorsHTML}
<div style="font-size: 12px; color: ${theme.color}; display: flex; flex: 0; justify-content: space-between">
<div style="display: flex; align-items: center;">
<img src="/assets/supervision-lenny.png" aria-label="stars" width="20" height="20" role="img" />
&nbsp;
<span style="margin-left: 4px">${version}</span>
</div>
<div style="display: flex; align-items: center; flex-wrap: wrap">
${labelHTML}
</div>
</div>
</div>
</div>
</a>
`
`;
let sanitizedHTML = DOMPurify.sanitize(element.innerText);
element.innerHTML = sanitizedHTML;
let sanitizedHTML = DOMPurify.sanitize(element.innerText);
element.innerHTML = sanitizedHTML;
document.querySelectorAll('.author-name').forEach(element => {
element.addEventListener('mouseenter', function() {
const login = this.getAttribute('data-login');
document.querySelector(`.author-container[data-login="${login}"]`).classList.add('hover');
document.querySelectorAll('.author-name').forEach(element => {
element.addEventListener('mouseenter', function () {
const login = this.getAttribute('data-login');
document.querySelector(`.author-container[data-login="${login}"]`).classList.add('hover');
});
element.addEventListener('mouseleave', function () {
const login = this.getAttribute('data-login');
document.querySelector(`.author-container[data-login="${login}"]`).classList.remove('hover');
});
});
element.addEventListener('mouseleave', function() {
const login = this.getAttribute('data-login');
document.querySelector(`.author-container[data-login="${login}"]`).classList.remove('hover');
});
});
}
}
repoCards.forEach((element, index) => {
renderCard(element, index);
});
+39 -2
View File
@@ -13,7 +13,10 @@ status: new
image = ...
key_points = sv.KeyPoints(...)
vertex_annotator = sv.VertexAnnotator(color=sv.Color.GREEN, radius=10)
vertex_annotator = sv.VertexAnnotator(
color=sv.Color.GREEN,
radius=10
)
annotated_frame = vertex_annotator.annotate(
scene=image.copy(),
key_points=key_points
@@ -34,7 +37,10 @@ status: new
image = ...
key_points = sv.KeyPoints(...)
edge_annotator = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5)
edge_annotator = sv.EdgeAnnotator(
color=sv.Color.GREEN,
thickness=5
)
annotated_frame = edge_annotator.annotate(
scene=image.copy(),
key_points=key_points
@@ -47,6 +53,31 @@ status: new
</div>
=== "VertexLabelAnnotator"
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
vertex_label_annotator = sv.VertexLabelAnnotator(
color=sv.Color.GREEN,
text_color=sv.Color.BLACK,
border_radius=5
)
annotated_frame = vertex_label_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
<div class="result" markdown>
![vertex-label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/vertex-label-annotator-example.png){ align=center width="800" }
</div>
<div class="md-typeset">
<h2><a href="#supervision.keypoint.annotators.VertexAnnotator">VertexAnnotator</a></h2>
</div>
@@ -58,3 +89,9 @@ status: new
</div>
:::supervision.keypoint.annotators.EdgeAnnotator
<div class="md-typeset">
<h2><a href="#supervision.keypoint.annotators.VertexLabelAnnotator">VertexLabelAnnotator</a></h2>
</div>
:::supervision.keypoint.annotators.VertexLabelAnnotator
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Keypoint Detection
+18
View File
@@ -0,0 +1,18 @@
---
comments: true
status: new
---
# Mean Average Precision
<div class="md-typeset">
<h2><a href="#supervision.metrics.mean_average_precision.MeanAveragePrecision">MeanAveragePrecision</a></h2>
</div>
:::supervision.metrics.mean_average_precision.MeanAveragePrecision
<div class="md-typeset">
<h2><a href="#supervision.metrics.mean_average_precision.MeanAveragePrecisionResult">MeanAveragePrecisionResult</a></h2>
</div>
:::supervision.metrics.mean_average_precision.MeanAveragePrecisionResult
+357 -224
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
+10
View File
@@ -19,6 +19,16 @@
}
}
.custom-grid a {
color: inherit;
text-decoration: none;
display: block;
}
.custom-grid a:hover {
color: inherit; /* Ensure color does not change on hover */
}
.repo-card {
background: radial-gradient(at right top, #A351FB25, #A5F9EA25);
border-radius: 0.5rem;
+39 -8
View File
@@ -11,14 +11,45 @@
<h1>Supervision Cookbooks</h1>
</div>
<div class="custom-grid">
<p class="card repo-card" data-url="/develop/notebooks/quickstart" data-name="Supervision Quickstart" data-labels="ANNOTATOR,DETECTION,SAM" data-version="v0.18.0" data-author="SkalskiP,onuralpszr"></p>
<p class="card repo-card" data-url="/develop/notebooks/count-objects-crossing-the-line" data-name="Count Objects Crossing the Line" data-labels="ANNOTATORS,LINE ZONE,TRACKING" data-version="v0.18.0" data-author="SkalskiP"></p>
<p class="card repo-card" data-url="/develop/notebooks/zero-shot-object-detection-with-yolo-world" data-name="Zero-Shot Object Detection with YOLO-World" data-labels="ANNOTATORS,DETECTION,INFERENCE" data-version="v0.19.0" data-author="SkalskiP"></p>
<p class="card repo-card" data-url="/develop/notebooks/download-supervision-assets" data-name="Downloading Supervision Assets" data-labels="ASSETS" data-version="v0.18.0" data-author="nickherrig"></p>
<p class="card repo-card" data-url="/develop/notebooks/annotate-video-with-detections" data-name="Annotate Video with Detections" data-labels="INFERENCE,YOLOV8" data-version="v0.18.0" data-author="nickherrig"></p>
<p class="card repo-card" data-url="/develop/notebooks/object-tracking" data-name="Object Tracking" data-labels="TRACKING, ANNOTATOR" data-version="v0.18.0" data-author="nickherrig"></p>
<p class="card repo-card" data-url="/develop/notebooks/occupancy_analytics" data-name="Analyzing Zone Occupancy" data-labels="ANNOTATOR,DETECTION,ZONES" data-version="v0.19.0" data-author="stellasphere"></p>
<p class="card repo-card" data-url="/develop/notebooks/evaluating-alignment-of-text-to-image-diffusion-models" data-name="Evaluating Alignment of Text-to-image Diffusion Models" data-labels="ANNOTATORS,YOLO WORLD" data-version="v0.19.0rc5" data-author="iamhatesz"></p>
<a href="/develop/notebooks/quickstart"> <p class="card repo-card" data-name="Supervision Quickstart" data-labels="ANNOTATOR,DETECTION,SAM"
data-version="v0.18.0" data-author="SkalskiP,onuralpszr"></p>
</a>
<a href="/develop/notebooks/count-objects-crossing-the-line">
<p class="card repo-card" data-name="Count Objects Crossing the Line"
data-labels="ANNOTATORS,LINE ZONE,TRACKING" data-version="v0.18.0" data-author="SkalskiP"></p>
</a>
<a href="/develop/notebooks/zero-shot-object-detection-with-yolo-world">
<p class="card repo-card" data-name="Zero-Shot Object Detection with YOLO-World"
data-labels="ANNOTATORS,DETECTION,INFERENCE" data-version="v0.19.0" data-author="SkalskiP"></p>
</a>
<a href="/develop/notebooks/download-supervision-assets">
<p class="card repo-card" data-name="Downloading Supervision Assets" data-labels="ASSETS" data-version="v0.18.0"
data-author="nickherrig"></p>
</a>
<a href="/develop/notebooks/annotate-video-with-detections">
<p class="card repo-card" data-name="Annotate Video with Detections" data-labels="INFERENCE,YOLOV8"
data-version="v0.18.0" data-author="nickherrig"></p>
</a>
<a href="/develop/notebooks/object-tracking">
<p class="card repo-card" data-name="Object Tracking" data-labels="TRACKING, ANNOTATOR" data-version="v0.18.0"
data-author="nickherrig"></p>
</a>
<a href="/develop/notebooks/occupancy_analytics">
<p class="card repo-card" data-name="Analyzing Zone Occupancy" data-labels="ANNOTATOR,DETECTION,ZONES"
data-version="v0.19.0" data-author="stellasphere"></p>
</a>
<a href="/develop/notebooks/evaluating-alignment-of-text-to-image-diffusion-models">
<p class="card repo-card" data-name="Evaluating Alignment of Text-to-image Diffusion Models"
data-labels="ANNOTATORS,YOLO WORLD" data-version="v0.19.0rc5" data-author="iamhatesz"></p>
</a>
<a href="/develop/notebooks/serialise-detections-to-csv">
<p class="card repo-card" data-name="Serialise Detections to a CSV File"
data-labels="DETECTIONS,CSV SINK,INFERENCE" data-version="v0.21.0" data-author="onuralpszr"></p>
</a>
<a href="/develop/notebooks/serialise-detections-to-json">
<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>
</div>
</div>
</section>
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# ByteTrack
+1 -1
View File
@@ -41,7 +41,7 @@ comments: true
:::supervision.draw.utils.draw_image
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.calculate_optimal_font_scale">calculate_optimal_font_scale</a></h2>
<h2><a href="#supervision.draw.utils.calculate_optimal_text_scale">calculate_optimal_text_scale</a></h2>
</div>
:::supervision.draw.utils.calculate_optimal_text_scale
+1 -1
View File
@@ -12,7 +12,7 @@ status: new
:::supervision.utils.image.crop_image
<div class="md-typeset">
<h2><a href="#supervision.utils.image.scale_image">crop_image</a></h2>
<h2><a href="#supervision.utils.image.scale_image">scale_image</a></h2>
</div>
:::supervision.utils.image.scale_image
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Iterables Utils
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# Video Utils
@@ -11,7 +11,7 @@ from tqdm import tqdm
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.DEFAULT
def load_zones_config(file_path: str) -> List[np.ndarray]:
@@ -75,7 +75,7 @@ def detect(
frame (np.ndarray): The frame to process, expected to be a NumPy array.
model (RoboflowInferenceModel): The Inference model used for processing the
frame.
confidence_threshold (float, optional): The confidence threshold for filtering
confidence_threshold (float): The confidence threshold for filtering
detections. Default is 0.5.
Returns:
@@ -1,5 +1,5 @@
gdown
inference
inference==0.9.17
supervision>=0.20.0
tqdm
ultralytics
@@ -9,7 +9,7 @@ from ultralytics import YOLO
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.DEFAULT
def load_zones_config(file_path: str) -> List[np.ndarray]:
@@ -72,7 +72,7 @@ def detect(
Args:
frame (np.ndarray): The frame to process, expected to be a NumPy array.
model (YOLO): The YOLO model used for processing the frame.
confidence_threshold (float, optional): The confidence threshold for filtering
confidence_threshold (float): The confidence threshold for filtering
detections. Default is 0.5.
Returns:
+7 -7
View File
@@ -20,9 +20,9 @@ def heatmap_and_track(
iou_threshold: float = 0.5,
heatmap_alpha: float = 0.5,
radius: int = 25,
track_threshold: float = 0.35,
track_activation_threshold: float = 0.35,
track_seconds: int = 5,
match_threshold: float = 0.99,
minimum_matching_threshold: float = 0.99,
) -> None:
### instantiate model
model = YOLO(source_weights_path)
@@ -47,9 +47,9 @@ def heatmap_and_track(
### tracker config
byte_tracker = sv.ByteTrack(
track_thresh=track_threshold,
track_buffer=track_seconds * fps,
match_thresh=match_threshold,
track_activation_threshold=track_activation_threshold,
lost_track_buffer=track_seconds * fps,
minimum_matching_threshold=minimum_matching_threshold,
frame_rate=fps,
)
@@ -175,7 +175,7 @@ if __name__ == "__main__":
iou_threshold=args.iou_threshold,
heatmap_alpha=args.heatmap_alpha,
radius=args.radius,
track_threshold=args.track_threshold,
track_activation_threshold=args.track_threshold,
track_seconds=args.track_seconds,
match_threshold=args.match_threshold,
minimum_matching_threshold=args.match_threshold,
)
@@ -95,14 +95,14 @@ if __name__ == "__main__":
model = get_roboflow_model(model_id=args.model_id, api_key=args.roboflow_api_key)
byte_track = sv.ByteTrack(
frame_rate=video_info.fps, track_thresh=args.confidence_threshold
frame_rate=video_info.fps, track_activation_threshold=args.confidence_threshold
)
thickness = sv.calculate_optimal_line_thickness(
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
box_annotator = sv.BoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
text_thickness=thickness,
@@ -154,7 +154,7 @@ if __name__ == "__main__":
annotated_frame = trace_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = label_annotator.annotate(
+2 -2
View File
@@ -1,6 +1,6 @@
supervision>=0.20.0
tqdm==4.66.1
tqdm==4.66.3
requests
ultralytics==8.0.237
super-gradients==3.5.0
inference<=0.9.21
inference==0.9.17
@@ -73,14 +73,14 @@ if __name__ == "__main__":
model = YOLO("yolov8x.pt")
byte_track = sv.ByteTrack(
frame_rate=video_info.fps, track_thresh=args.confidence_threshold
frame_rate=video_info.fps, track_activation_threshold=args.confidence_threshold
)
thickness = sv.calculate_optimal_line_thickness(
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
box_annotator = sv.BoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
text_thickness=thickness,
@@ -132,7 +132,7 @@ if __name__ == "__main__":
annotated_frame = trace_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = label_annotator.annotate(
@@ -74,14 +74,14 @@ if __name__ == "__main__":
model = models.get(Models.YOLO_NAS_L, pretrained_weights="coco")
byte_track = sv.ByteTrack(
frame_rate=video_info.fps, track_thresh=args.confidence_threshold
frame_rate=video_info.fps, track_activation_threshold=args.confidence_threshold
)
thickness = sv.calculate_optimal_line_thickness(
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
box_annotator = sv.BoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
text_thickness=thickness,
@@ -132,7 +132,7 @@ if __name__ == "__main__":
annotated_frame = trace_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = label_annotator.annotate(
+7 -7
View File
@@ -103,7 +103,7 @@ python scripts/draw_zones.py \
```bash
python scripts/draw_zones.py \
--source_path "data/traffic/video.mp4" \
--zone_configuration_path "data/traffic/custom_config.json"
--zone_configuration_path "data/traffic/config.json"
```
https://github.com/roboflow/supervision/assets/26109316/9d514c9e-2a61-418b-ae49-6ac1ad6ae5ac
@@ -157,7 +157,7 @@ Script to run object detection on a video stream using the Roboflow Inference mo
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
python inference_stream_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
@@ -167,7 +167,7 @@ python inference_file_example.py \
```
```bash
python inference_file_example.py \
python inference_stream_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
@@ -192,7 +192,7 @@ Script to run object detection on a video file using the Ultralytics YOLOv8 mode
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
python ultralytics_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--weights "yolov8x.pt" \
@@ -203,7 +203,7 @@ python inference_file_example.py \
```
```bash
python inference_file_example.py \
python ultralytics_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--weights "yolov8x.pt" \
@@ -226,7 +226,7 @@ Script to run object detection on a video stream using the Ultralytics YOLOv8 mo
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
python ultralytics_stream_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
@@ -237,7 +237,7 @@ python inference_file_example.py \
```
```bash
python inference_file_example.py \
python ultralytics_stream_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
+1 -1
View File
@@ -1,5 +1,5 @@
opencv-python
supervision>=0.20.0
ultralytics
inference<=0.9.21
inference==0.9.17
pytube
+1 -1
View File
@@ -22,7 +22,7 @@ class FPSBasedTimer:
"""Initializes the FPSBasedTimer with the specified frames per second rate.
Args:
fps (int, optional): The frame rate of the video stream. Defaults to 30.
fps (int): The frame rate of the video stream. Defaults to 30.
"""
self.fps = fps
self.frame_id = 0
+1 -1
View File
@@ -1,4 +1,4 @@
inference
inference==0.9.17
supervision==0.19.0
tqdm
ultralytics
@@ -93,7 +93,7 @@ class VideoProcessor:
self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER])
self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER])
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
@@ -134,9 +134,7 @@ class VideoProcessor:
labels = [f"#{tracker_id}" for tracker_id in detections.tracker_id]
annotated_frame = self.trace_annotator.annotate(annotated_frame, detections)
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.box_annotator.annotate(annotated_frame, detections)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
+1 -1
View File
@@ -1,5 +1,5 @@
gdown
inference<=0.9.21
inference==0.9.17
supervision>=0.20.0
tqdm
ultralytics
@@ -90,7 +90,7 @@ class VideoProcessor:
self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER])
self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER])
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
@@ -131,9 +131,7 @@ class VideoProcessor:
labels = [f"#{tracker_id}" for tracker_id in detections.tracker_id]
annotated_frame = self.trace_annotator.annotate(annotated_frame, detections)
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.box_annotator.annotate(annotated_frame, detections)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
+60 -50
View File
@@ -33,57 +33,61 @@ extra_css:
- stylesheets/extra.css
- stylesheets/cookbooks-card.css
nav:
- Home: index.md
- How to:
- Supervision: index.md
- Learn:
- Detect and Annotate: how_to/detect_and_annotate.md
- Save Detections: how_to/save_detections.md
- Filter Detections: how_to/filter_detections.md
- Detect Small Objects: how_to/detect_small_objects.md
- Track Objects: how_to/track_objects.md
- Track Objects on Video: how_to/track_objects.md
- Process Datasets: how_to/process_datasets.md
- API:
- Detection and Segmentation:
- Core: detection/core.md
- Annotators: detection/annotators.md
- Metrics: detection/metrics.md
- Utils: detection/utils.md
- Keypoint Detection:
- Core: keypoint/core.md
- Annotators: keypoint/annotators.md
- Classification:
- Core: classification/core.md
- Tools:
- Line Zone: detection/tools/line_zone.md
- Polygon Zone: detection/tools/polygon_zone.md
- Inference Slicer: detection/tools/inference_slicer.md
- Detection Smoother: detection/tools/smoother.md
- Save Detections: detection/tools/save_detections.md
- Trackers: trackers.md
- Datasets: datasets.md
- Utils:
- Video: utils/video.md
- Image: utils/image.md
- Iterables: utils/iterables.md
- Notebook: utils/notebook.md
- File: utils/file.md
- Draw: utils/draw.md
- Geometry: utils/geometry.md
- Assets: assets.md
- Reference - Code API:
- Detection and Segmentation:
- Core: detection/core.md
- Annotators: detection/annotators.md
- Double Detection Filter: detection/double_detection_filter.md
- Utils: detection/utils.md
- Keypoint Detection:
- Core: keypoint/core.md
- Annotators: keypoint/annotators.md
- Classification:
- Core: classification/core.md
- Tools:
- Line Zone: detection/tools/line_zone.md
- Polygon Zone: detection/tools/polygon_zone.md
- Inference Slicer: detection/tools/inference_slicer.md
- Detection Smoother: detection/tools/smoother.md
- Save Detections: detection/tools/save_detections.md
- Trackers: trackers.md
- Datasets:
- Core: datasets/core.md
- Utils: datasets/utils.md
- Metrics:
- mAP: metrics/mean_average_precision.md
- Legacy Metrics: detection/metrics.md
- Utils:
- Video: utils/video.md
- Image: utils/image.md
- Iterables: utils/iterables.md
- Notebook: utils/notebook.md
- File: utils/file.md
- Draw: utils/draw.md
- Geometry: utils/geometry.md
- Assets: assets.md
- Cookbooks: cookbooks.md
- Cheatsheet: https://roboflow.github.io/cheatsheet-supervision/
- Contribute:
- Contributing: contributing.md
- Code of Conduct: code_of_conduct.md
- License: license.md
- Changelog:
- Changelog: changelog.md
- Deprecated: deprecated.md
- Contributing: contributing.md
- Code of Conduct: code_of_conduct.md
- License: license.md
- Release Notes:
- Changelog: changelog.md
- Deprecated: deprecated.md
theme:
name: 'material'
name: "material"
icon:
edit: material/pencil
logo: assets/supervision-lenny.png
@@ -100,19 +104,18 @@ theme:
palette:
# Palette for light mode
- scheme: default
primary: 'custom'
primary: "custom"
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- scheme: slate
primary: 'custom'
primary: "custom"
toggle:
icon: material/brightness-4
name: Switch to light mode
font:
text: Roboto
code: Roboto Mono
@@ -123,10 +126,10 @@ theme:
plugins:
- search
- mkdocs-jupyter:
kernel_name: python3
execute: false
include_source: True
include_requirejs: true
kernel_name: python3
execute: false
include_source: True
include_requirejs: true
- mkdocstrings:
default_handler: python
handlers:
@@ -150,8 +153,6 @@ plugins:
- git-revision-date-localized:
enable_creation_date: true
markdown_extensions:
- admonition
- pymdownx.details
@@ -175,3 +176,12 @@ extra_javascript:
- "javascripts/init_kapa_widget.js"
- "javascripts/cookbooks-card.js"
- "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.8/purify.min.js"
# Messages shown during document build
# Reference: https://www.mkdocs.org/user-guide/configuration/#validation
# Values: [warn, info, ignore]
validation:
nav:
absolute_links: ignore
links:
absolute_links: ignore
Generated
+1456 -1210
View File
File diff suppressed because it is too large Load Diff
+94 -24
View File
@@ -1,18 +1,26 @@
[tool.poetry]
name = "supervision"
version = "0.21.0rc2"
version = "0.23.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>"]
readme = "README.md"
license = "MIT"
packages = [{include = "supervision"}]
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"
keywords = ["machine-learning", "deep-learning", "vision", "ML", "DL", "AI", "Roboflow"]
keywords = [
"machine-learning",
"deep-learning",
"vision",
"ML",
"DL",
"AI",
"Roboflow",
]
classifiers=[
classifiers = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Education',
@@ -27,33 +35,40 @@ classifiers=[
'Typing :: Typed',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS'
'Operating System :: MacOS',
]
[tool.poetry.dependencies]
python = "^3.8"
numpy = ">=1.21.2"
numpy = [
{ version = ">=1.21.2,<1.23.3", python = "<=3.10" },
{ version = ">=1.23.3", python = ">3.10" },
]
scipy = [
{ version = "1.10.0", python = "<3.9" },
{ version = "^1.10.0", python = ">=3.9" }
{ version = "^1.10.0", python = ">=3.9" },
]
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.31.0", optional = true }
tqdm = { version = ">=4.62.3,<=4.66.2", optional = true }
pillow = ">=9.4"
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 }
[tool.poetry.extras]
desktop = ["opencv-python"]
assets = ["requests","tqdm"]
assets = ["requests", "tqdm"]
metrics = ["pandas", "pandas-stubs"]
[tool.poetry.group.dev.dependencies]
twine = ">=4.0.2,<6.0.0"
twine = "^5.1.1"
pytest = ">=7.2.2,<9.0.0"
wheel = ">=0.40,<0.44"
wheel = ">=0.40,<0.45"
build = ">=0.10,<1.3"
ruff = ">=0.1.0"
mypy = "^1.4.1"
@@ -63,11 +78,14 @@ notebook = ">=6.5.3,<8.0.0"
ipywidgets = "^8.1.1"
jupytext = "^1.16.1"
nbconvert = "^7.14.2"
docutils = [
{ version = "^0.20.1", python = "<3.9" },
{ version = "^0.21.1", python = ">=3.9" },
]
[tool.poetry.group.docs.dependencies]
mkdocs-material = {extras = ["imaging"], version = "^9.5.5"}
mkdocstrings = {extras = ["python"], version = ">=0.20,<0.26"}
mkdocs-material = { extras = ["imaging"], version = "^9.5.5" }
mkdocstrings = { extras = ["python"], version = "^0.25.2" }
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
@@ -75,11 +93,15 @@ mkdocs-jupyter = "^0.24.3"
mkdocs-git-committers-plugin-2 = "^2.2.3"
mkdocs-git-revision-date-localized-plugin = "^1.2.4"
[tool.poetry.group.typecheck]
optional = true
[tool.poetry.group.typecheck.dependencies]
types-pyyaml = "^6.0.12.20240808"
types-cffi = "^1.16.0.20240331"
types-requests = "^2.32.0.20240712"
types-tqdm = "^4.66.0.20240417"
[tool.isort]
line_length = 88
profile = "black"
[tool.bandit]
target = ["test", "supervision"]
@@ -89,7 +111,6 @@ tests = ["B201", "B301", "B318", "B314", "B303", "B413", "B412", "B410"]
check = true
imports = ["cv2", "supervision"]
[tool.black]
target-version = ["py38"]
line-length = 88
@@ -145,10 +166,55 @@ indent-width = 4
[tool.ruff.lint]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
select = ["E", "F", "I", "A", "Q", "W"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
fixable = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"I",
"N",
"Q",
"S",
"T",
"W",
"ANN",
"ARG",
"BLE",
"COM",
"DJ",
"DTZ",
"EM",
"ERA",
"EXE",
"FBT",
"ICN",
"INP",
"ISC",
"NPY",
"PD",
"PGH",
"PIE",
"PL",
"PT",
"PTH",
"PYI",
"RET",
"RSE",
"RUF",
"SIM",
"SLF",
"TCH",
"TID",
"TRY",
"UP",
"YTT",
]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
@@ -163,12 +229,16 @@ docstring-quotes = "double"
convention = "google"
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402","F401"]
"__init__.py" = ["E402", "F401"]
[tool.ruff.lint.mccabe]
# Flag errors (`C901`) whenever the complexity level exceeds 5.
max-complexity = 20
[tool.ruff.lint.isort]
order-by-type = true
no-sections = false
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
@@ -186,7 +256,7 @@ line-ending = "auto"
include-package-data = false
[tool.setuptools.packages.find]
exclude = ["docs*", "test*","examples*"]
exclude = ["docs*", "test*", "examples*"]
[build-system]
requires = ["poetry-core"]
+24
View File
@@ -0,0 +1,24 @@
# Release Process
This doc outlines how supervision is released into production.
It assumes you already have the code changes, as well as a draft of the release notes.
1. Make sure you have all required changes were merged into `develop`.
2. Create and merge a PR, merging `develop` into `main`, containing:
- A commit that updates the project version in `pyproject.toml`.
- All changes made during the release.
3. Tag the commit with the new supervision version.
- make sure to pull from `main` !
- Verify that the latest merge commits exists. `git log`.
- Run `git tag x.y.z`, with your version
- Check with `git log`.
- Run `git push origin --tags`
- Upon pushing the 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.
6. Create a release on GitHub.
- Go to releases
- Assign the release notes to the tag created in step 3.
- Publish the release.
+24 -4
View File
@@ -7,8 +7,10 @@ except importlib_metadata.PackageNotFoundError:
__version__ = "development"
from supervision.annotators.core import (
BackgroundOverlayAnnotator,
BlurAnnotator,
BoundingBoxAnnotator,
BoxAnnotator,
BoxCornerAnnotator,
CircleAnnotator,
ColorAnnotator,
@@ -17,12 +19,14 @@ from supervision.annotators.core import (
EllipseAnnotator,
HaloAnnotator,
HeatMapAnnotator,
IconAnnotator,
LabelAnnotator,
MaskAnnotator,
OrientedBoxAnnotator,
PercentageBarAnnotator,
PixelateAnnotator,
PolygonAnnotator,
RichLabelAnnotator,
RoundBoxAnnotator,
TraceAnnotator,
TriangleAnnotator,
@@ -34,9 +38,16 @@ from supervision.dataset.core import (
ClassificationDataset,
DetectionDataset,
)
from supervision.detection.annotate import BoxAnnotator
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.lmm import LMM
from supervision.detection.overlap_filter import (
OverlapFilter,
box_non_max_merge,
box_non_max_suppression,
mask_non_max_suppression,
)
from supervision.detection.tools.csv_sink import CSVSink
from supervision.detection.tools.inference_slicer import InferenceSlicer
from supervision.detection.tools.json_sink import JSONSink
@@ -44,17 +55,22 @@ from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnn
from supervision.detection.tools.smoother import DetectionsSmoother
from supervision.detection.utils import (
box_iou_batch,
box_non_max_suppression,
calculate_masks_centroids,
clip_boxes,
contains_holes,
contains_multiple_segments,
filter_polygons_by_area,
mask_iou_batch,
mask_non_max_suppression,
mask_to_polygons,
mask_to_xyxy,
move_boxes,
move_masks,
pad_boxes,
polygon_to_mask,
polygon_to_xyxy,
scale_boxes,
xcycwh_to_xyxy,
xywh_to_xyxy,
)
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import (
@@ -69,7 +85,11 @@ from supervision.draw.utils import (
)
from supervision.geometry.core import Point, Position, Rect
from supervision.geometry.utils import get_polygon_center
from supervision.keypoint.annotators import EdgeAnnotator, VertexAnnotator
from supervision.keypoint.annotators import (
EdgeAnnotator,
VertexAnnotator,
VertexLabelAnnotator,
)
from supervision.keypoint.core import KeyPoints
from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision
from supervision.tracker.byte_tracker.core import ByteTrack
File diff suppressed because it is too large Load Diff
+58 -5
View File
@@ -1,5 +1,5 @@
from enum import Enum
from typing import Optional, Union
from typing import Optional, Tuple, Union
import numpy as np
@@ -34,14 +34,14 @@ def resolve_color_idx(
) -> int:
if detection_idx >= len(detections):
raise ValueError(
f"Detection index {detection_idx}"
f"Detection index {detection_idx} "
f"is out of bounds for detections of length {len(detections)}"
)
if isinstance(color_lookup, np.ndarray):
if len(color_lookup) != len(detections):
raise ValueError(
f"Length of color lookup {len(color_lookup)}"
f"Length of color lookup {len(color_lookup)} "
f"does not match length of detections {len(detections)}"
)
return color_lookup[detection_idx]
@@ -50,19 +50,72 @@ def resolve_color_idx(
elif color_lookup == ColorLookup.CLASS:
if detections.class_id is None:
raise ValueError(
"Could not resolve color by class because"
"Could not resolve color by class because "
"Detections do not have class_id"
)
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"
"Could not resolve color by track because "
"Detections do not have tracker_id"
)
return detections.tracker_id[detection_idx]
def resolve_text_background_xyxy(
center_coordinates: Tuple[int, int],
text_wh: Tuple[int, int],
position: Position,
) -> Tuple[int, int, int, int]:
center_x, center_y = center_coordinates
text_w, text_h = text_wh
if position == Position.TOP_LEFT:
return center_x, center_y - text_h, center_x + text_w, center_y
elif position == Position.TOP_RIGHT:
return center_x - text_w, center_y - text_h, center_x, center_y
elif position == Position.TOP_CENTER:
return (
center_x - text_w // 2,
center_y - text_h,
center_x + text_w // 2,
center_y,
)
elif position == Position.CENTER or position == Position.CENTER_OF_MASS:
return (
center_x - text_w // 2,
center_y - text_h // 2,
center_x + text_w // 2,
center_y + text_h // 2,
)
elif position == Position.BOTTOM_LEFT:
return center_x, center_y, center_x + text_w, center_y + text_h
elif position == Position.BOTTOM_RIGHT:
return center_x - text_w, center_y, center_x, center_y + text_h
elif position == Position.BOTTOM_CENTER:
return (
center_x - text_w // 2,
center_y,
center_x + text_w // 2,
center_y + text_h,
)
elif position == Position.CENTER_LEFT:
return (
center_x - text_w,
center_y - text_h // 2,
center_x,
center_y + text_h // 2,
)
elif position == Position.CENTER_RIGHT:
return (
center_x,
center_y - text_h // 2,
center_x + text_w,
center_y + text_h // 2,
)
def get_color_by_index(color: Union[Color, ColorPalette], idx: int) -> Color:
if isinstance(color, ColorPalette):
return color.by_idx(idx)
+405 -161
View File
@@ -3,8 +3,9 @@ from __future__ import annotations
import os
from abc import ABC, abstractmethod
from dataclasses import dataclass
from itertools import chain
from pathlib import Path
from typing import Dict, Iterator, List, Optional, Tuple
from typing import Dict, Iterator, List, Optional, Tuple, Union
import cv2
import numpy as np
@@ -31,9 +32,10 @@ from supervision.dataset.utils import (
train_test_split,
)
from supervision.detection.core import Detections
from supervision.utils.internal import deprecated, warn_deprecated
from supervision.utils.iterables import find_duplicates
@dataclass
class BaseDataset(ABC):
@abstractmethod
def __len__(self) -> int:
@@ -41,35 +43,103 @@ class BaseDataset(ABC):
@abstractmethod
def split(
self, split_ratio=0.8, random_state=None, shuffle: bool = True
self,
split_ratio: float = 0.8,
random_state: Optional[int] = None,
shuffle: bool = True,
) -> Tuple[BaseDataset, BaseDataset]:
pass
@dataclass
class DetectionDataset(BaseDataset):
"""
Dataclass containing information about object detection dataset.
Contains information about a detection dataset. Handles lazy image loading
and annotation retrieval, dataset splitting, conversions into multiple
formats.
Attributes:
classes (List[str]): List containing dataset class names.
images (Dict[str, np.ndarray]): Dictionary mapping image name to image.
images (Union[List[str], Dict[str, np.ndarray]]):
Accepts a list of image paths, or dictionaries of loaded cv2 images
with paths as keys. If you pass a list of paths, the dataset will
lazily load images on demand, which is much more memory-efficient.
annotations (Dict[str, Detections]): Dictionary mapping
image name to annotations.
image path to annotations. The dictionary keys match
match the keys in `images` or entries in the list of
image paths.
"""
classes: List[str]
images: Dict[str, np.ndarray]
annotations: Dict[str, Detections]
def __init__(
self,
classes: List[str],
images: Union[List[str], Dict[str, np.ndarray]],
annotations: Dict[str, Detections],
) -> None:
self.classes = classes
if set(images) != set(annotations):
raise ValueError(
"The keys of the images and annotations dictionaries must match."
)
self.annotations = annotations
# Eliminate duplicates while preserving order
self.image_paths = list(dict.fromkeys(images))
self._images_in_memory: Dict[str, np.ndarray] = {}
if isinstance(images, dict):
self._images_in_memory = images
warn_deprecated(
"Passing a `Dict[str, np.ndarray]` into `DetectionDataset` is "
"deprecated and will be removed in `supervision-0.26.0`. Use "
"a list of paths `List[str]` instead."
)
# TODO: when supervision-0.26.0 is released, and Dict[str, np.ndarray]
# for images is no longer supported, also simplify the rest of
# the code. E.g. list(images) is no longer needed, and merge can
# be simplified.
@property
@deprecated(
"`DetectionDataset.images` property is deprecated and will be removed in "
"`supervision-0.26.0`. Iterate with `for path, image, annotation in dataset:` "
"instead."
)
def images(self) -> Dict[str, np.ndarray]:
"""
Load all images to memory and return them as a dictionary.
!!! warning
Only use this when you need all images at once.
It is much more memory-efficient to initialize dataset with
image paths and use `for path, image, annotation in dataset:`.
"""
if self._images_in_memory:
return self._images_in_memory
images = {image_path: cv2.imread(image_path) for image_path in self.image_paths}
return images
def _get_image(self, image_path: str) -> np.ndarray:
"""Assumes that image is in dataset"""
if self._images_in_memory:
return self._images_in_memory[image_path]
return cv2.imread(image_path)
def __len__(self) -> int:
"""
Return the number of images in the dataset.
return len(self._images_in_memory) or len(self.image_paths)
Returns:
int: The number of images.
def __getitem__(self, i: int) -> Tuple[str, np.ndarray, Detections]:
"""
return len(self.images)
Returns:
Tuple[str, np.ndarray, Detections]: The image path, image data,
and its corresponding annotation at index i.
"""
image_path = self.image_paths[i]
image = self._get_image(image_path)
annotation = self.annotations[image_path]
return image_path, image, annotation
def __iter__(self) -> Iterator[Tuple[str, np.ndarray, Detections]]:
"""
@@ -77,77 +147,190 @@ class DetectionDataset(BaseDataset):
Yields:
Iterator[Tuple[str, np.ndarray, Detections]]:
An iterator that yields tuples containing the image name,
An iterator that yields tuples containing the image path,
the image data, and its corresponding annotation.
"""
for image_name, image in self.images.items():
yield image_name, image, self.annotations.get(image_name, None)
for i in range(len(self)):
image_path, image, annotation = self[i]
yield image_path, image, annotation
def __eq__(self, other):
def __eq__(self, other) -> bool:
if not isinstance(other, DetectionDataset):
return False
if set(self.classes) != set(other.classes):
return False
for key in self.images:
if not np.array_equal(self.images[key], other.images[key]):
return False
if not self.annotations[key] == other.annotations[key]:
if self.image_paths != other.image_paths:
return False
if self._images_in_memory or other._images_in_memory:
if not np.array_equal(
list(self._images_in_memory.values()),
list(other._images_in_memory.values()),
):
return False
if self.annotations != other.annotations:
return False
return True
def split(
self, split_ratio=0.8, random_state=None, shuffle: bool = True
self,
split_ratio: float = 0.8,
random_state: Optional[int] = None,
shuffle: bool = True,
) -> Tuple[DetectionDataset, DetectionDataset]:
"""
Splits the dataset into two parts (training and testing)
using the provided split_ratio.
Args:
split_ratio (float, optional): The ratio of the training
split_ratio (float): The ratio of the training
set to the entire dataset.
random_state (int, optional): The seed for the random number generator.
random_state (Optional[int]): The seed for the random number generator.
This is used for reproducibility.
shuffle (bool, optional): Whether to shuffle the data before splitting.
shuffle (bool): Whether to shuffle the data before splitting.
Returns:
Tuple[DetectionDataset, DetectionDataset]: A tuple containing
the training and testing datasets.
Example:
Examples:
```python
import supervision as sv
ds = sv.DetectionDataset(...)
train_ds, test_ds = ds.split(split_ratio=0.7,
random_state=42, shuffle=True)
train_ds, test_ds = ds.split(split_ratio=0.7, random_state=42, shuffle=True)
len(train_ds), len(test_ds)
# (700, 300)
```
"""
image_names = list(self.images.keys())
train_names, test_names = train_test_split(
data=image_names,
train_paths, test_paths = train_test_split(
data=self.image_paths,
train_ratio=split_ratio,
random_state=random_state,
shuffle=shuffle,
)
train_input: Union[List[str], Dict[str, np.ndarray]]
test_input: Union[List[str], Dict[str, np.ndarray]]
if self._images_in_memory:
train_input = {path: self._images_in_memory[path] for path in train_paths}
test_input = {path: self._images_in_memory[path] for path in test_paths}
else:
train_input = train_paths
test_input = test_paths
train_annotations = {path: self.annotations[path] for path in train_paths}
test_annotations = {path: self.annotations[path] for path in test_paths}
train_dataset = DetectionDataset(
classes=self.classes,
images={name: self.images[name] for name in train_names},
annotations={name: self.annotations[name] for name in train_names},
images=train_input,
annotations=train_annotations,
)
test_dataset = DetectionDataset(
classes=self.classes,
images={name: self.images[name] for name in test_names},
annotations={name: self.annotations[name] for name in test_names},
images=test_input,
annotations=test_annotations,
)
return train_dataset, test_dataset
@classmethod
def merge(cls, dataset_list: List[DetectionDataset]) -> DetectionDataset:
"""
Merge a list of `DetectionDataset` objects into a single
`DetectionDataset` object.
This method takes a list of `DetectionDataset` objects and combines
their respective fields (`classes`, `images`,
`annotations`) into a single `DetectionDataset` object.
Args:
dataset_list (List[DetectionDataset]): A list of `DetectionDataset`
objects to merge.
Returns:
(DetectionDataset): A single `DetectionDataset` object containing
the merged data from the input list.
Examples:
```python
import supervision as sv
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_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
"""
def is_in_memory(dataset: DetectionDataset) -> bool:
return len(dataset._images_in_memory) > 0 or len(dataset.image_paths) == 0
def is_lazy(dataset: DetectionDataset) -> bool:
return len(dataset._images_in_memory) == 0
all_in_memory = all([is_in_memory(dataset) for dataset in dataset_list])
all_lazy = all([is_lazy(dataset) for dataset in dataset_list])
if not all_in_memory and not all_lazy:
raise ValueError(
"Merging lazy and in-memory DetectionDatasets is not supported."
)
images_in_memory = {}
for dataset in dataset_list:
images_in_memory.update(dataset._images_in_memory)
image_paths = list(
chain.from_iterable(dataset.image_paths for dataset in dataset_list)
)
image_paths_unique = list(dict.fromkeys(image_paths))
if len(image_paths) != len(image_paths_unique):
duplicates = find_duplicates(image_paths)
raise ValueError(
f"Image paths {duplicates} are not unique across datasets."
)
image_paths = image_paths_unique
classes = merge_class_lists(
class_lists=[dataset.classes for dataset in dataset_list]
)
annotations = {}
for dataset in dataset_list:
annotations.update(dataset.annotations)
for dataset in dataset_list:
class_index_mapping = build_class_index_mapping(
source_classes=dataset.classes, target_classes=classes
)
for image_path in dataset.image_paths:
annotations[image_path] = map_detections_class_id(
source_to_target_mapping=class_index_mapping,
detections=annotations[image_path],
)
return cls(
classes=classes,
images=images_in_memory or image_paths,
annotations=annotations,
)
def as_pascal_voc(
self,
images_directory_path: Optional[str] = None,
@@ -181,25 +364,22 @@ class DetectionDataset(BaseDataset):
"""
if images_directory_path:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
dataset=self,
images_directory_path=images_directory_path,
)
if annotations_directory_path:
Path(annotations_directory_path).mkdir(parents=True, exist_ok=True)
for image_path, image in self.images.items():
detections = self.annotations[image_path]
if annotations_directory_path:
for image_path, image, annotations in self:
annotation_name = Path(image_path).stem
annotations_path = os.path.join(
annotations_directory_path, f"{annotation_name}.xml"
)
image_name = Path(image_path).name
pascal_voc_xml = detections_to_pascal_voc(
detections=detections,
detections=annotations,
classes=self.classes,
filename=image_name,
image_shape=image.shape,
image_shape=image.shape, # type: ignore
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
@@ -222,14 +402,14 @@ class DetectionDataset(BaseDataset):
images_directory_path (str): Path to the directory containing the images.
annotations_directory_path (str): Path to the directory
containing the PASCAL VOC XML annotations.
force_masks (bool, optional): If True, forces masks to
force_masks (bool): If True, forces masks to
be loaded for all annotations, regardless of whether they are present.
Returns:
DetectionDataset: A DetectionDataset instance containing
the loaded images and annotations.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
@@ -252,13 +432,15 @@ class DetectionDataset(BaseDataset):
```
"""
classes, images, annotations = load_pascal_voc_annotations(
classes, image_paths, annotations = load_pascal_voc_annotations(
images_directory_path=images_directory_path,
annotations_directory_path=annotations_directory_path,
force_masks=force_masks,
)
return DetectionDataset(classes=classes, images=images, annotations=annotations)
return DetectionDataset(
classes=classes, images=image_paths, annotations=annotations
)
@classmethod
def from_yolo(
@@ -267,6 +449,7 @@ class DetectionDataset(BaseDataset):
annotations_directory_path: str,
data_yaml_path: str,
force_masks: bool = False,
is_obb: bool = False,
) -> DetectionDataset:
"""
Creates a Dataset instance from YOLO formatted data.
@@ -278,15 +461,18 @@ class DetectionDataset(BaseDataset):
containing the YOLO annotation files.
data_yaml_path (str): The path to the data
YAML file containing class information.
force_masks (bool, optional): If True, forces
force_masks (bool): If True, forces
masks to be loaded for all annotations,
regardless of whether they are present.
is_obb (bool): If True, loads the annotations in OBB format.
OBB annotations are defined as `[class_id, x, y, x, y, x, y, x, y]`,
where pairs of [x, y] are box corners.
Returns:
DetectionDataset: A DetectionDataset instance
containing the loaded images and annotations.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
@@ -308,13 +494,16 @@ class DetectionDataset(BaseDataset):
# ['dog', 'person']
```
"""
classes, images, annotations = load_yolo_annotations(
classes, image_paths, annotations = load_yolo_annotations(
images_directory_path=images_directory_path,
annotations_directory_path=annotations_directory_path,
data_yaml_path=data_yaml_path,
force_masks=force_masks,
is_obb=is_obb,
)
return DetectionDataset(
classes=classes, images=image_paths, annotations=annotations
)
return DetectionDataset(classes=classes, images=images, annotations=annotations)
def as_yolo(
self,
@@ -355,13 +544,12 @@ class DetectionDataset(BaseDataset):
"""
if images_directory_path is not None:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
dataset=self, images_directory_path=images_directory_path
)
if annotations_directory_path is not None:
save_yolo_annotations(
dataset=self,
annotations_directory_path=annotations_directory_path,
images=self.images,
annotations=self.annotations,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
@@ -383,7 +571,7 @@ class DetectionDataset(BaseDataset):
images_directory_path (str): The path to the
directory containing the images.
annotations_path (str): The path to the json annotation files.
force_masks (bool, optional): If True,
force_masks (bool): If True,
forces masks to be loaded for all annotations,
regardless of whether they are present.
@@ -391,7 +579,7 @@ class DetectionDataset(BaseDataset):
DetectionDataset: A DetectionDataset instance containing
the loaded images and annotations.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
@@ -431,6 +619,20 @@ class DetectionDataset(BaseDataset):
Exports the dataset to COCO format. This method saves the
images and their corresponding annotations in COCO format.
!!! tip
The format of the mask is determined automatically based on its structure:
- If a mask contains multiple disconnected components or holes, it will be
saved using the Run-Length Encoding (RLE) format for efficient storage and
processing.
- If a mask consists of a single, contiguous region without any holes, it
will be encoded as a polygon, preserving the outline of the object.
This automatic selection ensures that the masks are stored in the most
appropriate and space-efficient format, complying with COCO dataset
standards.
Args:
images_directory_path (Optional[str]): The path to the directory
where the images should be saved.
@@ -451,151 +653,195 @@ class DetectionDataset(BaseDataset):
"""
if images_directory_path is not None:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
dataset=self, images_directory_path=images_directory_path
)
if annotations_path is not None:
save_coco_annotations(
dataset=self,
annotation_path=annotations_path,
images=self.images,
annotations=self.annotations,
classes=self.classes,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)
@classmethod
def merge(cls, dataset_list: List[DetectionDataset]) -> DetectionDataset:
"""
Merge a list of `DetectionDataset` objects into a single
`DetectionDataset` object.
This method takes a list of `DetectionDataset` objects and combines
their respective fields (`classes`, `images`,
`annotations`) into a single `DetectionDataset` object.
Args:
dataset_list (List[DetectionDataset]): A list of `DetectionDataset`
objects to merge.
Returns:
(DetectionDataset): A single `DetectionDataset` object containing
the merged data from the input list.
Example:
```python
import supervision as sv
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_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
"""
merged_images, merged_annotations = {}, {}
class_lists = [dataset.classes for dataset in dataset_list]
merged_classes = merge_class_lists(class_lists=class_lists)
for dataset in dataset_list:
class_index_mapping = build_class_index_mapping(
source_classes=dataset.classes, target_classes=merged_classes
)
for image_name, image, detections in dataset:
if image_name in merged_annotations:
raise ValueError(
f"Image name {image_name} is not unique across datasets."
)
merged_images[image_name] = image
merged_annotations[image_name] = map_detections_class_id(
source_to_target_mapping=class_index_mapping,
detections=detections,
)
return cls(
classes=merged_classes, images=merged_images, annotations=merged_annotations
)
@dataclass
class ClassificationDataset(BaseDataset):
"""
Dataclass containing information about a classification dataset.
Contains information about a classification dataset, handles lazy image
loading, dataset splitting.
Attributes:
classes (List[str]): List containing dataset class names.
images (Dict[str, np.ndarray]): Dictionary mapping image name to image.
annotations (Dict[str, Detections]): Dictionary mapping
images (Union[List[str], Dict[str, np.ndarray]]):
List of image paths or dictionary mapping image name to image data.
annotations (Dict[str, Classifications]): Dictionary mapping
image name to annotations.
"""
classes: List[str]
images: Dict[str, np.ndarray]
annotations: Dict[str, Classifications]
def __init__(
self,
classes: List[str],
images: Union[List[str], Dict[str, np.ndarray]],
annotations: Dict[str, Classifications],
) -> None:
self.classes = classes
if set(images) != set(annotations):
raise ValueError(
"The keys of the images and annotations dictionaries must match."
)
self.annotations = annotations
# Eliminate duplicates while preserving order
self.image_paths = list(dict.fromkeys(images))
self._images_in_memory: Dict[str, np.ndarray] = {}
if isinstance(images, dict):
self._images_in_memory = images
warn_deprecated(
"Passing a `Dict[str, np.ndarray]` into `ClassificationDataset` is "
"deprecated and will be removed in a future release. Use "
"a list of paths `List[str]` instead."
)
@property
@deprecated(
"`DetectionDataset.images` property is deprecated and will be removed in "
"`supervision-0.26.0`. Iterate with `for path, image, annotation in dataset:` "
"instead."
)
def images(self) -> Dict[str, np.ndarray]:
"""
Load all images to memory and return them as a dictionary.
!!! warning
Only use this when you need all images at once.
It is much more memory-efficient to initialize dataset with
image paths and use `for path, image, annotation in dataset:`.
"""
if self._images_in_memory:
return self._images_in_memory
images = {image_path: cv2.imread(image_path) for image_path in self.image_paths}
return images
def _get_image(self, image_path: str) -> np.ndarray:
"""Assumes that image is in dataset"""
if self._images_in_memory:
return self._images_in_memory[image_path]
return cv2.imread(image_path)
def __len__(self) -> int:
return len(self.images)
return len(self._images_in_memory) or len(self.image_paths)
def __getitem__(self, i: int) -> Tuple[str, np.ndarray, Classifications]:
"""
Returns:
Tuple[str, np.ndarray, Classifications]: The image path, image data,
and its corresponding annotation at index i.
"""
image_path = self.image_paths[i]
image = self._get_image(image_path)
annotation = self.annotations[image_path]
return image_path, image, annotation
def __iter__(self) -> Iterator[Tuple[str, np.ndarray, Classifications]]:
"""
Iterate over the images and annotations in the dataset.
Yields:
Iterator[Tuple[str, np.ndarray, Detections]]:
An iterator that yields tuples containing the image path,
the image data, and its corresponding annotation.
"""
for i in range(len(self)):
image_path, image, annotation = self[i]
yield image_path, image, annotation
def __eq__(self, other) -> bool:
if not isinstance(other, ClassificationDataset):
return False
if set(self.classes) != set(other.classes):
return False
if self.image_paths != other.image_paths:
return False
if self._images_in_memory or other._images_in_memory:
if not np.array_equal(
list(self._images_in_memory.values()),
list(other._images_in_memory.values()),
):
return False
if self.annotations != other.annotations:
return False
return True
def split(
self, split_ratio=0.8, random_state=None, shuffle: bool = True
self,
split_ratio: float = 0.8,
random_state: Optional[int] = None,
shuffle: bool = True,
) -> Tuple[ClassificationDataset, ClassificationDataset]:
"""
Splits the dataset into two parts (training and testing)
using the provided split_ratio.
Args:
split_ratio (float, optional): The ratio of the training
split_ratio (float): The ratio of the training
set to the entire dataset.
random_state (int, optional): The seed for the
random_state (Optional[int]): The seed for the
random number generator. This is used for reproducibility.
shuffle (bool, optional): Whether to shuffle the data before splitting.
shuffle (bool): Whether to shuffle the data before splitting.
Returns:
Tuple[ClassificationDataset, ClassificationDataset]: A tuple containing
the training and testing datasets.
Example:
Examples:
```python
import supervision as sv
cd = sv.ClassificationDataset(...)
train_cd,test_cd = cd.split(split_ratio=0.7,
random_state=42,shuffle=True)
train_cd,test_cd = cd.split(split_ratio=0.7, random_state=42,shuffle=True)
len(train_cd), len(test_cd)
# (700, 300)
```
"""
image_names = list(self.images.keys())
train_names, test_names = train_test_split(
data=image_names,
train_paths, test_paths = train_test_split(
data=self.image_paths,
train_ratio=split_ratio,
random_state=random_state,
shuffle=shuffle,
)
train_input: Union[List[str], Dict[str, np.ndarray]]
test_input: Union[List[str], Dict[str, np.ndarray]]
if self._images_in_memory:
train_input = {path: self._images_in_memory[path] for path in train_paths}
test_input = {path: self._images_in_memory[path] for path in test_paths}
else:
train_input = train_paths
test_input = test_paths
train_annotations = {path: self.annotations[path] for path in train_paths}
test_annotations = {path: self.annotations[path] for path in test_paths}
train_dataset = ClassificationDataset(
classes=self.classes,
images={name: self.images[name] for name in train_names},
annotations={name: self.annotations[name] for name in train_names},
images=train_input,
annotations=train_annotations,
)
test_dataset = ClassificationDataset(
classes=self.classes,
images={name: self.images[name] for name in test_names},
annotations={name: self.annotations[name] for name in test_names},
images=test_input,
annotations=test_annotations,
)
return train_dataset, test_dataset
def as_folder_structure(self, root_directory_path: str) -> None:
@@ -611,18 +857,16 @@ class ClassificationDataset(BaseDataset):
for class_name in self.classes:
os.makedirs(os.path.join(root_directory_path, class_name), exist_ok=True)
for image_path in self.images:
classification = self.annotations[image_path]
image = self.images[image_path]
image_name = Path(image_path).name
for image_save_path, image, annotation in self:
image_name = Path(image_save_path).name
class_id = (
classification.class_id[0]
if classification.confidence is None
else classification.get_top_k(1)[0][0]
annotation.class_id[0]
if annotation.confidence is None
else annotation.get_top_k(1)[0][0]
)
class_name = self.classes[class_id]
image_path = os.path.join(root_directory_path, class_name, image_name)
cv2.imwrite(image_path, image)
image_save_path = os.path.join(root_directory_path, class_name, image_name)
cv2.imwrite(image_save_path, image)
@classmethod
def from_folder_structure(cls, root_directory_path: str) -> ClassificationDataset:
@@ -635,7 +879,7 @@ class ClassificationDataset(BaseDataset):
Returns:
ClassificationDataset: The dataset.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
@@ -655,7 +899,7 @@ class ClassificationDataset(BaseDataset):
classes = os.listdir(root_directory_path)
classes = sorted(set(classes))
images = {}
image_paths = []
annotations = {}
for class_name in classes:
@@ -663,13 +907,13 @@ class ClassificationDataset(BaseDataset):
for image in os.listdir(os.path.join(root_directory_path, class_name)):
image_path = str(os.path.join(root_directory_path, class_name, image))
images[image_path] = cv2.imread(image_path)
image_paths.append(image_path)
annotations[image_path] = Classifications(
class_id=np.array([class_id]),
)
return cls(
classes=classes,
images=images,
images=image_paths,
annotations=annotations,
)
+61 -40
View File
@@ -1,19 +1,28 @@
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Tuple
from typing import TYPE_CHECKING, Dict, List, Tuple
import cv2
import numpy as np
import numpy.typing as npt
from supervision.dataset.utils import (
approximate_mask_with_polygons,
map_detections_class_id,
mask_to_rle,
rle_to_mask,
)
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask
from supervision.detection.utils import (
contains_holes,
contains_multiple_segments,
polygon_to_mask,
)
from supervision.utils.file import read_json_file, save_json_file
if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
def coco_categories_to_classes(coco_categories: List[dict]) -> List[str]:
return [
@@ -57,13 +66,24 @@ def group_coco_annotations_by_image_id(
return annotations
def _polygons_to_masks(
polygons: List[np.ndarray], resolution_wh: Tuple[int, int]
) -> np.ndarray:
def coco_annotations_to_masks(
image_annotations: List[dict], resolution_wh: Tuple[int, int]
) -> npt.NDArray[np.bool_]:
return np.array(
[
polygon_to_mask(polygon=polygon, resolution_wh=resolution_wh)
for polygon in polygons
rle_to_mask(
rle=np.array(image_annotation["segmentation"]["counts"]),
resolution_wh=resolution_wh,
)
if image_annotation["iscrowd"]
else polygon_to_mask(
polygon=np.reshape(
np.asarray(image_annotation["segmentation"], dtype=np.int32),
(-1, 2),
),
resolution_wh=resolution_wh,
)
for image_annotation in image_annotations
],
dtype=bool,
)
@@ -83,13 +103,9 @@ def coco_annotations_to_detections(
xyxy[:, 2:4] += xyxy[:, 0:2]
if with_masks:
polygons = [
np.reshape(
np.asarray(image_annotation["segmentation"], dtype=np.int32), (-1, 2)
)
for image_annotation in image_annotations
]
mask = _polygons_to_masks(polygons=polygons, resolution_wh=resolution_wh)
mask = coco_annotations_to_masks(
image_annotations=image_annotations, resolution_wh=resolution_wh
)
return Detections(
class_id=np.asarray(class_ids, dtype=int), xyxy=xyxy, mask=mask
)
@@ -108,24 +124,35 @@ def detections_to_coco_annotations(
coco_annotations = []
for xyxy, mask, _, class_id, _, _ in detections:
box_width, box_height = xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]
polygon = []
segmentation = []
iscrowd = 0
if mask is not None:
polygon = list(
approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)[0].flatten()
)
iscrowd = contains_holes(mask=mask) or contains_multiple_segments(mask=mask)
if iscrowd:
segmentation = {
"counts": mask_to_rle(mask=mask),
"size": list(mask.shape[:2]),
}
else:
segmentation = [
list(
approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)[0].flatten()
)
]
coco_annotation = {
"id": annotation_id,
"image_id": image_id,
"category_id": int(class_id),
"bbox": [xyxy[0], xyxy[1], box_width, box_height],
"area": box_width * box_height,
"segmentation": [polygon] if polygon else [],
"iscrowd": 0,
"segmentation": segmentation,
"iscrowd": iscrowd,
}
coco_annotations.append(coco_annotation)
annotation_id += 1
@@ -136,7 +163,7 @@ def load_coco_annotations(
images_directory_path: str,
annotations_path: str,
force_masks: bool = False,
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
) -> Tuple[List[str], List[str], Dict[str, Detections]]:
coco_data = read_json_file(file_path=annotations_path)
classes = coco_categories_to_classes(coco_categories=coco_data["categories"])
class_index_mapping = build_coco_class_index_mapping(
@@ -147,7 +174,7 @@ def load_coco_annotations(
coco_annotations=coco_data["annotations"]
)
images = {}
images = []
annotations = {}
for coco_image in coco_images:
@@ -159,7 +186,6 @@ def load_coco_annotations(
image_annotations = coco_annotations_groups.get(coco_image["id"], [])
image_path = os.path.join(images_directory_path, image_name)
image = cv2.imread(image_path)
annotation = coco_annotations_to_detections(
image_annotations=image_annotations,
resolution_wh=(image_width, image_height),
@@ -170,23 +196,20 @@ def load_coco_annotations(
detections=annotation,
)
images[image_path] = image
images.append(image_path)
annotations[image_path] = annotation
return classes, images, annotations
def save_coco_annotations(
dataset: "DetectionDataset",
annotation_path: str,
images: Dict[str, np.ndarray],
annotations: Dict[str, Detections],
classes: List[str],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> None:
Path(annotation_path).parent.mkdir(parents=True, exist_ok=True)
info = {}
licenses = [
{
"id": 1,
@@ -197,10 +220,10 @@ def save_coco_annotations(
coco_annotations = []
coco_images = []
coco_categories = classes_to_coco_categories(classes=classes)
coco_categories = classes_to_coco_categories(classes=dataset.classes)
image_id, annotation_id = 1, 1
for image_path, image in images.items():
for image_path, image, annotation in dataset:
image_height, image_width, _ = image.shape
image_name = f"{Path(image_path).stem}{Path(image_path).suffix}"
coco_image = {
@@ -213,10 +236,8 @@ def save_coco_annotations(
}
coco_images.append(coco_image)
detections = annotations[image_path]
coco_annotation, annotation_id = detections_to_coco_annotations(
detections=detections,
detections=annotation,
image_id=image_id,
annotation_id=annotation_id,
min_image_area_percentage=min_image_area_percentage,
@@ -228,7 +249,7 @@ def save_coco_annotations(
image_id += 1
annotation_dict = {
"info": info,
"info": {},
"licenses": licenses,
"categories": coco_categories,
"images": coco_images,
+19 -24
View File
@@ -5,7 +5,8 @@ from xml.etree.ElementTree import Element, SubElement
import cv2
import numpy as np
from defusedxml.ElementTree import fromstring, parse, tostring
from defusedxml.ElementTree import parse, tostring
from defusedxml.minidom import parseString
from supervision.dataset.utils import approximate_mask_with_polygons
from supervision.detection.core import Detections
@@ -129,8 +130,7 @@ def detections_to_pascal_voc(
annotation.append(next_object)
# Generate XML string
xml_string = fromstring(tostring(annotation)).toprettyxml(indent=" ")
xml_string = parseString(tostring(annotation)).toprettyxml(indent=" ")
return xml_string
@@ -138,7 +138,7 @@ def load_pascal_voc_annotations(
images_directory_path: str,
annotations_directory_path: str,
force_masks: bool = False,
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
) -> Tuple[List[str], List[str], Dict[str, Detections]]:
"""
Loads PASCAL VOC XML annotations and returns the image name,
a Detections instance, and a list of class names.
@@ -147,48 +147,43 @@ def load_pascal_voc_annotations(
images_directory_path (str): The path to the directory containing the images.
annotations_directory_path (str): The path to the directory containing the
PASCAL VOC annotation files.
force_masks (bool, optional): If True, forces masks to be loaded for all
force_masks (bool): If True, forces masks to be loaded for all
annotations, regardless of whether they are present.
Returns:
Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]: A tuple
containing a list of class names,
a dictionary with image names as keys and
images as values, and a dictionary with image names as
keys and corresponding Detections instances as values.
Tuple[List[str], List[str], Dict[str, Detections]]: A tuple with a list
of class names, a list of paths to images, and a dictionary with image
paths as keys and corresponding Detections instances as values.
"""
image_paths = list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
image_paths = [
str(path)
for path in list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
]
classes = []
images = {}
classes: List[str] = []
annotations = {}
for image_path in image_paths:
image_name = Path(image_path).stem
image_path = str(image_path)
image = cv2.imread(image_path)
annotation_path = os.path.join(annotations_directory_path, f"{image_name}.xml")
image_stem = Path(image_path).stem
annotation_path = os.path.join(annotations_directory_path, f"{image_stem}.xml")
if not os.path.exists(annotation_path):
images[image_path] = image
annotations[image_path] = Detections.empty()
continue
tree = parse(annotation_path)
root = tree.getroot()
image = cv2.imread(image_path)
resolution_wh = (image.shape[1], image.shape[0])
annotation, classes = detections_from_xml_obj(
root, classes, resolution_wh, force_masks
)
images[image_path] = image
annotations[image_path] = annotation
return classes, images, annotations
return classes, image_paths, annotations
def detections_from_xml_obj(
+47 -26
View File
@@ -1,10 +1,11 @@
import os
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
import cv2
import numpy as np
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.dataset.utils import approximate_mask_with_polygons
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy
@@ -16,6 +17,9 @@ from supervision.utils.file import (
save_yaml_file,
)
if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
def _parse_box(values: List[str]) -> np.ndarray:
x_center, y_center, width, height = values
@@ -70,12 +74,15 @@ def _image_name_to_annotation_name(image_name: str) -> str:
def yolo_annotations_to_detections(
lines: List[str], resolution_wh: Tuple[int, int], with_masks: bool
lines: List[str],
resolution_wh: Tuple[int, int],
with_masks: bool,
is_obb: bool = False,
) -> Detections:
if len(lines) == 0:
return Detections.empty()
class_id, relative_xyxy, relative_polygon = [], [], []
class_id, relative_xyxy, relative_polygon, relative_xyxyxyxy = [], [], [], []
w, h = resolution_wh
for line in lines:
values = line.split()
@@ -88,21 +95,30 @@ def yolo_annotations_to_detections(
elif len(values) > 5:
polygon = _parse_polygon(values=values[1:])
relative_xyxy.append(polygon_to_xyxy(polygon=polygon))
if is_obb:
relative_xyxyxyxy.append(np.array(values[1:]))
if with_masks:
relative_polygon.append(polygon)
class_id = np.array(class_id, dtype=int)
relative_xyxy = np.array(relative_xyxy, dtype=np.float32)
xyxy = relative_xyxy * np.array([w, h, w, h], dtype=np.float32)
data = {}
if is_obb:
relative_xyxyxyxy = np.array(relative_xyxyxyxy, dtype=np.float32)
xyxyxyxy = relative_xyxyxyxy.reshape(-1, 4, 2)
xyxyxyxy *= np.array([w, h], dtype=np.float32)
data[ORIENTED_BOX_COORDINATES] = xyxyxyxy
if not with_masks:
return Detections(class_id=class_id, xyxy=xyxy)
return Detections(class_id=class_id, xyxy=xyxy, data=data)
polygons = [
(polygon * np.array(resolution_wh)).astype(int) for polygon in relative_polygon
]
mask = _polygons_to_masks(polygons=polygons, resolution_wh=resolution_wh)
return Detections(class_id=class_id, xyxy=xyxy, mask=mask)
return Detections(class_id=class_id, xyxy=xyxy, data=data, mask=mask)
def load_yolo_annotations(
@@ -110,7 +126,8 @@ def load_yolo_annotations(
annotations_directory_path: str,
data_yaml_path: str,
force_masks: bool = False,
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
is_obb: bool = False,
) -> Tuple[List[str], List[str], Dict[str, Detections]]:
"""
Loads YOLO annotations and returns class names, images,
and their corresponding detections.
@@ -121,34 +138,36 @@ def load_yolo_annotations(
containing the YOLO annotation files.
data_yaml_path (str): The path to the data
YAML file containing class information.
force_masks (bool, optional): If True, forces masks to be loaded
force_masks (bool): If True, forces masks to be loaded
for all annotations, regardless of whether they are present.
is_obb (bool): If True, loads the annotations in OBB format.
OBB annotations are defined as `[class_id, x, y, x, y, x, y, x, y]`,
where pairs of [x, y] are box corners.
Returns:
Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
Tuple[List[str], List[str], Dict[str, Detections]]:
A tuple containing a list of class names, a dictionary with
image names as keys and images as values, and a dictionary
with image names as keys and corresponding Detections instances as values.
"""
image_paths = list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
image_paths = [
str(path)
for path in list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
]
classes = _extract_class_names(file_path=data_yaml_path)
images = {}
annotations = {}
for image_path in image_paths:
image_stem = Path(image_path).stem
image_path = str(image_path)
image = cv2.imread(image_path)
annotation_path = os.path.join(annotations_directory_path, f"{image_stem}.txt")
if not os.path.exists(annotation_path):
images[image_path] = image
annotations[image_path] = Detections.empty()
continue
image = cv2.imread(image_path)
lines = read_txt_file(file_path=annotation_path, skip_empty=True)
h, w, _ = image.shape
resolution_wh = (w, h)
@@ -156,12 +175,13 @@ def load_yolo_annotations(
with_masks = _with_mask(lines=lines)
with_masks = force_masks if force_masks else with_masks
annotation = yolo_annotations_to_detections(
lines=lines, resolution_wh=resolution_wh, with_masks=with_masks
lines=lines,
resolution_wh=resolution_wh,
with_masks=with_masks,
is_obb=is_obb,
)
images[image_path] = image
annotations[image_path] = annotation
return classes, images, annotations
return classes, image_paths, annotations
def object_to_yolo(
@@ -195,6 +215,9 @@ def detections_to_yolo_annotations(
) -> List[str]:
annotation = []
for xyxy, mask, _, class_id, _, _ in detections:
if class_id is None:
raise ValueError("Class ID is required for YOLO annotations.")
if mask is not None:
polygons = approximate_mask_with_polygons(
mask=mask,
@@ -220,24 +243,22 @@ def detections_to_yolo_annotations(
def save_yolo_annotations(
dataset: "DetectionDataset",
annotations_directory_path: str,
images: Dict[str, np.ndarray],
annotations: Dict[str, Detections],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> None:
Path(annotations_directory_path).mkdir(parents=True, exist_ok=True)
for image_path, image in images.items():
detections = annotations[image_path]
for image_path, image, annotation in dataset:
image_name = Path(image_path).name
yolo_annotations_name = _image_name_to_annotation_name(image_name=image_name)
yolo_annotations_path = os.path.join(
annotations_directory_path, yolo_annotations_name
)
lines = detections_to_yolo_annotations(
detections=detections,
image_shape=image.shape,
detections=annotation,
image_shape=image.shape, # type: ignore
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
+135 -7
View File
@@ -1,11 +1,13 @@
import copy
import os
import random
import shutil
from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypeVar
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, TypeVar, Union
import cv2
import numpy as np
import numpy.typing as npt
from supervision.detection.core import Detections
from supervision.detection.utils import (
@@ -14,6 +16,9 @@ from supervision.detection.utils import (
mask_to_polygons,
)
if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
T = TypeVar("T")
@@ -58,6 +63,7 @@ def merge_class_lists(class_lists: List[List[str]]) -> List[str]:
def build_class_index_mapping(
source_classes: List[str], target_classes: List[str]
) -> Dict[int, int]:
"""Returns the index map of source classes -> target classes."""
index_mapping = {}
for i, class_name in enumerate(source_classes):
@@ -93,14 +99,16 @@ def map_detections_class_id(
def save_dataset_images(
images_directory_path: str, images: Dict[str, np.ndarray]
dataset: "DetectionDataset", images_directory_path: str
) -> None:
Path(images_directory_path).mkdir(parents=True, exist_ok=True)
for image_path, image in images.items():
image_name = Path(image_path).name
target_image_path = os.path.join(images_directory_path, image_name)
cv2.imwrite(target_image_path, image)
for image_path in dataset.image_paths:
final_path = os.path.join(images_directory_path, Path(image_path).name)
if image_path in dataset._images_in_memory:
image = dataset._images_in_memory[image_path]
cv2.imwrite(final_path, image)
else:
shutil.copyfile(image_path, final_path)
def train_test_split(
@@ -129,3 +137,123 @@ def train_test_split(
split_index = int(len(data) * train_ratio)
return data[:split_index], data[split_index:]
def rle_to_mask(
rle: Union[npt.NDArray[np.int_], List[int]], resolution_wh: Tuple[int, int]
) -> npt.NDArray[np.bool_]:
"""
Converts run-length encoding (RLE) to a binary mask.
Args:
rle (Union[npt.NDArray[np.int_], List[int]]): The 1D RLE array, the format
used in the COCO dataset (column-wise encoding, values of an array with
even indices represent the number of pixels assigned as background,
values of an array with odd indices represent the number of pixels
assigned as foreground object).
resolution_wh (Tuple[int, int]): The width (w) and height (h)
of the desired binary mask.
Returns:
The generated 2D Boolean mask of shape `(h, w)`, where the foreground object is
marked with `True`'s and the rest is filled with `False`'s.
Raises:
AssertionError: If the sum of pixels encoded in RLE differs from the
number of pixels in the expected mask (computed based on resolution_wh).
Examples:
```python
import supervision as sv
sv.rle_to_mask([5, 2, 2, 2, 5], (4, 4))
# array([
# [False, False, False, False],
# [False, True, True, False],
# [False, True, True, False],
# [False, False, False, False],
# ])
```
"""
if isinstance(rle, list):
rle = np.array(rle, dtype=int)
width, height = resolution_wh
assert width * height == np.sum(rle), (
"the sum of the number of pixels in the RLE must be the same "
"as the number of pixels in the expected mask"
)
zero_one_values = np.zeros(shape=(rle.size, 1), dtype=np.uint8)
zero_one_values[1::2] = 1
decoded_rle = np.repeat(zero_one_values, rle, axis=0)
decoded_rle = np.append(
decoded_rle, np.zeros(width * height - len(decoded_rle), dtype=np.uint8)
)
return decoded_rle.reshape((height, width), order="F")
def mask_to_rle(mask: npt.NDArray[np.bool_]) -> List[int]:
"""
Converts a binary mask into a run-length encoding (RLE).
Args:
mask (npt.NDArray[np.bool_]): 2D binary mask where `True` indicates foreground
object and `False` indicates background.
Returns:
The run-length encoded mask. Values of a list with even indices
represent the number of pixels assigned as background (`False`), values
of a list with odd indices represent the number of pixels assigned
as foreground object (`True`).
Raises:
AssertionError: If input mask is not 2D or is empty.
Examples:
```python
import numpy as np
import supervision as sv
mask = np.array([
[True, True, True, True],
[True, True, True, True],
[True, True, True, True],
[True, True, True, True],
])
sv.mask_to_rle(mask)
# [0, 16]
mask = np.array([
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
])
sv.mask_to_rle(mask)
# [5, 2, 2, 2, 5]
```
![mask_to_rle](https://media.roboflow.com/supervision-docs/mask-to-rle.png){ align=center width="800" }
""" # noqa E501 // docs
assert mask.ndim == 2, "Input mask must be 2D"
assert mask.size != 0, "Input mask cannot be empty"
on_value_change_indices = np.where(
mask.ravel(order="F") != np.roll(mask.ravel(order="F"), 1)
)[0]
on_value_change_indices = np.append(on_value_change_indices, mask.size)
# need to add 0 at the beginning when the same value is in the first and
# last element of the flattened mask
if on_value_change_indices[0] != 0:
on_value_change_indices = np.insert(on_value_change_indices, 0, 0)
rle = np.diff(on_value_change_indices)
if mask[0][0] == 1:
rle = np.insert(rle, 0, 0)
return list(rle)
-155
View File
@@ -1,155 +0,0 @@
from typing import List, Optional, Union
import cv2
from supervision.annotators.base import ImageType
from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.utils.conversion import convert_for_annotation_method
from supervision.utils.internal import deprecated
class BoxAnnotator:
"""
A class for drawing bounding boxes on an image using detections provided.
Attributes:
color (Union[Color, ColorPalette]): The color to draw the bounding box,
can be a single color or a color palette
thickness (int): The thickness of the bounding box lines, default is 2
text_color (Color): The color of the text on the bounding box, default is white
text_scale (float): The scale of the text on the bounding box, default is 0.5
text_thickness (int): The thickness of the text on the bounding box,
default is 1
text_padding (int): The padding around the text on the bounding box,
default is 5
"""
def __init__(
self,
color: Union[Color, ColorPalette] = ColorPalette.DEFAULT,
thickness: int = 2,
text_color: Color = Color.BLACK,
text_scale: float = 0.5,
text_thickness: int = 1,
text_padding: int = 10,
):
self.color: Union[Color, ColorPalette] = color
self.thickness: int = thickness
self.text_color: Color = text_color
self.text_scale: float = text_scale
self.text_thickness: int = text_thickness
self.text_padding: int = text_padding
@deprecated(
"`BoxAnnotator` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `BoundingBoxAnnotator` and `LabelAnnotator` instead"
)
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
detections: Detections,
labels: Optional[List[str]] = None,
skip_label: bool = False,
) -> ImageType:
"""
Draws bounding boxes on the frame using the detections provided.
Args:
scene (ImageType): The image on which the bounding boxes will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): The detections for which the
bounding boxes will be drawn
labels (Optional[List[str]]): An optional list of labels
corresponding to each detection. If `labels` are not provided,
corresponding `class_id` will be used as label.
skip_label (bool): Is set to `True`, skips bounding box label annotation.
Returns:
ImageType: The image with the bounding boxes drawn on it, matching the
type of `scene` (`numpy.ndarray` or `PIL.Image.Image`)
Example:
```python
import supervision as sv
classes = ['person', ...]
image = ...
detections = sv.Detections(...)
box_annotator = sv.BoxAnnotator()
labels = [
f"{classes[class_id]} {confidence:0.2f}"
for _, _, confidence, class_id, _ in detections
]
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections,
labels=labels
)
```
"""
font = cv2.FONT_HERSHEY_SIMPLEX
for i in range(len(detections)):
x1, y1, x2, y2 = detections.xyxy[i].astype(int)
class_id = (
detections.class_id[i] if detections.class_id is not None else None
)
idx = class_id if class_id is not None else i
color = (
self.color.by_idx(idx)
if isinstance(self.color, ColorPalette)
else self.color
)
cv2.rectangle(
img=scene,
pt1=(x1, y1),
pt2=(x2, y2),
color=color.as_bgr(),
thickness=self.thickness,
)
if skip_label:
continue
text = (
f"{class_id}"
if (labels is None or len(detections) != len(labels))
else labels[i]
)
text_width, text_height = cv2.getTextSize(
text=text,
fontFace=font,
fontScale=self.text_scale,
thickness=self.text_thickness,
)[0]
text_x = x1 + self.text_padding
text_y = y1 - self.text_padding
text_background_x1 = x1
text_background_y1 = y1 - 2 * self.text_padding - text_height
text_background_x2 = x1 + 2 * self.text_padding + text_width
text_background_y2 = y1
cv2.rectangle(
img=scene,
pt1=(text_background_x1, text_background_y1),
pt2=(text_background_x2, text_background_y2),
color=color.as_bgr(),
thickness=cv2.FILLED,
)
cv2.putText(
img=scene,
text=text,
org=(text_x, text_y),
fontFace=font,
fontScale=self.text_scale,
color=self.text_color.as_rgb(),
thickness=self.text_thickness,
lineType=cv2.LINE_AA,
)
return scene
+338 -88
View File
@@ -7,20 +7,35 @@ 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.detection.utils import (
from supervision.detection.lmm import (
LMM,
from_florence_2,
from_paligemma,
validate_lmm_parameters,
)
from supervision.detection.overlap_filter import (
box_non_max_merge,
box_non_max_suppression,
mask_non_max_suppression,
)
from supervision.detection.tools.transformers import (
process_transformers_detection_result,
process_transformers_v4_segmentation_result,
process_transformers_v5_segmentation_result,
)
from supervision.detection.utils import (
box_iou_batch,
calculate_masks_centroids,
extract_ultralytics_masks,
get_data_item,
is_data_equal,
mask_non_max_suppression,
mask_to_xyxy,
merge_data,
process_roboflow_result,
xywh_to_xyxy,
)
from supervision.geometry.core import Position
from supervision.utils.internal import deprecated
from supervision.utils.internal import get_instance_variables
from supervision.validators import validate_detections_fields
@@ -234,13 +249,9 @@ class Detections:
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```
!!! tip
Class names values can be accessed using `detections["class_name"]`.
""" # noqa: E501 // docs
if "obb" in ultralytics_results and ultralytics_results.obb is not None:
if hasattr(ultralytics_results, "obb") and ultralytics_results.obb is not None:
class_id = ultralytics_results.obb.cls.cpu().numpy().astype(int)
class_names = np.array([ultralytics_results.names[i] for i in class_id])
oriented_box_coordinates = ultralytics_results.obb.xyxyxyxy.cpu().numpy()
@@ -257,6 +268,14 @@ class Detections:
},
)
if hasattr(ultralytics_results, "boxes") and ultralytics_results.boxes is None:
masks = extract_ultralytics_masks(ultralytics_results)
return cls(
xyxy=mask_to_xyxy(masks),
mask=masks,
class_id=np.arange(len(ultralytics_results)),
)
class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int)
class_names = np.array([ultralytics_results.names[i] for i in class_id])
return cls(
@@ -418,6 +437,9 @@ class Detections:
xyxy=mmdet_results.pred_instances.bboxes.cpu().numpy(),
confidence=mmdet_results.pred_instances.scores.cpu().numpy(),
class_id=mmdet_results.pred_instances.labels.cpu().numpy().astype(int),
mask=mmdet_results.pred_instances.masks.cpu().numpy()
if "masks" in mmdet_results.pred_instances
else None,
)
@classmethod
@@ -425,15 +447,19 @@ class Detections:
cls, transformers_results: dict, id2label: Optional[Dict[int, str]] = None
) -> Detections:
"""
Creates a Detections instance from object detection or segmentation
Creates a Detections instance from object detection or panoptic, semantic
and instance segmentation
[Transformer](https://github.com/huggingface/transformers) inference result.
Args:
transformers_results (dict): The output of Transformers model inference. A
dictionary containing the `scores`, `labels`, `boxes` and `masks` keys.
transformers_results (Union[dict, torch.Tensor]): Inference results from
your Transformers model. This can be either a dictionary containing
valuable outputs like `scores`, `labels`, `boxes`, `masks`,
`segments_info`, and `segmentation`, or a `torch.Tensor` holding a
segmentation map where values represent class IDs.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to
class names. If provided, the resulting Detections object will contain
`class_name` data field with the class names.
labels, typically part of the `transformers` model configuration. If
provided, the resulting dictionary will include class names.
Returns:
Detections: A new Detections object.
@@ -464,36 +490,28 @@ class Detections:
id2label=model.config.id2label
)
```
!!! tip
Class names values can be accessed using `detections["class_name"]`.
""" # noqa: E501 // docs
class_ids = transformers_results["labels"].cpu().detach().numpy().astype(int)
data = {}
if id2label is not None:
class_names = np.array([id2label[class_id] for class_id in class_ids])
data[CLASS_NAME_DATA_FIELD] = class_names
if (
transformers_results.__class__.__name__ == "Tensor"
or "segmentation" in transformers_results
):
return cls(
**process_transformers_v5_segmentation_result(
transformers_results, id2label
)
)
if "masks" in transformers_results or "png_string" in transformers_results:
return cls(
**process_transformers_v4_segmentation_result(
transformers_results, id2label
)
)
if "boxes" in transformers_results:
return cls(
xyxy=transformers_results["boxes"].cpu().detach().numpy(),
confidence=transformers_results["scores"].cpu().detach().numpy(),
class_id=class_ids,
data=data,
)
elif "masks" in transformers_results:
masks = transformers_results["masks"].cpu().detach().numpy().astype(bool)
return cls(
xyxy=mask_to_xyxy(masks),
mask=masks,
confidence=transformers_results["scores"].cpu().detach().numpy(),
class_id=class_ids,
data=data,
)
else:
raise NotImplementedError(
"Only object detection and semantic segmentation results are supported."
**process_transformers_detection_result(transformers_results, id2label)
)
@classmethod
@@ -532,6 +550,9 @@ class Detections:
return cls(
xyxy=detectron2_results["instances"].pred_boxes.tensor.cpu().numpy(),
confidence=detectron2_results["instances"].scores.cpu().numpy(),
mask=detectron2_results["instances"].pred_masks.cpu().numpy()
if hasattr(detectron2_results["instances"], "pred_masks")
else None,
class_id=detectron2_results["instances"]
.pred_classes.cpu()
.numpy()
@@ -567,10 +588,6 @@ class Detections:
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
```
!!! tip
Class names values can be accessed using `detections["class_name"]`.
"""
with suppress(AttributeError):
roboflow_result = roboflow_result.dict(exclude_none=True, by_alias=True)
@@ -592,45 +609,6 @@ class Detections:
data=data,
)
@classmethod
@deprecated(
"`Detections.from_roboflow` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Detections.from_inference` instead."
)
def from_roboflow(cls, roboflow_result: Union[dict, Any]) -> Detections:
"""
!!! failure "Deprecated"
`Detections.from_roboflow` is deprecated and will be removed in
`supervision-0.22.0`. Use `Detections.from_inference` instead.
Create a Detections object from the [Roboflow](https://roboflow.com/)
API inference result or the [Inference](https://inference.roboflow.com/)
package results.
Args:
roboflow_result (dict): The result from the
Roboflow API containing predictions.
Returns:
(Detections): A Detections object containing the bounding boxes, class IDs,
and confidences of the predictions.
Example:
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8s-640")
result = model.infer(image)[0]
detections = sv.Detections.from_roboflow(result)
```
"""
return cls.from_inference(roboflow_result)
@classmethod
def from_sam(cls, sam_result: List[dict]) -> Detections:
"""
@@ -670,7 +648,7 @@ class Detections:
if np.asarray(xywh).shape[0] == 0:
return cls.empty()
xyxy = xywh_to_xyxy(boxes_xywh=xywh)
xyxy = xywh_to_xyxy(xywh=xywh)
return cls(xyxy=xyxy, mask=mask)
@classmethod
@@ -802,6 +780,69 @@ class Detections:
class_id=paddledet_result["bbox"][:, 0].astype(int),
)
@classmethod
def from_lmm(
cls, lmm: Union[LMM, str], result: Union[str, dict], **kwargs
) -> Detections:
"""
Creates a Detections object from the given result string based on the specified
Large Multimodal Model (LMM).
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.
Returns:
Detections: A new Detections object.
Raises:
ValueError: If the LMM is invalid, required arguments are missing, or
disallowed arguments are provided.
ValueError: If the specified LMM is not supported.
Examples:
```python
import supervision as sv
paligemma_result = "<loc0256><loc0256><loc0768><loc0768> cat"
detections = sv.Detections.from_lmm(
sv.LMM.PALIGEMMA,
paligemma_result,
resolution_wh=(1000, 1000),
classes=['cat', 'dog']
)
detections.xyxy
# array([[250., 250., 750., 750.]])
detections.class_id
# array([0])
```
"""
lmm = validate_lmm_parameters(lmm, result, kwargs)
if lmm == LMM.PALIGEMMA:
assert isinstance(result, str)
xyxy, class_id, class_name = from_paligemma(result, **kwargs)
data = {CLASS_NAME_DATA_FIELD: class_name}
return cls(xyxy=xyxy, class_id=class_id, data=data)
if lmm == LMM.FLORENCE_2:
assert isinstance(result, dict)
xyxy, labels, mask, xyxyxyxy = from_florence_2(result, **kwargs)
if len(xyxy) == 0:
return cls.empty()
data = {}
if labels is not None:
data[CLASS_NAME_DATA_FIELD] = labels
if xyxyxyxy is not None:
data[ORIENTED_BOX_COORDINATES] = xyxyxyxy
return cls(xyxy=xyxy, mask=mask, data=data)
raise ValueError(f"Unsupported LMM: {lmm}")
@classmethod
def empty(cls) -> Detections:
"""
@@ -824,6 +865,14 @@ class Detections:
class_id=np.array([], dtype=int),
)
def is_empty(self) -> bool:
"""
Returns `True` if the `Detections` object is considered empty.
"""
empty_detections = Detections.empty()
empty_detections.data = self.data
return self == empty_detections
@classmethod
def merge(cls, detections_list: List[Detections]) -> Detections:
"""
@@ -831,9 +880,14 @@ class Detections:
This method takes a list of Detections objects and combines their
respective fields (`xyxy`, `mask`, `confidence`, `class_id`, and `tracker_id`)
into a single Detections object. If all elements in a field are not
`None`, the corresponding field will be stacked.
Otherwise, the field will be set to `None`.
into a single Detections object.
For example, if merging Detections with 3 and 4 detected objects, this method
will return a Detections with 7 objects (7 entries in `xyxy`, `mask`, etc).
!!! Note
When merging, empty `Detections` objects are ignored.
Args:
detections_list (List[Detections]): A list of Detections objects to merge.
@@ -873,6 +927,10 @@ class Detections:
array([0.1, 0.2, 0.3])
```
"""
detections_list = [
detections for detections in detections_list if not detections.is_empty()
]
if len(detections_list) == 0:
return Detections.empty()
@@ -1100,10 +1158,10 @@ class Detections:
from a segmentation model, the IoU mask is applied. Otherwise, box IoU is used.
Args:
threshold (float, optional): The intersection-over-union threshold
threshold (float): The intersection-over-union threshold
to use for non-maximum suppression. I'm the lower the value the more
restrictive the NMS becomes. Defaults to 0.5.
class_agnostic (bool, optional): Whether to perform class-agnostic
class_agnostic (bool): Whether to perform class-agnostic
non-maximum suppression. If True, the class_id of each detection
will be ignored. Defaults to False.
@@ -1147,3 +1205,195 @@ class Detections:
)
return self[indices]
def with_nmm(
self, threshold: float = 0.5, class_agnostic: bool = False
) -> Detections:
"""
Perform non-maximum merging on the current set of object detections.
Args:
threshold (float): The intersection-over-union threshold
to use for non-maximum merging. Defaults to 0.5.
class_agnostic (bool): Whether to perform class-agnostic
non-maximum merging. If True, the class_id of each detection
will be ignored. Defaults to False.
Returns:
Detections: A new Detections object containing the subset of detections
after non-maximum merging.
Raises:
AssertionError: If `confidence` is None or `class_id` is None and
class_agnostic is False.
![non-max-merging](https://media.roboflow.com/supervision-docs/non-max-merging.png){ align=center width="800" }
""" # noqa: E501 // docs
if len(self) == 0:
return self
assert (
self.confidence is not None
), "Detections confidence must be given for NMM to be executed."
if class_agnostic:
predictions = np.hstack((self.xyxy, self.confidence.reshape(-1, 1)))
else:
assert self.class_id is not None, (
"Detections class_id must be given for NMM to be executed. If you"
" intended to perform class agnostic NMM set class_agnostic=True."
)
predictions = np.hstack(
(
self.xyxy,
self.confidence.reshape(-1, 1),
self.class_id.reshape(-1, 1),
)
)
merge_groups = box_non_max_merge(
predictions=predictions, iou_threshold=threshold
)
result = []
for merge_group in merge_groups:
unmerged_detections = [self[i] for i in merge_group]
merged_detections = merge_inner_detections_objects(
unmerged_detections, threshold
)
result.append(merged_detections)
return Detections.merge(result)
def merge_inner_detection_object_pair(
detections_1: Detections, detections_2: Detections
) -> Detections:
"""
Merges two Detections object into a single Detections object.
Assumes each Detections contains exactly one object.
A `winning` detection is determined based on the confidence score of the two
input detections. This winning detection is then used to specify which
`class_id`, `tracker_id`, and `data` to include in the merged Detections object.
The resulting `confidence` of the merged object is calculated by the weighted
contribution of ea detection to the merged object.
The bounding boxes and masks of the two input detections are merged into a
single bounding box and mask, respectively.
Args:
detections_1 (Detections):
The first Detections object
detections_2 (Detections):
The second Detections object
Returns:
Detections: A new Detections object, with merged attributes.
Raises:
ValueError: If the input Detections objects do not have exactly 1 detected
object.
Example:
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8s-640")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
merged_detections = merge_object_detection_pair(
detections[0], detections[1])
```
"""
if len(detections_1) != 1 or len(detections_2) != 1:
raise ValueError("Both Detections should have exactly 1 detected object.")
validate_fields_both_defined_or_none(detections_1, detections_2)
xyxy_1 = detections_1.xyxy[0]
xyxy_2 = detections_2.xyxy[0]
if detections_1.confidence is None and detections_2.confidence is None:
merged_confidence = None
else:
detection_1_area = (xyxy_1[2] - xyxy_1[0]) * (xyxy_1[3] - xyxy_1[1])
detections_2_area = (xyxy_2[2] - xyxy_2[0]) * (xyxy_2[3] - xyxy_2[1])
merged_confidence = (
detection_1_area * detections_1.confidence[0]
+ detections_2_area * detections_2.confidence[0]
) / (detection_1_area + detections_2_area)
merged_confidence = np.array([merged_confidence])
merged_x1, merged_y1 = np.minimum(xyxy_1[:2], xyxy_2[:2])
merged_x2, merged_y2 = np.maximum(xyxy_1[2:], xyxy_2[2:])
merged_xyxy = np.array([[merged_x1, merged_y1, merged_x2, merged_y2]])
if detections_1.mask is None and detections_2.mask is None:
merged_mask = None
else:
merged_mask = np.logical_or(detections_1.mask, detections_2.mask)
if detections_1.confidence is None and detections_2.confidence is None:
winning_detection = detections_1
elif detections_1.confidence[0] >= detections_2.confidence[0]:
winning_detection = detections_1
else:
winning_detection = detections_2
return Detections(
xyxy=merged_xyxy,
mask=merged_mask,
confidence=merged_confidence,
class_id=winning_detection.class_id,
tracker_id=winning_detection.tracker_id,
data=winning_detection.data,
)
def merge_inner_detections_objects(
detections: List[Detections], threshold=0.5
) -> Detections:
"""
Given N detections each of length 1 (exactly one object inside), combine them into a
single detection object of length 1. The contained inner object will be the merged
result of all the input detections.
For example, this lets you merge N boxes into one big box, N masks into one mask,
etc.
"""
detections_1 = detections[0]
for detections_2 in detections[1:]:
box_iou = box_iou_batch(detections_1.xyxy, detections_2.xyxy)[0]
if box_iou < threshold:
break
detections_1 = merge_inner_detection_object_pair(detections_1, detections_2)
return detections_1
def validate_fields_both_defined_or_none(
detections_1: Detections, detections_2: Detections
) -> None:
"""
Verify that for each optional field in the Detections, both instances either have
the field set to None or both have it set to non-None values.
`data` field is ignored.
Raises:
ValueError: If one field is None and the other is not, for any of the fields.
"""
attributes = get_instance_variables(detections_1)
for attribute in attributes:
value_1 = getattr(detections_1, attribute)
value_2 = getattr(detections_2, attribute)
if (value_1 is None) != (value_2 is None):
raise ValueError(
f"Field '{attribute}' should be consistently None or not None in both "
"Detections."
)
+26 -20
View File
@@ -1,12 +1,15 @@
import warnings
from typing import Dict, Iterable, Optional, Tuple
import cv2
import numpy as np
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.utils.internal import SupervisionWarnings
class LineZone:
@@ -81,6 +84,8 @@ class LineZone:
self.in_count: int = 0
self.out_count: int = 0
self.triggering_anchors = triggering_anchors
if not list(self.triggering_anchors):
raise ValueError("Triggering anchors cannot be empty.")
@staticmethod
def calculate_region_of_interest_limits(vector: Vector) -> Tuple[Vector, Vector]:
@@ -140,6 +145,15 @@ class LineZone:
if len(detections) == 0:
return crossed_in, crossed_out
if detections.tracker_id is None:
warnings.warn(
"Line zone counting skipped. LineZone requires tracker_id. Refer to "
"https://supervision.roboflow.com/latest/trackers for more "
"information.",
category=SupervisionWarnings,
)
return crossed_in, crossed_out
all_anchors = np.array(
[
detections.get_anchors_coordinates(anchor)
@@ -147,31 +161,23 @@ class LineZone:
]
)
cross_products_1 = cross_product(all_anchors, self.limits[0])
cross_products_2 = cross_product(all_anchors, self.limits[1])
in_limits = (cross_products_1 > 0) == (cross_products_2 > 0)
in_limits = np.all(in_limits, axis=0)
triggers = cross_product(all_anchors, self.vector) < 0
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):
if tracker_id is None:
if not in_limits[i]:
continue
box_anchors = [Point(x=x, y=y) for x, y in all_anchors[:, i, :]]
in_limits = all(
[
self.is_point_in_limits(point=anchor, limits=self.limits)
for anchor in box_anchors
]
)
if not in_limits:
if not is_uniformly_triggered[i]:
continue
triggers = [
self.vector.cross_product(point=anchor) < 0 for anchor in box_anchors
]
if len(set(triggers)) == 2:
continue
tracker_state = triggers[0]
tracker_state = has_any_left_trigger[i]
if tracker_id not in self.tracker_state:
self.tracker_state[tracker_id] = tracker_state
continue
+184
View File
@@ -0,0 +1,184 @@
import re
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union
import numpy as np
from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy
class LMM(Enum):
PALIGEMMA = "paligemma"
FLORENCE_2 = "florence_2"
RESULT_TYPES: Dict[LMM, type] = {LMM.PALIGEMMA: str, LMM.FLORENCE_2: dict}
REQUIRED_ARGUMENTS: Dict[LMM, List[str]] = {
LMM.PALIGEMMA: ["resolution_wh"],
LMM.FLORENCE_2: ["resolution_wh"],
}
ALLOWED_ARGUMENTS: Dict[LMM, List[str]] = {
LMM.PALIGEMMA: ["resolution_wh", "classes"],
LMM.FLORENCE_2: ["resolution_wh"],
}
SUPPORTED_TASKS_FLORENCE_2 = [
"<OD>",
"<CAPTION_TO_PHRASE_GROUNDING>",
"<DENSE_REGION_CAPTION>",
"<REGION_PROPOSAL>",
"<OCR_WITH_REGION>",
"<REFERRING_EXPRESSION_SEGMENTATION>",
"<REGION_TO_SEGMENTATION>",
"<OPEN_VOCABULARY_DETECTION>",
"<REGION_TO_CATEGORY>",
"<REGION_TO_DESCRIPTION>",
]
def validate_lmm_parameters(
lmm: Union[LMM, str], result: Any, kwargs: Dict[str, Any]
) -> LMM:
if isinstance(lmm, str):
try:
lmm = LMM(lmm.lower())
except ValueError:
raise ValueError(
f"Invalid lmm value: {lmm}. Must be one of {[e.value for e in LMM]}"
)
if not isinstance(result, RESULT_TYPES[lmm]):
raise ValueError(
f"Invalid LMM result type: {type(result)}. Must be {RESULT_TYPES[lmm]}"
)
required_args = REQUIRED_ARGUMENTS.get(lmm, [])
for arg in required_args:
if arg not in kwargs:
raise ValueError(f"Missing required argument: {arg}")
allowed_args = ALLOWED_ARGUMENTS.get(lmm, [])
for arg in kwargs:
if arg not in allowed_args:
raise ValueError(f"Argument {arg} is not allowed for {lmm.name}")
return lmm
def from_paligemma(
result: str, resolution_wh: Tuple[int, int], classes: Optional[List[str]] = None
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
w, h = resolution_wh
pattern = re.compile(
r"(?<!<loc\d{4}>)<loc(\d{4})><loc(\d{4})><loc(\d{4})><loc(\d{4})> ([\w\s\-]+)"
)
matches = pattern.findall(result)
matches = np.array(matches) if matches else np.empty((0, 5))
xyxy, class_name = matches[:, [1, 0, 3, 2]], matches[:, 4]
xyxy = xyxy.astype(int) / 1024 * np.array([w, h, w, h])
class_name = np.char.strip(class_name.astype(str))
class_id = None
if classes is not None:
mask = np.array([name in classes for name in class_name]).astype(bool)
xyxy, class_name = xyxy[mask], class_name[mask]
class_id = np.array([classes.index(name) for name in class_name])
return xyxy, class_id, class_name
def from_florence_2(
result: dict, resolution_wh: Tuple[int, int]
) -> Tuple[
np.ndarray, Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray]
]:
"""
Parse results from the Florence 2 multi-model model.
https://huggingface.co/microsoft/Florence-2-large
Parameters:
result: dict containing the model output
Returns:
xyxy (np.ndarray): An array of shape `(n, 4)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
labels: (Optional[np.ndarray]): An array of shape `(n,)` containing
the class labels for each bounding box
masks: (Optional[np.ndarray]): An array of shape `(n, h, w)` containing
the segmentation masks for each bounding box
obb_boxes: (Optional[np.ndarray]): An array of shape `(n, 4, 2)` containing
oriented bounding boxes.
"""
assert len(result) == 1, f"Expected result with a single element. Got: {result}"
task = list(result.keys())[0]
if task not in SUPPORTED_TASKS_FLORENCE_2:
raise ValueError(
f"{task} not supported. Supported tasks are: {SUPPORTED_TASKS_FLORENCE_2}"
)
result = result[task]
if task in ["<OD>", "<CAPTION_TO_PHRASE_GROUNDING>", "<DENSE_REGION_CAPTION>"]:
xyxy = np.array(result["bboxes"], dtype=np.float32)
labels = np.array(result["labels"])
return xyxy, labels, None, None
if task == "<REGION_PROPOSAL>":
xyxy = np.array(result["bboxes"], dtype=np.float32)
# provides labels, but they are ["", "", "", ...]
return xyxy, None, None, None
if task == "<OCR_WITH_REGION>":
xyxyxyxy = np.array(result["quad_boxes"], dtype=np.float32)
xyxyxyxy = xyxyxyxy.reshape(-1, 4, 2)
xyxy = np.array([polygon_to_xyxy(polygon) for polygon in xyxyxyxy])
labels = np.array(result["labels"])
return xyxy, labels, None, xyxyxyxy
if task in ["<REFERRING_EXPRESSION_SEGMENTATION>", "<REGION_TO_SEGMENTATION>"]:
xyxy_list = []
masks_list = []
for polygons_of_same_class in result["polygons"]:
for polygon in polygons_of_same_class:
polygon = np.reshape(polygon, (-1, 2)).astype(np.int32)
mask = polygon_to_mask(polygon, resolution_wh).astype(bool)
masks_list.append(mask)
xyxy = polygon_to_xyxy(polygon)
xyxy_list.append(xyxy)
# per-class labels also provided, but they are ["", "", "", ...]
# when we figure out how to set class names, we can do
# zip(result["labels"], result["polygons"])
xyxy = np.array(xyxy_list, dtype=np.float32)
masks = np.array(masks_list)
return xyxy, None, masks, None
if task == "<OPEN_VOCABULARY_DETECTION>":
xyxy = np.array(result["bboxes"], dtype=np.float32)
labels = np.array(result["bboxes_labels"])
# Also has "polygons" and "polygons_labels", but they don't seem to be used
return xyxy, labels, None, None
if task in ["<REGION_TO_CATEGORY>", "<REGION_TO_DESCRIPTION>"]:
assert isinstance(
result, str
), f"Expected string as <REGION_TO_CATEGORY> result, got {type(result)}"
if result == "No object detected.":
return np.empty((0, 4), dtype=np.float32), np.array([]), None, None
pattern = re.compile(r"<loc_(\d+)><loc_(\d+)><loc_(\d+)><loc_(\d+)>")
match = pattern.search(result)
assert (
match is not None
), f"Expected string to end in location tags, but got {result}"
w, h = resolution_wh
xyxy = np.array([match.groups()], dtype=np.float32)
xyxy *= np.array([w, h, w, h]) / 1000
result_string = result[: match.start()]
labels = np.array([result_string])
return xyxy, labels, None, None
assert False, f"Unimplemented task: {task}"
+270
View File
@@ -0,0 +1,270 @@
from __future__ import annotations
from enum import Enum
from typing import List, Union
import numpy as np
import numpy.typing as npt
from supervision.detection.utils import box_iou_batch, mask_iou_batch
def resize_masks(masks: np.ndarray, max_dimension: int = 640) -> np.ndarray:
"""
Resize all masks in the array to have a maximum dimension of max_dimension,
maintaining aspect ratio.
Args:
masks (np.ndarray): 3D array of binary masks with shape (N, H, W).
max_dimension (int): The maximum dimension for the resized masks.
Returns:
np.ndarray: Array of resized masks.
"""
max_height = np.max(masks.shape[1])
max_width = np.max(masks.shape[2])
scale = min(max_dimension / max_height, max_dimension / max_width)
new_height = int(scale * max_height)
new_width = int(scale * max_width)
x = np.linspace(0, max_width - 1, new_width).astype(int)
y = np.linspace(0, max_height - 1, new_height).astype(int)
xv, yv = np.meshgrid(x, y)
resized_masks = masks[:, yv, xv]
resized_masks = resized_masks.reshape(masks.shape[0], new_height, new_width)
return resized_masks
def mask_non_max_suppression(
predictions: np.ndarray,
masks: np.ndarray,
iou_threshold: float = 0.5,
mask_dimension: int = 640,
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on segmentation predictions.
Args:
predictions (np.ndarray): A 2D array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`. Shape: `(N, 5)` or
`(N, 6)`, where N is the number of predictions.
masks (np.ndarray): A 3D array of binary masks corresponding to the predictions.
Shape: `(N, H, W)`, where N is the number of predictions, and H, W are the
dimensions of each mask.
iou_threshold (float): The intersection-over-union threshold
to use for non-maximum suppression.
mask_dimension (int): The dimension to which the masks should be
resized before computing IOU values. Defaults to 640.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after
non-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the closed
range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
sort_index = predictions[:, 4].argsort()[::-1]
predictions = predictions[sort_index]
masks = masks[sort_index]
masks_resized = resize_masks(masks, mask_dimension)
ious = mask_iou_batch(masks_resized, masks_resized)
categories = predictions[:, 5]
keep = np.ones(rows, dtype=bool)
for i in range(rows):
if keep[i]:
condition = (ious[i] > iou_threshold) & (categories[i] == categories)
keep[i + 1 :] = np.where(condition[i + 1 :], False, keep[i + 1 :])
return keep[sort_index.argsort()]
def box_non_max_suppression(
predictions: np.ndarray, iou_threshold: float = 0.5
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on object detection predictions.
Args:
predictions (np.ndarray): An array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`.
iou_threshold (float): The intersection-over-union threshold
to use for non-maximum suppression.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after n
on-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the
closed range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
# add column #5 - category filled with zeros for agnostic nms
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
# sort predictions column #4 - score
sort_index = np.flip(predictions[:, 4].argsort())
predictions = predictions[sort_index]
boxes = predictions[:, :4]
categories = predictions[:, 5]
ious = box_iou_batch(boxes, boxes)
ious = ious - np.eye(rows)
keep = np.ones(rows, dtype=bool)
for index, (iou, category) in enumerate(zip(ious, categories)):
if not keep[index]:
continue
# drop detections with iou > iou_threshold and
# same category as current detections
condition = (iou > iou_threshold) & (categories == category)
keep = keep & ~condition
return keep[sort_index.argsort()]
def group_overlapping_boxes(
predictions: npt.NDArray[np.float64], iou_threshold: float = 0.5
) -> List[List[int]]:
"""
Apply greedy version of non-maximum merging to avoid detecting too many
overlapping bounding boxes for a given object.
Args:
predictions (npt.NDArray[np.float64]): An array of shape `(n, 5)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
and the confidence scores.
iou_threshold (float): The intersection-over-union threshold
to use for non-maximum suppression. Defaults to 0.5.
Returns:
List[List[int]]: Groups of prediction indices be merged.
Each group may have 1 or more elements.
"""
merge_groups: List[List[int]] = []
scores = predictions[:, 4]
order = scores.argsort()
while len(order) > 0:
idx = int(order[-1])
order = order[:-1]
if len(order) == 0:
merge_groups.append([idx])
break
merge_candidate = np.expand_dims(predictions[idx], axis=0)
ious = box_iou_batch(predictions[order][:, :4], merge_candidate[:, :4])
ious = ious.flatten()
above_threshold = ious >= iou_threshold
merge_group = [idx] + np.flip(order[above_threshold]).tolist()
merge_groups.append(merge_group)
order = order[~above_threshold]
return merge_groups
def box_non_max_merge(
predictions: npt.NDArray[np.float64],
iou_threshold: float = 0.5,
) -> List[List[int]]:
"""
Apply greedy version of non-maximum merging per category to avoid detecting
too many overlapping bounding boxes for a given object.
Args:
predictions (npt.NDArray[np.float64]): An array of shape `(n, 5)` or `(n, 6)`
containing the bounding boxes coordinates in format `[x1, y1, x2, y2]`,
the confidence scores and class_ids. Omit class_id column to allow
detections of different classes to be merged.
iou_threshold (float): The intersection-over-union threshold
to use for non-maximum suppression. Defaults to 0.5.
Returns:
List[List[int]]: Groups of prediction indices be merged.
Each group may have 1 or more elements.
"""
if predictions.shape[1] == 5:
return group_overlapping_boxes(predictions, iou_threshold)
category_ids = predictions[:, 5]
merge_groups = []
for category_id in np.unique(category_ids):
curr_indices = np.where(category_ids == category_id)[0]
merge_class_groups = group_overlapping_boxes(
predictions[curr_indices], iou_threshold
)
for merge_class_group in merge_class_groups:
merge_groups.append(curr_indices[merge_class_group].tolist())
for merge_group in merge_groups:
if len(merge_group) == 0:
raise ValueError(
f"Empty group detected when non-max-merging "
f"detections: {merge_groups}"
)
return merge_groups
class OverlapFilter(Enum):
"""
Enum specifying the strategy for filtering overlapping detections.
Attributes:
NONE: Do not filter detections based on overlap.
NON_MAX_SUPPRESSION: Filter detections using non-max suppression. This means,
detections that overlap by more than a set threshold will be discarded,
except for the one with the highest confidence.
NON_MAX_MERGE: Merge detections with non-max merging. This means,
detections that overlap by more than a set threshold will be merged
into a single detection.
"""
NONE = "none"
NON_MAX_SUPPRESSION = "non_max_suppression"
NON_MAX_MERGE = "non_max_merge"
@classmethod
def list(cls):
return list(map(lambda c: c.value, cls))
@classmethod
def from_value(cls, value: Union[OverlapFilter, str]) -> OverlapFilter:
if isinstance(value, cls):
return value
if isinstance(value, str):
value = value.lower()
try:
return cls(value)
except ValueError:
raise ValueError(f"Invalid value: {value}. Must be one of {cls.list()}")
raise ValueError(
f"Invalid value type: {type(value)}. Must be an instance of "
f"{cls.__name__} or str."
)
+142 -28
View File
@@ -1,23 +1,51 @@
import warnings
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Callable, Optional, Tuple
from typing import Callable, Optional, Tuple, Union
import numpy as np
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.detection.core import Detections
from supervision.detection.utils import move_boxes
from supervision.detection.overlap_filter import OverlapFilter
from supervision.detection.utils import move_boxes, move_masks, move_oriented_boxes
from supervision.utils.image import crop_image
from supervision.utils.internal import (
SupervisionWarnings,
deprecated_parameter,
warn_deprecated,
)
def move_detections(detections: Detections, offset: np.array) -> Detections:
def move_detections(
detections: Detections,
offset: np.ndarray,
resolution_wh: Optional[Tuple[int, int]] = None,
) -> Detections:
"""
Args:
detections (sv.Detections): Detections object to be moved.
offset (np.array): An array of shape `(2,)` containing offset values in format
offset (np.ndarray): An array of shape `(2,)` containing offset values in format
is `[dx, dy]`.
resolution_wh (Tuple[int, int]): The width and height of the desired mask
resolution. Required for segmentation detections.
Returns:
(sv.Detections) repositioned Detections object.
"""
detections.xyxy = move_boxes(xyxy=detections.xyxy, offset=offset)
if ORIENTED_BOX_COORDINATES in detections.data:
detections.data[ORIENTED_BOX_COORDINATES] = move_oriented_boxes(
xyxyxyxy=detections.data[ORIENTED_BOX_COORDINATES], offset=offset
)
if detections.mask is not None:
if resolution_wh is None:
raise ValueError(
"Resolution width and height are required for moving segmentation "
"detections. This should be the same as (width, height) of image shape."
)
detections.mask = move_masks(
masks=detections.mask, offset=offset, resolution_wh=resolution_wh
)
return detections
@@ -30,12 +58,19 @@ class InferenceSlicer:
slice, and then merging the detections.
Args:
slice_wh (Tuple[int, int]): Dimensions of each slice in the format
`(width, height)`.
overlap_ratio_wh (Tuple[float, float]): Overlap ratio between consecutive
slices in the format `(width_ratio, height_ratio)`.
iou_threshold (Optional[float]): Intersection over Union (IoU) threshold
used for non-max suppression.
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
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.
overlap_filter (Union[OverlapFilter, str]): Strategy for
filtering or merging overlapping detections in slices.
iou_threshold (float): Intersection over Union (IoU) threshold
used when filtering by overlap.
callback (Callable): A function that performs inference on a given image
slice and returns detections.
thread_workers (int): Number of threads for parallel execution.
@@ -47,17 +82,37 @@ 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],
slice_wh: Tuple[int, int] = (320, 320),
overlap_ratio_wh: Tuple[float, float] = (0.2, 0.2),
iou_threshold: Optional[float] = 0.5,
overlap_ratio_wh: Optional[Tuple[float, float]] = (0.2, 0.2),
overlap_wh: Optional[Tuple[int, int]] = None,
overlap_filter: Union[OverlapFilter, str] = OverlapFilter.NON_MAX_SUPPRESSION,
iou_threshold: float = 0.5,
thread_workers: int = 1,
):
self.slice_wh = slice_wh
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."
)
self._validate_overlap(overlap_ratio_wh, overlap_wh)
self.overlap_ratio_wh = overlap_ratio_wh
self.overlap_wh = overlap_wh
self.slice_wh = slice_wh
self.iou_threshold = iou_threshold
self.overlap_filter = OverlapFilter.from_value(overlap_filter)
self.callback = callback
self.thread_workers = thread_workers
@@ -88,7 +143,10 @@ class InferenceSlicer:
result = model(image_slice)[0]
return sv.Detections.from_ultralytics(result)
slicer = sv.InferenceSlicer(callback = callback)
slicer = sv.InferenceSlicer(
callback=callback,
overlap_filter_strategy=sv.OverlapFilter.NON_MAX_SUPPRESSION,
)
detections = slicer(image)
```
@@ -99,6 +157,7 @@ class InferenceSlicer:
resolution_wh=resolution_wh,
slice_wh=self.slice_wh,
overlap_ratio_wh=self.overlap_ratio_wh,
overlap_wh=self.overlap_wh,
)
with ThreadPoolExecutor(max_workers=self.thread_workers) as executor:
@@ -108,9 +167,19 @@ class InferenceSlicer:
for future in as_completed(futures):
detections_list.append(future.result())
return Detections.merge(detections_list=detections_list).with_nms(
threshold=self.iou_threshold
)
merged = Detections.merge(detections_list=detections_list)
if self.overlap_filter == OverlapFilter.NONE:
return merged
elif self.overlap_filter == OverlapFilter.NON_MAX_SUPPRESSION:
return merged.with_nms(threshold=self.iou_threshold)
elif self.overlap_filter == OverlapFilter.NON_MAX_MERGE:
return merged.with_nmm(threshold=self.iou_threshold)
else:
warnings.warn(
f"Invalid overlap filter strategy: {self.overlap_filter}",
category=SupervisionWarnings,
)
return merged
def _run_callback(self, image, offset) -> Detections:
"""
@@ -126,7 +195,10 @@ class InferenceSlicer:
"""
image_slice = crop_image(image=image, xyxy=offset)
detections = self.callback(image_slice)
detections = move_detections(detections=detections, offset=offset[:2])
resolution_wh = (image.shape[1], image.shape[0])
detections = move_detections(
detections=detections, offset=offset[:2], resolution_wh=resolution_wh
)
return detections
@@ -134,7 +206,8 @@ class InferenceSlicer:
def _generate_offset(
resolution_wh: Tuple[int, int],
slice_wh: Tuple[int, int],
overlap_ratio_wh: Tuple[float, float],
overlap_ratio_wh: Optional[Tuple[float, float]],
overlap_wh: Optional[Tuple[int, int]],
) -> np.ndarray:
"""
Generate offset coordinates for slicing an image based on the given resolution,
@@ -143,12 +216,15 @@ class InferenceSlicer:
Args:
resolution_wh (Tuple[int, int]): A tuple representing the width and height
of the image to be sliced.
slice_wh (Tuple[int, int]): A tuple representing the desired width and
height of each slice.
overlap_ratio_wh (Tuple[float, float]): 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.
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
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.
Returns:
np.ndarray: An array of shape `(n, 4)` containing coordinates for each
@@ -163,10 +239,19 @@ class InferenceSlicer:
"""
slice_width, slice_height = slice_wh
image_width, image_height = resolution_wh
overlap_ratio_width, overlap_ratio_height = overlap_ratio_wh
overlap_width = (
overlap_wh[0]
if overlap_wh is not None
else int(overlap_ratio_wh[0] * slice_width)
)
overlap_height = (
overlap_wh[1]
if overlap_wh is not None
else int(overlap_ratio_wh[1] * slice_height)
)
width_stride = slice_width - int(overlap_ratio_width * slice_width)
height_stride = slice_height - int(overlap_ratio_height * slice_height)
width_stride = slice_width - overlap_width
height_stride = slice_height - overlap_height
ws = np.arange(0, image_width, width_stride)
hs = np.arange(0, image_height, height_stride)
@@ -178,3 +263,32 @@ class InferenceSlicer:
offsets = np.stack([xmin, ymin, xmax, ymax], axis=-1).reshape(-1, 4)
return offsets
@staticmethod
def _validate_overlap(
overlap_ratio_wh: Optional[Tuple[float, float]],
overlap_wh: Optional[Tuple[int, int]],
) -> None:
if overlap_ratio_wh is not None and overlap_wh is not None:
raise ValueError(
"Both `overlap_ratio_wh` and `overlap_wh` cannot be provided. "
"Please provide only one of them."
)
if overlap_ratio_wh is None and overlap_wh is None:
raise ValueError(
"Either `overlap_ratio_wh` or `overlap_wh` must be provided. "
"Please provide one of them."
)
if overlap_ratio_wh is not None:
if not (0 <= overlap_ratio_wh[0] < 1 and 0 <= overlap_ratio_wh[1] < 1):
raise ValueError(
"Overlap ratios must be in the range [0, 1). "
f"Received: {overlap_ratio_wh}"
)
if overlap_wh is not None:
if not (overlap_wh[0] >= 0 and overlap_wh[1] >= 0):
raise ValueError(
"Overlap values must be greater than or equal to 0. "
f"Received: {overlap_wh}"
)
+4 -10
View File
@@ -12,7 +12,7 @@ from supervision.draw.color import Color
from supervision.draw.utils import draw_polygon, draw_text
from supervision.geometry.core import Position
from supervision.geometry.utils import get_polygon_center
from supervision.utils.internal import SupervisionWarnings, deprecated_parameter
from supervision.utils.internal import SupervisionWarnings
class PolygonZone:
@@ -30,14 +30,6 @@ class PolygonZone:
mask (np.ndarray): The 2D bool mask for the polygon zone
"""
@deprecated_parameter(
old_parameter="triggering_position",
new_parameter="triggering_anchors",
map_function=lambda x: [x],
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
def __init__(
self,
polygon: npt.NDArray[np.int64],
@@ -54,6 +46,8 @@ class PolygonZone:
self.polygon = polygon.astype(int)
self.triggering_anchors = triggering_anchors
if not list(self.triggering_anchors):
raise ValueError("Triggering anchors cannot be empty.")
self.current_count = 0
@@ -145,7 +139,7 @@ class PolygonZoneAnnotator:
Parameters:
scene (np.ndarray): The image on which the polygon zone will be annotated
label (Optional[str]): An optional label for the count of detected objects
label (Optional[str]): A label for the count of detected objects
within the polygon zone (default: None)
Returns:
+8 -6
View File
@@ -1,3 +1,4 @@
import warnings
from collections import defaultdict, deque
from copy import deepcopy
from typing import Optional
@@ -5,6 +6,7 @@ from typing import Optional
import numpy as np
from supervision.detection.core import Detections
from supervision.utils.internal import SupervisionWarnings
class DetectionsSmoother:
@@ -39,7 +41,7 @@ class DetectionsSmoother:
tracker = sv.ByteTrack(frame_rate=video_info.fps)
smoother = sv.DetectionsSmoother()
annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
with sv.VideoSink(<TARGET_FILE_PATH>, video_info=video_info) as sink:
for frame in frame_generator:
@@ -48,7 +50,7 @@ class DetectionsSmoother:
detections = tracker.update_with_detections(detections)
detections = smoother.update_with_detections(detections)
annotated_frame = bounding_box_annotator.annotate(frame.copy(), detections)
annotated_frame = box_annotator.annotate(frame.copy(), detections)
sink.write_frame(annotated_frame)
```
""" # noqa: E501 // docs
@@ -70,16 +72,16 @@ class DetectionsSmoother:
"""
if detections.tracker_id is None:
print(
warnings.warn(
"Smoothing skipped. DetectionsSmoother requires tracker_id. Refer to "
"https://supervision.roboflow.com/latest/trackers for more information."
"https://supervision.roboflow.com/latest/trackers for more "
"information.",
category=SupervisionWarnings,
)
return detections
for detection_idx in range(len(detections)):
tracker_id = detections.tracker_id[detection_idx]
if tracker_id is None:
continue
self.tracks[tracker_id].append(detections[detection_idx])
+247
View File
@@ -0,0 +1,247 @@
import io
from typing import Any, Dict, Optional
import numpy as np
from PIL import Image
from supervision.config import CLASS_NAME_DATA_FIELD
from supervision.detection.utils import mask_to_xyxy
def process_transformers_detection_result(
detection_result: dict, id2label: Optional[Dict[int, str]]
) -> dict:
"""
Process the result of Transformers object detection functions such as
`post_process` (v4) and `post_process_detection` (v5).
Args:
detection_result (dict): Dictionary containing detection results with keys
'boxes', 'labels', and 'scores'.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to labels,
typically part of the `transformers` model configuration. If provided, the
resulting dictionary will include class names.
Returns:
dict: Processed detection result including bounding boxes, confidence scores,
class IDs, and data.
"""
class_ids = detection_result["labels"].cpu().detach().numpy().astype(int)
data = append_class_names_to_data(class_ids, id2label, {})
return dict(
xyxy=detection_result["boxes"].cpu().detach().numpy(),
confidence=detection_result["scores"].cpu().detach().numpy(),
class_id=class_ids,
data=data,
)
def process_transformers_v4_segmentation_result(
segmentation_result: dict, id2label: Optional[Dict[int, str]]
) -> dict:
"""
Process the result of Transformers segmentation functions such as
`post_process_panoptic`, `post_process_segmentation`, and `post_process_instance`
(v4).
Args:
segmentation_result (dict): Dictionary containing segmentation results with keys
'masks', 'labels', and 'scores'.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to labels,
typically part of the `transformers` model configuration. If provided, the
resulting dictionary will include class names.
Returns:
dict: Processed segmentation result including bounding boxes, masks, confidence
scores, class IDs, and data.
"""
if "png_string" in segmentation_result:
return process_transformers_v4_panoptic_segmentation_result(
segmentation_result, id2label
)
else:
boxes = None
if "boxes" in segmentation_result:
boxes = segmentation_result["boxes"].cpu().detach().numpy()
masks = segmentation_result["masks"].cpu().detach().numpy().astype(bool)
class_ids = segmentation_result["labels"].cpu().detach().numpy().astype(int)
return dict(
xyxy=boxes if boxes is not None else mask_to_xyxy(masks),
mask=np.squeeze(masks, axis=1) if boxes is not None else masks,
confidence=segmentation_result["scores"].cpu().detach().numpy(),
class_id=class_ids,
data=append_class_names_to_data(class_ids, id2label, {}),
)
def process_transformers_v5_segmentation_result(
segmentation_result: dict, id2label: Optional[Dict[int, str]]
) -> dict:
"""
Process the result of Transformers segmentation functions such as
`post_process_semantic_segmentation`, `post_process_instance_segmentation`, and
`post_process_panoptic_segmentation` (v5).
Args:
segmentation_result (Union[dict, np.ndarray]): Either a dictionary containing
segmentation results or an ndarray representing a segmentation map.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to labels,
typically part of the `transformers` model configuration. If provided, the
resulting dictionary will include class names.
Returns:
dict: Processed segmentation result including bounding boxes, masks, confidence
scores, class IDs, and data.
"""
if segmentation_result.__class__.__name__ == "Tensor":
segmentation_array = segmentation_result.cpu().detach().numpy()
return process_transformers_v5_panoptic_segmentation_result(
segmentation_array, id2label
)
return process_transformers_v5_semantic_or_instance_segmentation_result(
segmentation_result, id2label
)
def process_transformers_v5_semantic_or_instance_segmentation_result(
segmentation_result: dict, id2label: Optional[Dict[int, str]]
) -> dict:
"""
Process the result of Transformers segmentation functions such as
`post_process_semantic_segmentation` and `post_process_instance_segmentation` (v5).
Args:
segmentation_result (dict): Dictionary containing segmentation results with keys
`segments_info` and `segmentation`.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to labels,
typically part of the `transformers` model configuration. If provided, the
resulting dictionary will include class names.
Returns:
dict: Processed segmentation result including bounding boxes, masks, confidence
scores, class IDs, and data.
"""
segments_info = segmentation_result["segments_info"]
scores = np.array([segment["score"] for segment in segments_info])
class_ids = np.array([segment["label_id"] for segment in segments_info])
segmentation_array = segmentation_result["segmentation"].cpu().detach().numpy()
masks = np.array(
[segmentation_array == segment["id"] for segment in segments_info]
).astype(bool)
data = append_class_names_to_data(class_ids, id2label, {})
return dict(
xyxy=mask_to_xyxy(masks),
mask=masks,
confidence=scores,
class_id=class_ids,
data=data,
)
def process_transformers_v4_panoptic_segmentation_result(
segmentation_result: dict, id2label: Optional[Dict[int, str]]
) -> dict:
"""
Process the result of the Transformers function `post_process_panoptic` (v4).
Args:
segmentation_result (dict): Dictionary containing segmentation results with keys
such as 'png_string' and 'segments_info'.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to labels,
typically part of the `transformers` model configuration. If provided, the
resulting dictionary will include class names.
Returns:
dict: Processed segmentation result including bounding boxes, masks,
class IDs, and data.
"""
segments_info = segmentation_result["segments_info"]
png_string = segmentation_result["png_string"]
class_ids = np.array([segment["category_id"] for segment in segments_info])
segmentation_array = png_string_to_segmentation_array(png_string=png_string)
masks = np.array(
[segmentation_array == segment["id"] for segment in segments_info]
).astype(bool)
data = append_class_names_to_data(class_ids, id2label, {})
return dict(
xyxy=mask_to_xyxy(masks),
mask=masks,
class_id=class_ids,
data=data,
)
def process_transformers_v5_panoptic_segmentation_result(
segmentation_array: np.ndarray, id2label: Optional[Dict[int, str]]
) -> dict:
"""
Process the result of the Transformers function
`post_process_panoptic_segmentation` (v5).
Args:
segmentation_array (np.ndarray): Segmentation array.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to labels,
typically part of the `transformers` model configuration. If provided, the
resulting dictionary will include class names.
Returns:
dict: Processed segmentation result including bounding boxes, masks,
class IDs, and data.
"""
class_ids = np.unique(segmentation_array)
masks = np.stack(
[segmentation_array == class_id for class_id in class_ids], axis=0
).astype(bool)
data = append_class_names_to_data(class_ids, id2label, {})
return dict(xyxy=mask_to_xyxy(masks), mask=masks, class_id=class_ids, data=data)
def png_string_to_segmentation_array(png_string: bytes) -> np.ndarray:
"""
Convert a PNG byte string to a label mask array.
Args:
png_string (bytes): A byte string representing the PNG image.
Returns:
np.ndarray: A label mask array with shape (H, W), where H and W
are the height and width of the image. Each unique value in the array
represents a different object or category.
"""
image = Image.open(io.BytesIO(png_string))
mask = np.array(image, dtype=np.uint8)
return mask[:, :, 0]
def append_class_names_to_data(
class_ids: np.ndarray,
id2label: Optional[Dict[int, str]],
data: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Helper function to create or append to a data dictionary with class names if
available.
Args:
class_ids (np.ndarray): Array of class IDs.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to labels,
typically part of the `transformers` model configuration. If provided, the
resulting dictionary will include class names.
data (Optional[Dict[str, Any]]): An existing data dictionary to append to.
Returns:
Dict[str, Any]: Dictionary containing class names if id2label is provided.
"""
if data is None:
data = {}
if id2label is not None:
class_names = np.array([id2label[class_id] for class_id in class_ids])
data[CLASS_NAME_DATA_FIELD] = class_names
return data
+408 -174
View File
@@ -3,8 +3,10 @@ from typing import Dict, List, Optional, Tuple, Union
import cv2
import numpy as np
import numpy.typing as npt
from supervision.config import CLASS_NAME_DATA_FIELD
from supervision.geometry.core import Vector
MIN_POLYGON_POINT_COUNT = 3
@@ -56,7 +58,9 @@ def box_iou_batch(boxes_true: np.ndarray, boxes_detection: np.ndarray) -> np.nda
bottom_right = np.minimum(boxes_true[:, None, 2:], boxes_detection[:, 2:])
area_inter = np.prod(np.clip(bottom_right - top_left, a_min=0, a_max=None), 2)
return area_inter / (area_true[:, None] + area_detection - area_inter)
ious = area_inter / (area_true[:, None] + area_detection - area_inter)
ious = np.nan_to_num(ious)
return ious
def _mask_iou_batch_split(
@@ -102,7 +106,7 @@ def mask_iou_batch(
Args:
masks_true (np.ndarray): 3D `np.ndarray` representing ground-truth masks.
masks_detection (np.ndarray): 3D `np.ndarray` representing detection masks.
memory_limit (int, optional): memory limit in MB, default is 1024 * 5 MB (5GB).
memory_limit (int): memory limit in MB, default is 1024 * 5 MB (5GB).
Returns:
np.ndarray: Pairwise IoU of masks from `masks_true` and `masks_detection`.
@@ -136,144 +140,6 @@ def mask_iou_batch(
return np.vstack(ious)
def resize_masks(masks: np.ndarray, max_dimension: int = 640) -> np.ndarray:
"""
Resize all masks in the array to have a maximum dimension of max_dimension,
maintaining aspect ratio.
Args:
masks (np.ndarray): 3D array of binary masks with shape (N, H, W).
max_dimension (int): The maximum dimension for the resized masks.
Returns:
np.ndarray: Array of resized masks.
"""
max_height = np.max(masks.shape[1])
max_width = np.max(masks.shape[2])
scale = min(max_dimension / max_height, max_dimension / max_width)
new_height = int(scale * max_height)
new_width = int(scale * max_width)
x = np.linspace(0, max_width - 1, new_width).astype(int)
y = np.linspace(0, max_height - 1, new_height).astype(int)
xv, yv = np.meshgrid(x, y)
resized_masks = masks[:, yv, xv]
resized_masks = resized_masks.reshape(masks.shape[0], new_height, new_width)
return resized_masks
def mask_non_max_suppression(
predictions: np.ndarray,
masks: np.ndarray,
iou_threshold: float = 0.5,
mask_dimension: int = 640,
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on segmentation predictions.
Args:
predictions (np.ndarray): A 2D array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`. Shape: `(N, 5)` or
`(N, 6)`, where N is the number of predictions.
masks (np.ndarray): A 3D array of binary masks corresponding to the predictions.
Shape: `(N, H, W)`, where N is the number of predictions, and H, W are the
dimensions of each mask.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression.
mask_dimension (int, optional): The dimension to which the masks should be
resized before computing IOU values. Defaults to 640.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after
non-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the closed
range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
sort_index = predictions[:, 4].argsort()[::-1]
predictions = predictions[sort_index]
masks = masks[sort_index]
masks_resized = resize_masks(masks, mask_dimension)
ious = mask_iou_batch(masks_resized, masks_resized)
categories = predictions[:, 5]
keep = np.ones(rows, dtype=bool)
for i in range(rows):
if keep[i]:
condition = (ious[i] > iou_threshold) & (categories[i] == categories)
keep[i + 1 :] = np.where(condition[i + 1 :], False, keep[i + 1 :])
return keep[sort_index.argsort()]
def box_non_max_suppression(
predictions: np.ndarray, iou_threshold: float = 0.5
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on object detection predictions.
Args:
predictions (np.ndarray): An array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after n
on-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the
closed range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
# add column #5 - category filled with zeros for agnostic nms
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
# sort predictions column #4 - score
sort_index = np.flip(predictions[:, 4].argsort())
predictions = predictions[sort_index]
boxes = predictions[:, :4]
categories = predictions[:, 5]
ious = box_iou_batch(boxes, boxes)
ious = ious - np.eye(rows)
keep = np.ones(rows, dtype=bool)
for index, (iou, category) in enumerate(zip(ious, categories)):
if not keep[index]:
continue
# drop detections with iou > iou_threshold and
# same category as current detections
condition = (iou > iou_threshold) & (categories == category)
keep = keep & ~condition
return keep[sort_index.argsort()]
def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
"""
Clips bounding boxes coordinates to fit within the frame resolution.
@@ -289,6 +155,25 @@ def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
np.ndarray: A numpy array of shape `(N, 4)` where each row
corresponds to a bounding box with coordinates clipped to fit
within the frame resolution.
Examples:
```python
import numpy as np
import supervision as sv
xyxy = np.array([
[10, 20, 300, 200],
[15, 25, 350, 450],
[-10, -20, 30, 40]
])
sv.clip_boxes(xyxy=xyxy, resolution_wh=(320, 240))
# array([
# [ 10, 20, 300, 200],
# [ 15, 25, 320, 240],
# [ 0, 0, 30, 40]
# ])
```
"""
result = np.copy(xyxy)
width, height = resolution_wh
@@ -297,10 +182,124 @@ def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
return result
def xywh_to_xyxy(boxes_xywh: np.ndarray) -> np.ndarray:
xyxy = boxes_xywh.copy()
xyxy[:, 2] = boxes_xywh[:, 0] + boxes_xywh[:, 2]
xyxy[:, 3] = boxes_xywh[:, 1] + boxes_xywh[:, 3]
def pad_boxes(xyxy: np.ndarray, px: int, py: Optional[int] = None) -> np.ndarray:
"""
Pads bounding boxes coordinates with a constant padding.
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)`.
px (int): The padding value to be added to both the left and right sides of
each bounding box.
py (Optional[int]): The padding value to be added to both the top and bottom
sides of each bounding box. If not provided, `px` will be used for both
dimensions.
Returns:
np.ndarray: A numpy array of shape `(N, 4)` where each row corresponds to a
bounding box with coordinates padded according to the provided padding
values.
Examples:
```python
import numpy as np
import supervision as sv
xyxy = np.array([
[10, 20, 30, 40],
[15, 25, 35, 45]
])
sv.pad_boxes(xyxy=xyxy, px=5, py=10)
# array([
# [ 5, 10, 35, 50],
# [10, 15, 40, 55]
# ])
```
"""
if py is None:
py = px
result = xyxy.copy()
result[:, [0, 1]] -= [px, py]
result[:, [2, 3]] += [px, py]
return result
def xywh_to_xyxy(xywh: np.ndarray) -> np.ndarray:
"""
Converts bounding box coordinates from `(x, y, width, height)`
format to `(x_min, y_min, x_max, y_max)` format.
Args:
xywh (np.ndarray): A numpy array of shape `(N, 4)` where each row
corresponds to a bounding box in the format `(x, y, width, height)`.
Returns:
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)`.
Examples:
```python
import numpy as np
import supervision as sv
xywh = np.array([
[10, 20, 30, 40],
[15, 25, 35, 45]
])
sv.xywh_to_xyxy(xywh=xywh)
# array([
# [10, 20, 40, 60],
# [15, 25, 50, 70]
# ])
```
"""
xyxy = xywh.copy()
xyxy[:, 2] = xywh[:, 0] + xywh[:, 2]
xyxy[:, 3] = xywh[:, 1] + xywh[:, 3]
return xyxy
def xcycwh_to_xyxy(xcycwh: np.ndarray) -> np.ndarray:
"""
Converts bounding box coordinates from `(center_x, center_y, width, height)`
format to `(x_min, y_min, x_max, y_max)` format.
Args:
xcycwh (np.ndarray): A numpy array of shape `(N, 4)` where each row
corresponds to a bounding box in the format `(center_x, center_y, width,
height)`.
Returns:
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)`.
Examples:
```python
import numpy as np
import supervision as sv
xcycwh = np.array([
[50, 50, 20, 30],
[30, 40, 10, 15]
])
sv.xcycwh_to_xyxy(xcycwh=xcycwh)
# array([
# [40, 35, 60, 65],
# [25, 32.5, 35, 47.5]
# ])
```
"""
xyxy = xcycwh.copy()
xyxy[:, 0] = xcycwh[:, 0] - xcycwh[:, 2] / 2
xyxy[:, 1] = xcycwh[:, 1] - xcycwh[:, 3] / 2
xyxy[:, 2] = xcycwh[:, 0] + xcycwh[:, 2] / 2
xyxy[:, 3] = xcycwh[:, 1] + xcycwh[:, 3] / 2
return xyxy
@@ -317,7 +316,7 @@ def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
`(x_min, y_min, x_max, y_max)` for each mask
"""
n = masks.shape[0]
bboxes = np.zeros((n, 4), dtype=int)
xyxy = np.zeros((n, 4), dtype=int)
for i, mask in enumerate(masks):
rows, cols = np.where(mask)
@@ -325,9 +324,9 @@ def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
if len(rows) > 0 and len(cols) > 0:
x_min, x_max = np.min(cols), np.max(cols)
y_min, y_max = np.min(rows), np.max(rows)
bboxes[i, :] = [x_min, y_min, x_max, y_max]
xyxy[i, :] = [x_min, y_min, x_max, y_max]
return bboxes
return xyxy
def mask_to_polygons(mask: np.ndarray) -> List[np.ndarray]:
@@ -500,7 +499,7 @@ def process_roboflow_result(
np.ndarray,
Optional[np.ndarray],
Optional[np.ndarray],
Dict[str, List[np.ndarray]],
Dict[str, Union[List[np.ndarray], np.ndarray]],
]:
if not roboflow_result["predictions"]:
return (
@@ -563,59 +562,158 @@ def process_roboflow_result(
return xyxy, confidence, class_id, masks, tracker_id, data
def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray:
def move_boxes(
xyxy: npt.NDArray[np.float64], offset: npt.NDArray[np.int32]
) -> npt.NDArray[np.float64]:
"""
Parameters:
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes
coordinates in format `[x1, y1, x2, y2]`
xyxy (npt.NDArray[np.float64]): An array of shape `(n, 4)` containing the
bounding boxes coordinates in format `[x1, y1, x2, y2]`
offset (np.array): An array of shape `(2,)` containing offset values in format
is `[dx, dy]`.
Returns:
np.ndarray: Repositioned bounding boxes.
npt.NDArray[np.float64]: Repositioned bounding boxes.
Example:
Examples:
```python
import numpy as np
import supervision as sv
boxes = np.array([[10, 10, 20, 20], [30, 30, 40, 40]])
xyxy = np.array([
[10, 10, 20, 20],
[30, 30, 40, 40]
])
offset = np.array([5, 5])
moved_box = sv.move_boxes(boxes, offset)
print(moved_box)
# np.array([
sv.move_boxes(xyxy=xyxy, offset=offset)
# array([
# [15, 15, 25, 25],
# [35, 35, 45, 45]
# [35, 35, 45, 45]
# ])
```
"""
return xyxy + np.hstack([offset, offset])
def scale_boxes(xyxy: np.ndarray, factor: float) -> np.ndarray:
def move_oriented_boxes(
xyxyxyxy: npt.NDArray[np.float64], offset: npt.NDArray[np.int32]
) -> npt.NDArray[np.float64]:
"""
Parameters:
xyxyxyxy (npt.NDArray[np.float64]): An array of shape `(n, 4, 2)` containing the
oriented bounding boxes coordinates in format
`[[x1, y1], [x2, y2], [x3, y3], [x3, y3]]`
offset (np.array): An array of shape `(2,)` containing offset values in format
is `[dx, dy]`.
Returns:
npt.NDArray[np.float64]: Repositioned bounding boxes.
Examples:
```python
import numpy as np
import supervision as sv
xyxyxyxy = np.array([
[
[20, 10],
[10, 20],
[20, 30],
[30, 20]
],
[
[30 ,30],
[20, 40],
[30, 50],
[40, 40]
]
])
offset = np.array([5, 5])
sv.move_oriented_boxes(xyxy=xyxy, offset=offset)
# array([
# [
# [25, 15],
# [15, 25],
# [25, 35],
# [35, 25]
# ],
# [
# [35, 35],
# [25, 45],
# [35, 55],
# [45, 45]
# ]
# ])
```
"""
return xyxyxyxy + offset
def move_masks(
masks: npt.NDArray[np.bool_],
offset: npt.NDArray[np.int32],
resolution_wh: Tuple[int, int],
) -> npt.NDArray[np.bool_]:
"""
Offset the masks in an array by the specified (x, y) amount.
Args:
masks (npt.NDArray[np.bool_]): A 3D array of binary masks corresponding to the
predictions. Shape: `(N, H, W)`, where N is the number of predictions, and
H, W are the dimensions of each mask.
offset (npt.NDArray[np.int32]): An array of shape `(2,)` containing non-negative
int values `[dx, dy]`.
resolution_wh (Tuple[int, int]): The width and height of the desired mask
resolution.
Returns:
(npt.NDArray[np.bool_]) repositioned masks, optionally padded to the specified
shape.
"""
if offset[0] < 0 or offset[1] < 0:
raise ValueError(f"Offset values must be non-negative integers. Got: {offset}")
mask_array = np.full((masks.shape[0], resolution_wh[1], resolution_wh[0]), False)
mask_array[
:,
offset[1] : masks.shape[1] + offset[1],
offset[0] : masks.shape[2] + offset[0],
] = masks
return mask_array
def scale_boxes(
xyxy: npt.NDArray[np.float64], factor: float
) -> npt.NDArray[np.float64]:
"""
Scale the dimensions of bounding boxes.
Parameters:
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes
coordinates in format `[x1, y1, x2, y2]`
xyxy (npt.NDArray[np.float64]): An array of shape `(n, 4)` containing the
bounding boxes coordinates in format `[x1, y1, x2, y2]`
factor (float): A float value representing the factor by which the box
dimensions are scaled. A factor greater than 1 enlarges the boxes, while a
factor less than 1 shrinks them.
Returns:
np.ndarray: Scaled bounding boxes.
npt.NDArray[np.float64]: Scaled bounding boxes.
Example:
Examples:
```python
import numpy as np
import supervision as sv
boxes = np.array([[10, 10, 20, 20], [30, 30, 40, 40]])
factor = 1.5
scaled_bb = sv.scale_boxes(boxes, factor)
print(scaled_bb)
# np.array([
xyxy = np.array([
[10, 10, 20, 20],
[30, 30, 40, 40]
])
sv.scale_boxes(xyxy=xyxy, factor=1.5)
# array([
# [ 7.5, 7.5, 22.5, 22.5],
# [27.5, 27.5, 42.5, 42.5]
# ])
@@ -672,17 +770,19 @@ def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray])
def merge_data(
data_list: List[Dict[str, Union[np.ndarray, List]]],
) -> Dict[str, Union[np.ndarray, List]]:
data_list: List[Dict[str, Union[npt.NDArray[np.generic], List]]],
) -> Dict[str, Union[npt.NDArray[np.generic], List]]:
"""
Merges the data payloads of a list of Detections instances.
Args:
data_list: The data payloads of the instances.
data_list: The data payloads of the Detections instances. Each data payload
is a dictionary with the same keys, and the values are either lists or
npt.NDArray[np.generic].
Returns:
A single data payload containing the merged data, preserving the original data
types (list or np.ndarray).
types (list or npt.NDArray[np.generic]).
Raises:
ValueError: If data values within a single object have different lengths or if
@@ -703,9 +803,8 @@ def merge_data(
)
merged_data = {key: [] for key in all_keys_sets[0]}
for data in data_list:
for key in merged_data:
for key in data:
merged_data[key].append(data[key])
for key in merged_data:
@@ -766,3 +865,138 @@ def get_data_item(
raise TypeError(f"Unsupported data type for key '{key}': {type(value)}")
return subset_data
def contains_holes(mask: npt.NDArray[np.bool_]) -> bool:
"""
Checks if the binary mask contains holes (background pixels fully enclosed by
foreground pixels).
Args:
mask (npt.NDArray[np.bool_]): 2D binary mask where `True` indicates foreground
object and `False` indicates background.
Returns:
True if holes are detected, False otherwise.
Examples:
```python
import numpy as np
import supervision as sv
mask = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
]).astype(bool)
sv.contains_holes(mask=mask)
# True
mask = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
]).astype(bool)
sv.contains_holes(mask=mask)
# False
```
![contains_holes](https://media.roboflow.com/supervision-docs/contains-holes.png){ align=center width="800" }
""" # noqa E501 // docs
mask_uint8 = mask.astype(np.uint8)
_, hierarchy = cv2.findContours(mask_uint8, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
if hierarchy is not None:
parent_contour_index = 3
for h in hierarchy[0]:
if h[parent_contour_index] != -1:
return True
return False
def contains_multiple_segments(
mask: npt.NDArray[np.bool_], connectivity: int = 4
) -> bool:
"""
Checks if the binary mask contains multiple unconnected foreground segments.
Args:
mask (npt.NDArray[np.bool_]): 2D binary mask where `True` indicates foreground
object and `False` indicates background.
connectivity (int) : Default: 4 is 4-way connectivity, which means that
foreground pixels are the part of the same segment/component
if their edges touch.
Alternatively: 8 for 8-way connectivity, when foreground pixels are
connected by their edges or corners touch.
Returns:
True when the mask contains multiple not connected components, False otherwise.
Raises:
ValueError: If connectivity(int) parameter value is not 4 or 8.
Examples:
```python
import numpy as np
import supervision as sv
mask = np.array([
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 1, 1],
[0, 1, 1, 0, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0]
]).astype(bool)
sv.contains_multiple_segments(mask=mask, connectivity=4)
# True
mask = np.array([
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0]
]).astype(bool)
sv.contains_multiple_segments(mask=mask, connectivity=4)
# False
```
![contains_multiple_segments](https://media.roboflow.com/supervision-docs/contains-multiple-segments.png){ align=center width="800" }
""" # noqa E501 // docs
if connectivity != 4 and connectivity != 8:
raise ValueError(
"Incorrect connectivity value. Possible connectivity values: 4 or 8."
)
mask_uint8 = mask.astype(np.uint8)
labels = np.zeros_like(mask_uint8, dtype=np.int32)
number_of_labels, _ = cv2.connectedComponents(
mask_uint8, labels, connectivity=connectivity
)
return number_of_labels > 2
def cross_product(anchors: np.ndarray, vector: Vector) -> np.ndarray:
"""
Get array of cross products of each anchor with a vector.
Args:
anchors: Array of anchors of shape (number of anchors, detections, 2)
vector: Vector to calculate cross product with
Returns:
Array of cross products of shape (number of anchors, detections)
"""
vector_at_zero = np.array(
[vector.end.x - vector.start.x, vector.end.y - vector.start.y]
)
vector_start = np.array([vector.start.x, vector.start.y])
return np.cross(vector_at_zero, anchors - vector_start)
+10 -73
View File
@@ -5,7 +5,7 @@ from typing import List, Tuple, Union
import matplotlib.pyplot as plt
from supervision.utils.internal import classproperty, deprecated
from supervision.utils.internal import classproperty
DEFAULT_COLOR_PALETTE = [
"A351FB",
@@ -255,46 +255,6 @@ class Color:
def ROBOFLOW(cls) -> Color:
return Color.from_hex("#A351FB")
@classmethod
@deprecated(
"`Color.white()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.WHITE` instead."
)
def white(cls) -> Color:
return Color.from_hex(color_hex="#ffffff")
@classmethod
@deprecated(
"`Color.black()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.BLACK` instead."
)
def black(cls) -> Color:
return Color.from_hex(color_hex="#000000")
@classmethod
@deprecated(
"`Color.red()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.RED` instead."
)
def red(cls) -> Color:
return Color.from_hex(color_hex="#ff0000")
@classmethod
@deprecated(
"`Color.green()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.GREEN` instead."
)
def green(cls) -> Color:
return Color.from_hex(color_hex="#00ff00")
@classmethod
@deprecated(
"`Color.blue()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.BLUE` instead."
)
def blue(cls) -> Color:
return Color.from_hex(color_hex="#0000ff")
@dataclass
class ColorPalette:
@@ -346,33 +306,6 @@ class ColorPalette:
def LEGACY(cls) -> ColorPalette:
return ColorPalette.from_hex(color_hex_list=LEGACY_COLOR_PALETTE)
@classmethod
@deprecated(
"`ColorPalette.default()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.DEFAULT` instead."
)
def default(cls) -> ColorPalette:
"""
!!! failure "Deprecated"
`ColorPalette.default()` is deprecated and will be removed in
`supervision-0.22.0`. Use `Color.DEFAULT` instead.
Returns a default color palette.
Returns:
ColorPalette: A ColorPalette instance with default colors.
Example:
```python
import supervision as sv
sv.ColorPalette.default()
# ColorPalette(colors=[Color(r=255, g=64, b=64), Color(r=255, g=161, b=160), ...])
```
""" # noqa: E501 // docs
return ColorPalette.from_hex(color_hex_list=DEFAULT_COLOR_PALETTE)
@classmethod
def from_hex(cls, color_hex_list: List[str]) -> ColorPalette:
"""
@@ -419,11 +352,15 @@ class ColorPalette:
supervision-annotator-examples/visualized_color_palette.png)
""" # noqa: E501 // docs
mpl_palette = plt.get_cmap(palette_name, color_count)
colors = [
Color(int(r * 255), int(g * 255), int(b * 255))
for r, g, b, _ in mpl_palette.colors
]
return cls(colors)
if hasattr(mpl_palette, "colors"):
colors = mpl_palette.colors
else:
colors = [mpl_palette(i / (color_count - 1)) for i in range(color_count)]
return cls(
[Color(int(r * 255), int(g * 255), int(b * 255)) for r, g, b, _ in colors]
)
def by_idx(self, idx: int) -> Color:
"""
+61 -9
View File
@@ -81,6 +81,58 @@ def draw_filled_rectangle(scene: np.ndarray, rect: Rect, color: Color) -> np.nda
return scene
def draw_rounded_rectangle(
scene: np.ndarray,
rect: Rect,
color: Color,
border_radius: int,
) -> np.ndarray:
"""
Draws a rounded rectangle on an image.
Parameters:
scene (np.ndarray): The image on which the rounded rectangle will be drawn.
rect (Rect): The rectangle to be drawn.
color (Color): The color of the rounded rectangle.
border_radius (int): The radius of the corner rounding.
Returns:
np.ndarray: The image with the rounded rectangle drawn on it.
"""
x1, y1, x2, y2 = rect.as_xyxy_int_tuple()
width, height = x2 - x1, y2 - y1
border_radius = min(border_radius, min(width, height) // 2)
rectangle_coordinates = [
((x1 + border_radius, y1), (x2 - border_radius, y2)),
((x1, y1 + border_radius), (x2, y2 - border_radius)),
]
circle_centers = [
(x1 + border_radius, y1 + border_radius),
(x2 - border_radius, y1 + border_radius),
(x1 + border_radius, y2 - border_radius),
(x2 - border_radius, y2 - border_radius),
]
for coordinates in rectangle_coordinates:
cv2.rectangle(
img=scene,
pt1=coordinates[0],
pt2=coordinates[1],
color=color.as_bgr(),
thickness=-1,
)
for center in circle_centers:
cv2.circle(
img=scene,
center=center,
radius=border_radius,
color=color.as_bgr(),
thickness=-1,
)
return scene
def draw_polygon(
scene: np.ndarray, polygon: np.ndarray, color: Color, thickness: int = 2
) -> np.ndarray:
@@ -90,7 +142,7 @@ def draw_polygon(
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.
thickness (int, optional): The thickness of the polygon lines, by default 2.
thickness (int): The thickness of the polygon lines, by default 2.
Returns:
np.ndarray: The scene with the polygon drawn on it.
@@ -120,14 +172,14 @@ def draw_text(
text (str): The text to be drawn.
text_anchor (Point): The anchor point for the text, represented as a
Point object with x and y attributes.
text_color (Color, optional): The color of the text. Defaults to black.
text_scale (float, optional): The scale of the text. Defaults to 0.5.
text_thickness (int, optional): The thickness of the text. Defaults to 1.
text_padding (int, optional): The amount of padding to add around the text
text_color (Color): The color of the text. Defaults to black.
text_scale (float): The scale of the text. Defaults to 0.5.
text_thickness (int): The thickness of the text. Defaults to 1.
text_padding (int): The amount of padding to add around the text
when drawing a rectangle in the background. Defaults to 10.
text_font (int, optional): The font to use for the text.
text_font (int): The font to use for the text.
Defaults to cv2.FONT_HERSHEY_SIMPLEX.
background_color (Color, optional): The color of the background rectangle,
background_color (Optional[Color]): The color of the background rectangle,
if one is to be drawn. Defaults to None.
Returns:
@@ -243,8 +295,8 @@ def calculate_optimal_text_scale(resolution_wh: Tuple[int, int]) -> float:
Calculate font scale based on the resolution of an image.
Parameters:
resolution_wh (Tuple[int, int]): A tuple representing the width and height
of the image.
resolution_wh (Tuple[int, int]): A tuple representing the width and height
of the image.
Returns:
float: The calculated font scale factor.
+13
View File
@@ -98,6 +98,11 @@ class Rect:
width: float
height: float
@classmethod
def from_xyxy(cls, xyxy: Tuple[float, float, float, float]) -> Rect:
x1, y1, x2, y2 = xyxy
return cls(x=x1, y=y1, width=x2 - x1, height=y2 - y1)
@property
def top_left(self) -> Point:
return Point(x=self.x, y=self.y)
@@ -113,3 +118,11 @@ class Rect:
width=self.width + 2 * padding,
height=self.height + 2 * padding,
)
def as_xyxy_int_tuple(self) -> Tuple[int, int, int, int]:
return (
int(self.x),
int(self.y),
int(self.x + self.width),
int(self.y + self.height),
)
+271 -13
View File
@@ -1,15 +1,17 @@
from abc import ABC, abstractmethod
from logging import warn
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Union
import cv2
import numpy as np
from supervision import Rect, pad_boxes
from supervision.annotators.base import ImageType
from supervision.draw.color import Color
from supervision.draw.utils import draw_rounded_rectangle
from supervision.keypoint.core import KeyPoints
from supervision.keypoint.skeletons import SKELETONS_BY_VERTEX_COUNT
from supervision.utils.conversion import convert_for_annotation_method
from supervision.utils.conversion import ensure_cv2_image_for_annotation
class BaseKeyPointAnnotator(ABC):
@@ -32,22 +34,22 @@ class VertexAnnotator(BaseKeyPointAnnotator):
) -> None:
"""
Args:
color (Color, optional): The color to use for annotating key points.
radius (int, optional): The radius of the circles used to represent the key
color (Color): The color to use for annotating key points.
radius (int): The radius of the circles used to represent the key
points.
"""
self.color = color
self.radius = radius
@convert_for_annotation_method
@ensure_cv2_image_for_annotation
def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
"""
Annotates the given scene with skeleton vertices based on the provided key
points. It draws circles at each key point location.
Args:
scene (ImageType): The image where bounding boxes will be drawn. `ImageType`
is a flexible type, accepting either `numpy.ndarray` or
scene (ImageType): The image where skeleton vertices will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray` or
`PIL.Image.Image`.
key_points (KeyPoints): A collection of key points where each key point
consists of x and y coordinates.
@@ -63,7 +65,10 @@ class VertexAnnotator(BaseKeyPointAnnotator):
image = ...
key_points = sv.KeyPoints(...)
vertex_annotator = sv.VertexAnnotator(color=sv.Color.GREEN, radius=10)
vertex_annotator = sv.VertexAnnotator(
color=sv.Color.GREEN,
radius=10
)
annotated_frame = vertex_annotator.annotate(
scene=image.copy(),
key_points=key_points
@@ -73,6 +78,7 @@ class VertexAnnotator(BaseKeyPointAnnotator):
![vertex-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/vertex-annotator-example.png)
"""
assert isinstance(scene, np.ndarray)
if len(key_points) == 0:
return scene
@@ -103,8 +109,8 @@ class EdgeAnnotator(BaseKeyPointAnnotator):
) -> None:
"""
Args:
color (Color, optional): The color to use for the edges.
thickness (int, optional): The thickness of the edges.
color (Color): The color to use for the edges.
thickness (int): The thickness of the edges.
edges (Optional[List[Tuple[int, int]]]): The edges to draw.
If set to `None`, will attempt to select automatically.
"""
@@ -112,14 +118,14 @@ class EdgeAnnotator(BaseKeyPointAnnotator):
self.thickness = thickness
self.edges = edges
@convert_for_annotation_method
@ensure_cv2_image_for_annotation
def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
"""
Annotates the given scene by drawing lines between specified key points to form
edges.
Args:
scene (ImageType): The image where bounding boxes will be drawn. `ImageType`
scene (ImageType): The image where skeleton edges will be drawn. `ImageType`
is a flexible type, accepting either `numpy.ndarray` or
`PIL.Image.Image`.
key_points (KeyPoints): A collection of key points where each key point
@@ -137,7 +143,10 @@ class EdgeAnnotator(BaseKeyPointAnnotator):
image = ...
key_points = sv.KeyPoints(...)
edge_annotator = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5)
edge_annotator = sv.EdgeAnnotator(
color=sv.Color.GREEN,
thickness=5
)
annotated_frame = edge_annotator.annotate(
scene=image.copy(),
key_points=key_points
@@ -147,6 +156,7 @@ class EdgeAnnotator(BaseKeyPointAnnotator):
![edge-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/edge-annotator-example.png)
"""
assert isinstance(scene, np.ndarray)
if len(key_points) == 0:
return scene
@@ -175,3 +185,251 @@ class EdgeAnnotator(BaseKeyPointAnnotator):
)
return scene
class VertexLabelAnnotator:
"""
A class that draws labels of skeleton vertices on images. It uses specified key
points to determine the locations where the vertices should be drawn.
"""
def __init__(
self,
color: Union[Color, List[Color]] = Color.ROBOFLOW,
text_color: Union[Color, List[Color]] = Color.WHITE,
text_scale: float = 0.5,
text_thickness: int = 1,
text_padding: int = 10,
border_radius: int = 0,
):
"""
Args:
color (Union[Color, List[Color]]): The color to use for each
keypoint label. If a list is provided, the colors will be used in order
for each keypoint.
text_color (Union[Color, List[Color]]): The color to use
for the labels. If a list is provided, the colors will be used in order
for each keypoint.
text_scale (float): The scale of the text.
text_thickness (int): The thickness of the text.
text_padding (int): The padding around the text.
border_radius (int): The radius of the rounded corners of the
boxes. Set to a high value to produce circles.
"""
self.border_radius: int = border_radius
self.color: Union[Color, List[Color]] = color
self.text_color: Union[Color, List[Color]] = text_color
self.text_scale: float = text_scale
self.text_thickness: int = text_thickness
self.text_padding: int = text_padding
def annotate(
self,
scene: ImageType,
key_points: KeyPoints,
labels: Optional[List[str]] = None,
) -> ImageType:
"""
A class that draws labels of skeleton vertices on images. It uses specified key
points to determine the locations where the vertices should be drawn.
Args:
scene (ImageType): The image where vertex labels will be drawn. `ImageType`
is a flexible type, accepting either `numpy.ndarray` or
`PIL.Image.Image`.
key_points (KeyPoints): A collection of key points where each key point
consists of x and y coordinates.
labels (Optional[List[str]]): A list of labels to be displayed on the
annotated image. If not provided, keypoint indices will be used.
Returns:
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
vertex_label_annotator = sv.VertexLabelAnnotator(
color=sv.Color.GREEN,
text_color=sv.Color.BLACK,
border_radius=5
)
annotated_frame = vertex_label_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
![vertex-label-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/vertex-label-annotator-example.png)
!!! tip
`VertexLabelAnnotator` allows to customize the color of each keypoint label
values.
Example:
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
LABELS = [
"nose", "left eye", "right eye", "left ear",
"right ear", "left shoulder", "right shoulder", "left elbow",
"right elbow", "left wrist", "right wrist", "left hip",
"right hip", "left knee", "right knee", "left ankle",
"right ankle"
]
COLORS = [
"#FF6347", "#FF6347", "#FF6347", "#FF6347",
"#FF6347", "#FF1493", "#00FF00", "#FF1493",
"#00FF00", "#FF1493", "#00FF00", "#FFD700",
"#00BFFF", "#FFD700", "#00BFFF", "#FFD700",
"#00BFFF"
]
COLORS = [sv.Color.from_hex(color_hex=c) for c in COLORS]
vertex_label_annotator = sv.VertexLabelAnnotator(
color=COLORS,
text_color=sv.Color.BLACK,
border_radius=5
)
annotated_frame = vertex_label_annotator.annotate(
scene=image.copy(),
key_points=key_points,
labels=labels
)
```
![vertex-label-annotator-custom-example](https://media.roboflow.com/
supervision-annotator-examples/vertex-label-annotator-custom-example.png)
"""
assert isinstance(scene, np.ndarray)
font = cv2.FONT_HERSHEY_SIMPLEX
skeletons_count, points_count, _ = key_points.xy.shape
if skeletons_count == 0:
return scene
anchors = key_points.xy.reshape(points_count * skeletons_count, 2).astype(int)
mask = np.all(anchors != 0, axis=1)
if not np.any(mask):
return scene
colors = self.preprocess_and_validate_colors(
colors=self.color,
points_count=points_count,
skeletons_count=skeletons_count,
)
text_colors = self.preprocess_and_validate_colors(
colors=self.text_color,
points_count=points_count,
skeletons_count=skeletons_count,
)
labels = self.preprocess_and_validate_labels(
labels=labels, points_count=points_count, skeletons_count=skeletons_count
)
anchors = anchors[mask]
colors = colors[mask]
text_colors = text_colors[mask]
labels = labels[mask]
xyxy = np.array(
[
self.get_text_bounding_box(
text=label,
font=font,
text_scale=self.text_scale,
text_thickness=self.text_thickness,
center_coordinates=tuple(anchor),
)
for anchor, label in zip(anchors, labels)
]
)
xyxy_padded = pad_boxes(xyxy=xyxy, px=self.text_padding)
for text, color, text_color, box, box_padded in zip(
labels, colors, text_colors, xyxy, xyxy_padded
):
draw_rounded_rectangle(
scene=scene,
rect=Rect.from_xyxy(box_padded),
color=color,
border_radius=self.border_radius,
)
cv2.putText(
img=scene,
text=text,
org=(box[0], box[3]),
fontFace=font,
fontScale=self.text_scale,
color=text_color.as_bgr(),
thickness=self.text_thickness,
lineType=cv2.LINE_AA,
)
return scene
@staticmethod
def get_text_bounding_box(
text: str,
font: int,
text_scale: float,
text_thickness: int,
center_coordinates: Tuple[int, int],
) -> Tuple[int, int, int, int]:
text_w, text_h = cv2.getTextSize(
text=text,
fontFace=font,
fontScale=text_scale,
thickness=text_thickness,
)[0]
center_x, center_y = center_coordinates
return (
center_x - text_w // 2,
center_y - text_h // 2,
center_x + text_w // 2,
center_y + text_h // 2,
)
@staticmethod
def preprocess_and_validate_labels(
labels: Optional[List[str]], points_count: int, skeletons_count: int
) -> np.ndarray:
if labels and len(labels) != points_count:
raise ValueError(
f"Number of labels ({len(labels)}) must match number of key points "
f"({points_count})."
)
if labels is None:
labels = [str(i) for i in range(points_count)]
return np.array(labels * skeletons_count)
@staticmethod
def preprocess_and_validate_colors(
colors: Optional[Union[Color, List[Color]]],
points_count: int,
skeletons_count: int,
) -> np.ndarray:
if isinstance(colors, list) and len(colors) != points_count:
raise ValueError(
f"Number of colors ({len(colors)}) must match number of key points "
f"({points_count})."
)
return (
np.array(colors * skeletons_count)
if isinstance(colors, list)
else np.array([colors] * points_count * skeletons_count)
)
+344 -48
View File
@@ -18,12 +18,13 @@ 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 annotators.
integration with Supervision [keypoints annotators](/keypoint/annotators).
=== "Ultralytics"
Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics)
method, which accepts model results.
method, which accepts [YOLOv8](https://github.com/ultralytics/ultralytics)
pose result.
```python
import cv2
@@ -32,20 +33,72 @@ class KeyPoints:
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s-pose.pt')
result = model(image)[0]
key_points = sv.KeyPoints.from_ultralytics(result)
```
=== "Inference"
Use [`sv.KeyPoints.from_inference`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_inference)
method, which accepts [Inference](https://inference.roboflow.com/) pose result.
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id=<POSE_MODEL_ID>, api_key=<ROBOFLOW_API_KEY>)
result = model.infer(image)[0]
key_points = sv.KeyPoints.from_inference(result)
```
=== "MediaPipe"
Use [`sv.KeyPoints.from_mediapipe`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe)
method, which accepts [MediaPipe](https://github.com/google-ai-edge/mediapipe)
pose result.
```python
import cv2
import mediapipe as mp
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image_height, image_width, _ = image.shape
mediapipe_image = mp.Image(
image_format=mp.ImageFormat.SRGB,
data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
options = mp.tasks.vision.PoseLandmarkerOptions(
base_options=mp.tasks.BaseOptions(
model_asset_path="pose_landmarker_heavy.task"
),
running_mode=mp.tasks.vision.RunningMode.IMAGE,
num_poses=2)
PoseLandmarker = mp.tasks.vision.PoseLandmarker
with PoseLandmarker.create_from_options(options) as landmarker:
pose_landmarker_result = landmarker.detect(mediapipe_image)
key_points = sv.KeyPoints.from_mediapipe(
pose_landmarker_result, (image_width, image_height))
```
Attributes:
xy (np.ndarray): An array of shape `(n, 2)` containing
the bounding boxes coordinates in format `[x1, y1]`
xy (np.ndarray): An array of shape `(n, m, 2)` containing
`n` detected objects, each composed of `m` equally-sized
sets of keypoints, where each point is `[x, y]`.
confidence (Optional[np.ndarray]): An array of shape
`(n,)` containing the confidence scores of the keypoint keypoints.
`(n, m)` containing the confidence scores of each keypoint.
class_id (Optional[np.ndarray]): An array of shape
`(n,)` containing the class ids of the keypoint keypoints.
`(n,)` containing the class ids of the detected objects.
data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional
data where each key is a string representing the data type, and the value
is either a NumPy array or a list of corresponding data.
is either a NumPy array or a list of corresponding data of length `n`
(one entry per detected object).
""" # noqa: E501 // docs
xy: npt.NDArray[np.float32]
@@ -63,7 +116,7 @@ class KeyPoints:
def __len__(self) -> int:
"""
Returns the number of keypoints in the keypoints object.
Returns the number of keypoints in the `sv.KeyPoints` object.
"""
return len(self.xy)
@@ -81,7 +134,7 @@ class KeyPoints:
]:
"""
Iterates over the Keypoint object and yield a tuple of
`(xy, confidence, class_id, data)` for each keypoint detection.
`(xy, confidence, class_id, data)` for each object detection.
"""
for i in range(len(self.xy)):
yield (
@@ -106,18 +159,17 @@ class KeyPoints:
"""
Create a `sv.KeyPoints` object from the [Roboflow](https://roboflow.com/)
API inference result or the [Inference](https://inference.roboflow.com/)
package results. When a keypoint detection model is used, this method
extracts the keypoint coordinates, class IDs, confidences, and class names.
package results.
Args:
inference_result (dict, any): The result from the
Roboflow API or Inference package containing predictions with keypoints.
Returns:
(KeyPoints): A KeyPoints object containing the keypoint coordinates,
class IDs, and confidences of each keypoint.
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
and class names, and confidences of each keypoint.
Example:
Examples:
```python
import cv2
import supervision as sv
@@ -151,8 +203,6 @@ class KeyPoints:
"You can retrieve it like so: inference_result = model.infer(image)[0]"
)
# Unpack the result if received from inference.get_model,
# rather than inference_sdk.InferenceHTTPClient
with suppress(AttributeError):
inference_result = inference_result.dict(exclude_none=True, by_alias=True)
@@ -185,20 +235,147 @@ class KeyPoints:
data=data,
)
@classmethod
def from_mediapipe(
cls, mediapipe_results, resolution_wh: Tuple[int, int]
) -> KeyPoints:
"""
Creates a `sv.KeyPoints` instance from a
[MediaPipe](https://github.com/google-ai-edge/mediapipe)
pose landmark detection inference result.
Args:
mediapipe_results (Union[PoseLandmarkerResult, FaceLandmarkerResult, SolutionOutputs]):
The output results from Mediapipe. It support pose and face landmarks
from `PoseLandmaker`, `FaceLandmarker` and the legacy ones
from `Pose` and `FaceMesh`.
resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
representing the resolution of the frame.
Returns:
A `sv.KeyPoints` object containing the keypoint coordinates and
confidences of each keypoint.
!!! tip
Before you start, download model bundles from the
[MediaPipe website](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker/index#models).
Examples:
```python
import cv2
import mediapipe as mp
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image_height, image_width, _ = image.shape
mediapipe_image = mp.Image(
image_format=mp.ImageFormat.SRGB,
data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
options = mp.tasks.vision.PoseLandmarkerOptions(
base_options=mp.tasks.BaseOptions(
model_asset_path="pose_landmarker_heavy.task"
),
running_mode=mp.tasks.vision.RunningMode.IMAGE,
num_poses=2)
PoseLandmarker = mp.tasks.vision.PoseLandmarker
with PoseLandmarker.create_from_options(options) as landmarker:
pose_landmarker_result = landmarker.detect(mediapipe_image)
key_points = sv.KeyPoints.from_mediapipe(
pose_landmarker_result, (image_width, image_height))
```
```python
import cv2
import mediapipe as mp
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image_height, image_width, _ = image.shape
mediapipe_image = mp.Image(
image_format=mp.ImageFormat.SRGB,
data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
options = mp.tasks.vision.FaceLandmarkerOptions(
base_options=mp.tasks.BaseOptions(
model_asset_path="face_landmarker.task"
),
output_face_blendshapes=True,
output_facial_transformation_matrixes=True,
num_faces=2)
FaceLandmarker = mp.tasks.vision.FaceLandmarker
with FaceLandmarker.create_from_options(options) as landmarker:
face_landmarker_result = landmarker.detect(mediapipe_image)
key_points = sv.KeyPoints.from_mediapipe(
face_landmarker_result, (image_width, image_height))
```
""" # noqa: E501 // docs
if hasattr(mediapipe_results, "pose_landmarks"):
results = mediapipe_results.pose_landmarks
if not isinstance(mediapipe_results.pose_landmarks, list):
if mediapipe_results.pose_landmarks is None:
results = []
else:
results = [
[
landmark
for landmark in mediapipe_results.pose_landmarks.landmark
]
]
elif hasattr(mediapipe_results, "face_landmarks"):
results = mediapipe_results.face_landmarks
elif hasattr(mediapipe_results, "multi_face_landmarks"):
if mediapipe_results.multi_face_landmarks is None:
results = []
else:
results = [
face_landmark.landmark
for face_landmark in mediapipe_results.multi_face_landmarks
]
if len(results) == 0:
return cls.empty()
xy = []
confidence = []
for pose in results:
prediction_xy = []
prediction_confidence = []
for landmark in pose:
keypoint_xy = [
landmark.x * resolution_wh[0],
landmark.y * resolution_wh[1],
]
prediction_xy.append(keypoint_xy)
prediction_confidence.append(landmark.visibility)
xy.append(prediction_xy)
confidence.append(prediction_confidence)
return cls(
xy=np.array(xy, dtype=np.float32),
confidence=np.array(confidence, dtype=np.float32),
)
@classmethod
def from_ultralytics(cls, ultralytics_results) -> KeyPoints:
"""
Creates a Keypoints instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
Creates a `sv.KeyPoints` instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) pose inference result.
Args:
ultralytics_results (ultralytics.engine.results.Keypoints):
The output Results instance from YOLOv8
Returns:
KeyPoints: A new Keypoints object.
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
and class names, and confidences of each keypoint.
Example:
Examples:
```python
import cv2
import supervision as sv
@@ -206,8 +383,9 @@ class KeyPoints:
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s-pose.pt')
result = model(image)[0]
keypoints = sv.KeyPoints.from_ultralytics(result)
key_points = sv.KeyPoints.from_ultralytics(result)
```
"""
if ultralytics_results.keypoints.xy.numel() == 0:
@@ -221,38 +399,155 @@ class KeyPoints:
data = {CLASS_NAME_DATA_FIELD: class_names}
return cls(xy, class_id, confidence, data)
def __getitem__(
self, index: Union[int, slice, List[int], np.ndarray, str]
) -> Union["KeyPoints", List, np.ndarray, None]:
@classmethod
def from_yolo_nas(cls, yolo_nas_results) -> KeyPoints:
"""
Get a subset of the KeyPoints object or access an item from its data field.
When provided with an integer, slice, list of integers, or a numpy array, this
method returns a new KeyPoints object that represents a subset of the original
keypoints. When provided with a string, it accesses the corresponding item in
the data dictionary.
Create a `sv.KeyPoints` instance from a [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS-POSE.md)
pose inference results.
Args:
index (Union[int, slice, List[int], np.ndarray, str]): The index, indices,
or key to access a subset of the KeyPoints or an item from the data.
yolo_nas_results (ImagePoseEstimationPrediction): The output object from
YOLO NAS.
Returns:
Union[KeyPoints, Any]: A subset of the KeyPoints object or an item from
the data field.
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
and class names, and confidences of each keypoint.
Examples:
```python
import cv2
import torch
import supervision as sv
import super_gradients
image = cv2.imread(<SOURCE_IMAGE_PATH>)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = super_gradients.training.models.get(
"yolo_nas_pose_s", pretrained_weights="coco_pose").to(device)
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()
xy = yolo_nas_results.prediction.poses[:, :, :2]
confidence = yolo_nas_results.prediction.poses[:, :, 2]
# yolo_nas_results treats params differently.
# prediction.labels may not exist, whereas class_names might be None
if hasattr(yolo_nas_results.prediction, "labels"):
class_id = yolo_nas_results.prediction.labels # np.array[int]
else:
class_id = None
data = {}
if class_id is not None and yolo_nas_results.class_names is not None:
class_names = []
for c_id in class_id:
name = yolo_nas_results.class_names[c_id] # tuple[str]
class_names.append(name)
data[CLASS_NAME_DATA_FIELD] = class_names
return cls(
xy=xy,
confidence=confidence,
class_id=class_id,
data=data,
)
@classmethod
def from_detectron2(cls, detectron2_results) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the
[Detectron2](https://github.com/facebookresearch/detectron2) inference result.
Args:
detectron2_results: The output of a
Detectron2 model containing instances with prediction data.
Returns:
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
and class names, and confidences of each keypoint.
Example:
```python
import cv2
import supervision as sv
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
image = cv2.imread(<SOURCE_IMAGE_PATH>)
cfg = get_cfg()
cfg.merge_from_file(<CONFIG_PATH>)
cfg.MODEL.WEIGHTS = <WEIGHTS_PATH>
predictor = DefaultPredictor(cfg)
result = predictor(image)
keypoints = sv.KeyPoints.from_detectron2(result)
```
"""
if hasattr(detectron2_results["instances"], "pred_keypoints"):
if detectron2_results["instances"].pred_keypoints.cpu().numpy().size == 0:
return cls.empty()
return cls(
xy=detectron2_results["instances"]
.pred_keypoints.cpu()
.numpy()[:, :, :2],
confidence=detectron2_results["instances"]
.pred_keypoints.cpu()
.numpy()[:, :, 2],
class_id=detectron2_results["instances"]
.pred_classes.cpu()
.numpy()
.astype(int),
)
else:
return cls.empty()
def __getitem__(
self, index: Union[int, slice, List[int], np.ndarray, str]
) -> Union[KeyPoints, List, np.ndarray, None]:
"""
Get a subset of the `sv.KeyPoints` object or access an item from its data field.
When provided with an integer, slice, list of integers, or a numpy array, this
method returns a new `sv.KeyPoints` object that represents a subset of the
original `sv.KeyPoints`. When provided with a string, it accesses the
corresponding item in the data dictionary.
Args:
index (Union[int, slice, List[int], np.ndarray, str]): The index, indices,
or key to access a subset of the `sv.KeyPoints` or an item from the
data.
Returns:
A subset of the `sv.KeyPoints` object or an item from the data field.
Examples:
```python
import supervision as sv
keypoints = sv.KeyPoints()
key_points = sv.KeyPoints()
first_detection = keypoints[0]
first_10_keypoints = keypoints[0:10]
some_keypoints = keypoints[[0, 2, 4]]
class_0_keypoints = keypoints[keypoints.class_id == 0]
high_confidence_keypoints = keypoints[keypoints.confidence > 0.5]
# access the first keypoint using an integer index
key_points[0]
feature_vector = keypoints['feature_vector']
# access the first 10 keypoints using index slice
key_points[0:10]
# access selected keypoints using a list of indices
key_points[[0, 2, 4]]
# access keypoints with selected class_id
key_points[key_points.class_id == 0]
# access keypoints with confidence greater than 0.5
key_points[key_points.confidence > 0.5]
```
"""
if isinstance(index, str):
@@ -268,13 +563,13 @@ class KeyPoints:
def __setitem__(self, key: str, value: Union[np.ndarray, List]):
"""
Set a value in the data dictionary of the KeyPoints object.
Set a value in the data dictionary of the `sv.KeyPoints` object.
Args:
key (str): The key in the data dictionary to set.
value (Union[np.ndarray, List]): The value to set for the key.
Example:
Examples:
```python
import cv2
import supervision as sv
@@ -286,7 +581,7 @@ class KeyPoints:
result = model(image)[0]
keypoints = sv.KeyPoints.from_ultralytics(result)
keypoints['names'] = [
keypoints['class_name'] = [
model.model.names[class_id]
for class_id
in keypoints.class_id
@@ -307,12 +602,13 @@ class KeyPoints:
Create an empty Keypoints object with no keypoints.
Returns:
(KeyPoints): An empty Keypoints object.
An empty `sv.KeyPoints` object.
Example:
Examples:
```python
from supervision import Keypoints
empty_keypoints = Keypoints.empty()
import supervision as sv
key_points = sv.KeyPoints.empty()
```
"""
return cls(xy=np.empty((0, 0, 2), dtype=np.float32))
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
from supervision.metrics.core import CLASS_ID_NONE, Metric, MetricTarget
from supervision.metrics.mean_average_precision import (
MeanAveragePrecision,
MeanAveragePrecisionResult,
)
from supervision.metrics.utils.object_size import (
ObjectSizeCategory,
get_detection_size_category,
get_object_size_category,
)
+50
View File
@@ -0,0 +1,50 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from enum import Enum
from typing import Any
CLASS_ID_NONE = -1
"""Used by metrics module as class ID, when none is present"""
class Metric(ABC):
"""
The base class for all supervision metrics.
"""
@abstractmethod
def update(self, *args, **kwargs) -> "Metric":
"""
Add data to the metric, without computing the result.
Return the metric itself to allow method chaining.
"""
raise NotImplementedError
@abstractmethod
def reset(self) -> None:
"""
Reset internal metric state.
"""
raise NotImplementedError
@abstractmethod
def compute(self, *args, **kwargs) -> Any:
"""
Compute the metric from the internal state and return the result.
"""
raise NotImplementedError
class MetricTarget(Enum):
"""
Specifies what type of detection is used to compute the metric.
* BOXES: xyxy bounding boxes
* MASKS: Binary masks
* ORIENTED_BOUNDING_BOXES: Oriented bounding boxes (OBB)
"""
BOXES = "boxes"
MASKS = "masks"
ORIENTED_BOUNDING_BOXES = "obb"
+9 -11
View File
@@ -391,11 +391,10 @@ class ConfusionMatrix:
```
"""
predictions, targets = [], []
for img_name, img in dataset.images.items():
predictions_batch = callback(img)
for _, image, annotation in dataset:
predictions_batch = callback(image)
predictions.append(predictions_batch)
targets_batch = dataset.annotations[img_name]
targets.append(targets_batch)
targets.append(annotation)
return cls.from_detections(
predictions=predictions,
targets=targets,
@@ -604,11 +603,10 @@ class MeanAveragePrecision:
```
"""
predictions, targets = [], []
for img_name, img in dataset.images.items():
predictions_batch = callback(img)
for _, image, annotation in dataset:
predictions_batch = callback(image)
predictions.append(predictions_batch)
targets_batch = dataset.annotations[img_name]
targets.append(targets_batch)
targets.append(annotation)
return cls.from_detections(
predictions=predictions,
targets=targets,
@@ -666,12 +664,12 @@ class MeanAveragePrecision:
np.array([[1.0, 1.0, 2.0, 2.0, 2, 0.8]])
]
mean_average_precison = sv.MeanAveragePrecision.from_tensors(
mean_average_precision = sv.MeanAveragePrecision.from_tensors(
predictions=predictions,
targets=targets,
)
print(mean_average_precison.map50_95)
print(mean_average_precision.map50_95)
# 0.6649
```
"""
@@ -808,7 +806,7 @@ class MeanAveragePrecision:
prediction_confidence (np.ndarray): Objectness value from 0-1.
prediction_class_ids (np.ndarray): Predicted object classes.
true_class_ids (np.ndarray): True object classes.
eps (float, optional): Small value to prevent division by zero.
eps (float): Small value to prevent division by zero.
Returns:
np.ndarray: Average precision for different IoU levels.
@@ -0,0 +1,562 @@
from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, 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 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 MeanAveragePrecision(Metric):
def __init__(
self,
metric_target: MetricTarget = MetricTarget.BOXES,
class_agnostic: bool = False,
):
"""
Initialize the Mean Average Precision metric.
Args:
metric_target (MetricTarget): The type of detection data to use.
class_agnostic (bool): Whether to treat all data as a single class.
"""
self._metric_target = metric_target
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
raise NotImplementedError(
"Mean Average Precision is not implemented for oriented bounding boxes."
)
self._class_agnostic = class_agnostic
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]],
) -> MeanAveragePrecision:
"""
Add new predictions and targets to the metric, but do not compute the result.
Args:
predictions (Union[Detections, List[Detections]]): The predicted detections.
targets (Union[Detections, List[Detections]]): The ground-truth detections.
Returns:
(MeanAveragePrecision): The updated metric instance.
"""
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,
) -> MeanAveragePrecisionResult:
"""
Calculate Mean Average Precision based on predicted and ground-truth
detections at different threshold.
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.
Example:
```python
import supervision as sv
from supervision.metrics import MeanAveragePrecision
predictions = sv.Detections(...)
targets = sv.Detections(...)
map_metric = MeanAveragePrecision()
map_result = map_metric.update(predictions, targets).compute()
print(map_result)
print(map_result.map50_95)
map_result.plot()
```
"""
result = self._compute(self._predictions_list, self._targets_list)
small_predictions = []
small_targets = []
for predictions, targets in zip(self._predictions_list, self._targets_list):
small_predictions.append(
self._filter_detections_by_size(predictions, ObjectSizeCategory.SMALL)
)
small_targets.append(
self._filter_detections_by_size(targets, ObjectSizeCategory.SMALL)
)
result.small_objects = self._compute(small_predictions, small_targets)
medium_predictions = []
medium_targets = []
for predictions, targets in zip(self._predictions_list, self._targets_list):
medium_predictions.append(
self._filter_detections_by_size(predictions, ObjectSizeCategory.MEDIUM)
)
medium_targets.append(
self._filter_detections_by_size(targets, ObjectSizeCategory.MEDIUM)
)
result.medium_objects = self._compute(medium_predictions, medium_targets)
large_predictions = []
large_targets = []
for predictions, targets in zip(self._predictions_list, self._targets_list):
large_predictions.append(
self._filter_detections_by_size(predictions, ObjectSizeCategory.LARGE)
)
large_targets.append(
self._filter_detections_by_size(targets, ObjectSizeCategory.LARGE)
)
result.large_objects = self._compute(large_predictions, large_targets)
return result
def _compute(
self,
predictions_list: List[Detections],
targets_list: List[Detections],
) -> MeanAveragePrecisionResult:
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,
)
)
# 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()
else:
map50, map75, map50_95 = 0, 0, 0
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,
)
@staticmethod
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.
Args:
recall (np.ndarray): The recall curve.
precision (np.ndarray): The precision curve.
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)
return average_precision
@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 _average_precisions_per_class(
matches: np.ndarray,
prediction_confidence: np.ndarray,
prediction_class_ids: np.ndarray,
true_class_ids: np.ndarray,
) -> np.ndarray:
"""
Compute the average precision, given the recall and precision curves.
Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
Args:
matches (np.ndarray): True positives.
prediction_confidence (np.ndarray): Objectness value from 0-1.
prediction_class_ids (np.ndarray): Predicted object classes.
true_class_ids (np.ndarray): True object classes.
eps (float, optional): Small value to prevent division by zero.
Returns:
(np.ndarray): Average precision for different IoU levels.
"""
eps = 1e-16
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)
num_classes = unique_classes.shape[0]
average_precisions = np.zeros((num_classes, matches.shape[1]))
for class_idx, class_id in enumerate(unique_classes):
is_class = prediction_class_ids == class_id
total_true = class_counts[class_idx]
total_prediction = is_class.sum()
if total_prediction == 0 or total_true == 0:
continue
false_positives = (1 - matches[is_class]).cumsum(0)
true_positives = matches[is_class].cumsum(0)
true_negatives = total_true - true_positives
recall = true_positives / (true_positives + true_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(
recall[:, iou_level_idx], precision[:, iou_level_idx]
)
)
return average_precisions
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 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) -> np.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 _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
@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"""
map50: float
"""Mean Average Precision at IoU threshold of 0.5"""
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"""
metric_target: MetricTarget
"""
Defines the type of data used for the metric - boxes, masks or
oriented bounding boxes.
"""
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.
Example:
```python
print(map_result)
```
"""
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:"
)
for class_id, ap in enumerate(self.per_class_ap50_95):
out_str += f"\n {class_id}: {ap}"
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}"
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}"
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}"
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 = {
"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():
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
# Average precisions are currently not included in the DataFrame.
return pd.DataFrame(
pandas_data,
index=[0],
)
def plot(self):
"""
Plot the mAP results.
"""
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",
]
values += [
self.small_objects.map50_95,
self.small_objects.map50,
self.small_objects.map75,
]
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",
]
values += [
self.medium_objects.map50_95,
self.medium_objects.map50,
self.medium_objects.map75,
]
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",
]
values += [
self.large_objects.map50_95,
self.large_objects.map50,
self.large_objects.map75,
]
colors += [LEGACY_COLOR_PALETTE[4]] * 3
plt.rcParams["font.family"] = "monospace"
_, ax = plt.subplots(figsize=(10, 6))
ax.set_ylim(0, 1)
ax.set_ylabel("Value", fontweight="bold")
ax.set_title("Mean Average Precision", 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()
@@ -0,0 +1,300 @@
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()
+155
View File
@@ -0,0 +1,155 @@
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
import numpy as np
import numpy.typing as npt
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.metrics.core import MetricTarget
if TYPE_CHECKING:
from supervision.detection.core import Detections
SIZE_THRESHOLDS = (32**2, 96**2)
class ObjectSizeCategory(Enum):
ANY = -1
SMALL = 1
MEDIUM = 2
LARGE = 3
def get_object_size_category(
data: npt.NDArray, metric_target: MetricTarget
) -> npt.NDArray[np.int_]:
"""
Get the size category of an object. Distinguish based on the metric target.
Args:
data (np.ndarray): The object data, shaped (N, ...).
metric_target (MetricTarget): Determines whether boxes, masks or
oriented bounding boxes are used.
Returns:
(np.ndarray) The size category of each object, matching
the enum values of ObjectSizeCategory. Shaped (N,).
"""
if metric_target == MetricTarget.BOXES:
return get_bbox_size_category(data)
if metric_target == MetricTarget.MASKS:
return get_mask_size_category(data)
if metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
return get_obb_size_category(data)
raise ValueError("Invalid metric type")
def get_bbox_size_category(xyxy: npt.NDArray[np.float32]) -> npt.NDArray[np.int_]:
"""
Get the size category of a bounding boxes array.
Args:
xyxy (np.ndarray): The bounding boxes array shaped (N, 4).
Returns:
(np.ndarray) The size category of each bounding box, matching
the enum values of ObjectSizeCategory. Shaped (N,).
"""
if len(xyxy.shape) != 2 or xyxy.shape[1] != 4:
raise ValueError("Bounding boxes must be shaped (N, 4)")
width = xyxy[:, 2] - xyxy[:, 0]
height = xyxy[:, 3] - xyxy[:, 1]
areas = width * height
result = np.full(areas.shape, ObjectSizeCategory.ANY.value)
SM, LG = SIZE_THRESHOLDS
result[areas < SM] = ObjectSizeCategory.SMALL.value
result[(areas >= SM) & (areas < LG)] = ObjectSizeCategory.MEDIUM.value
result[areas >= LG] = ObjectSizeCategory.LARGE.value
return result
def get_mask_size_category(mask: npt.NDArray[np.bool_]) -> npt.NDArray[np.int_]:
"""
Get the size category of detection masks.
Args:
mask (np.ndarray): The mask array shaped (N, H, W).
Returns:
(np.ndarray) The size category of each mask, matching
the enum values of ObjectSizeCategory. Shaped (N,).
"""
if len(mask.shape) != 3:
raise ValueError("Masks must be shaped (N, H, W)")
areas = np.sum(mask, axis=(1, 2))
result = np.full(areas.shape, ObjectSizeCategory.ANY.value)
SM, LG = SIZE_THRESHOLDS
result[areas < SM] = ObjectSizeCategory.SMALL.value
result[(areas >= SM) & (areas < LG)] = ObjectSizeCategory.MEDIUM.value
result[areas >= LG] = ObjectSizeCategory.LARGE.value
return result
def get_obb_size_category(xyxyxyxy: npt.NDArray[np.float32]) -> npt.NDArray[np.int_]:
"""
Get the size category of a oriented bounding boxes array.
Args:
xyxyxyxy (np.ndarray): The bounding boxes array shaped (N, 8).
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)")
# Shoelace formula
x1, y1, x2, y2, x3, y3, x4, y4 = xyxyxyxy.T
areas = 0.5 * np.abs(
(x1 * y2 + x2 * y3 + x3 * y4 + x4 * y1)
- (x2 * y1 + x3 * y2 + x4 * y3 + x1 * y4)
)
result = np.full(areas.shape, ObjectSizeCategory.ANY.value)
SM, LG = SIZE_THRESHOLDS
result[areas < SM] = ObjectSizeCategory.SMALL.value
result[(areas >= SM) & (areas < LG)] = ObjectSizeCategory.MEDIUM.value
result[areas >= LG] = ObjectSizeCategory.LARGE.value
return result
def get_detection_size_category(
detections: Detections, metric_target: MetricTarget = MetricTarget.BOXES
) -> npt.NDArray[np.int_]:
"""
Get the size category of a detections object.
Args:
xyxyxyxy (np.ndarray): The bounding boxes array shaped (N, 8).
metric_target (MetricTarget): Determines whether boxes, masks or
oriented bounding boxes are used.
Returns:
(np.ndarray) The size category of each bounding box, matching
the enum values of ObjectSizeCategory. Shaped (N,).
"""
if metric_target == MetricTarget.BOXES:
return get_bbox_size_category(detections.xyxy)
if metric_target == MetricTarget.MASKS:
if detections.mask is None:
raise ValueError("Detections mask is not available")
return get_mask_size_category(detections.mask)
if metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
if detections.data.get(ORIENTED_BOX_COORDINATES) is None:
raise ValueError("Detections oriented bounding boxes are not available")
return get_obb_size_category(
np.array(detections.data[ORIENTED_BOX_COORDINATES])
)
raise ValueError("Invalid metric type")
+70
View File
@@ -0,0 +1,70 @@
from typing import List, Tuple
import numpy as np
import numpy.typing as npt
def ensure_pandas_installed():
try:
import pandas # noqa
except ImportError:
raise ImportError(
"`metrics` extra is required to run the function."
" 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)
+8 -33
View File
@@ -7,7 +7,6 @@ from supervision.detection.utils import box_iou_batch
from supervision.tracker.byte_tracker import matching
from supervision.tracker.byte_tracker.basetrack import BaseTrack, TrackState
from supervision.tracker.byte_tracker.kalman_filter import KalmanFilter
from supervision.utils.internal import deprecated_parameter
class STrack(BaseTrack):
@@ -197,48 +196,24 @@ class ByteTrack:
</video>
Parameters:
track_activation_threshold (float, optional): Detection confidence threshold
track_activation_threshold (float): Detection confidence threshold
for track activation. Increasing track_activation_threshold improves accuracy
and stability but might miss true detections. Decreasing it increases
completeness but risks introducing noise and instability.
lost_track_buffer (int, optional): Number of frames to buffer when a track is lost.
lost_track_buffer (int): Number of frames to buffer when a track is lost.
Increasing lost_track_buffer enhances occlusion handling, significantly
reducing the likelihood of track fragmentation or disappearance caused
by brief detection gaps.
minimum_matching_threshold (float, optional): Threshold for matching tracks with detections.
minimum_matching_threshold (float): Threshold for matching tracks with detections.
Increasing minimum_matching_threshold improves accuracy but risks fragmentation.
Decreasing it improves completeness but risks false positives and drift.
frame_rate (int, optional): The frame rate of the video.
minimum_consecutive_frames (int, optional): Number of consecutive frames that an object must
frame_rate (int): The frame rate of the video.
minimum_consecutive_frames (int): Number of consecutive frames that an object must
be tracked before it is considered a 'valid' track.
Increasing minimum_consecutive_frames prevents the creation of accidental tracks from
false detection or double detection, but risks missing shorter tracks.
""" # noqa: E501 // docs
@deprecated_parameter(
old_parameter="track_buffer",
new_parameter="lost_track_buffer",
map_function=lambda x: x,
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
@deprecated_parameter(
old_parameter="track_thresh",
new_parameter="track_activation_threshold",
map_function=lambda x: x,
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
@deprecated_parameter(
old_parameter="match_thresh",
new_parameter="minimum_matching_threshold",
map_function=lambda x: x,
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
def __init__(
self,
track_activation_threshold: float = 0.25,
@@ -276,7 +251,7 @@ class ByteTrack:
model = YOLO(<MODEL_PATH>)
tracker = sv.ByteTrack()
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
def callback(frame: np.ndarray, index: int) -> np.ndarray:
@@ -286,7 +261,7 @@ class ByteTrack:
labels = [f"#{tracker_id}" for tracker_id in detections.tracker_id]
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=frame.copy(), detections=detections)
annotated_frame = label_annotator.annotate(
scene=annotated_frame, detections=detections, labels=labels)
@@ -487,7 +462,7 @@ class ByteTrack:
self.lost_tracks = sub_tracks(self.lost_tracks, self.tracked_tracks)
self.lost_tracks.extend(lost_stracks)
self.lost_tracks = sub_tracks(self.lost_tracks, self.removed_tracks)
self.removed_tracks.extend(removed_stracks)
self.removed_tracks = removed_stracks
self.tracked_tracks, self.lost_tracks = remove_duplicate_tracks(
self.tracked_tracks, self.lost_tracks
)
+36 -7
View File
@@ -8,11 +8,13 @@ from PIL import Image
from supervision.annotators.base import ImageType
def convert_for_annotation_method(annotate_func):
def ensure_cv2_image_for_annotation(annotate_func):
"""
Decorates `BaseAnnotator.annotate` implementations, converts scene to
an image type used internally by the annotators, converts back when annotation
is complete.
Assumes the annotators modify the scene in-place.
"""
@wraps(annotate_func)
@@ -21,19 +23,22 @@ def convert_for_annotation_method(annotate_func):
return annotate_func(self, scene, *args, **kwargs)
if isinstance(scene, Image.Image):
scene = pillow_to_cv2(scene)
annotated = annotate_func(self, scene, *args, **kwargs)
return cv2_to_pillow(image=annotated)
scene_np = pillow_to_cv2(scene)
annotated_np = annotate_func(self, scene_np, *args, **kwargs)
scene.paste(cv2_to_pillow(annotated_np))
return scene
raise ValueError(f"Unsupported image type: {type(scene)}")
return wrapper
def convert_for_image_processing(image_processing_fun):
def ensure_cv2_image_for_processing(image_processing_fun):
"""
Decorates image processing functions that accept np.ndarray, converting `image` to
np.ndarray, converts back when processing is complete.
Assumes the annotators do NOT modify the scene in-place.
"""
@wraps(image_processing_fun)
@@ -44,13 +49,37 @@ def convert_for_image_processing(image_processing_fun):
if isinstance(image, Image.Image):
scene = pillow_to_cv2(image)
annotated = image_processing_fun(scene, *args, **kwargs)
return cv2_to_pillow(image=annotated)
return cv2_to_pillow(annotated)
raise ValueError(f"Unsupported image type: {type(image)}")
return wrapper
def ensure_pil_image_for_annotation(annotate_func):
"""
Decorates image processing functions that accept np.ndarray, converting `image` to
PIL image, converts back when processing is complete.
Assumes the annotators modify the scene in-place.
"""
@wraps(annotate_func)
def wrapper(self, scene: ImageType, *args, **kwargs):
if isinstance(scene, np.ndarray):
scene_pil = cv2_to_pillow(scene)
annotated_pil = annotate_func(self, scene_pil, *args, **kwargs)
np.copyto(scene, pillow_to_cv2(annotated_pil))
return scene
if isinstance(scene, Image.Image):
return annotate_func(self, scene, *args, **kwargs)
raise ValueError(f"Unsupported image type: {type(scene)}")
return wrapper
def images_to_cv2(images: List[ImageType]) -> List[np.ndarray]:
"""
Converts images provided either as Pillow images or OpenCV
@@ -67,7 +96,7 @@ def images_to_cv2(images: List[ImageType]) -> List[np.ndarray]:
result = []
for image in images:
if issubclass(type(image), Image.Image):
image = pillow_to_cv2(image=image)
image = pillow_to_cv2(image)
result.append(image)
return result
+19 -19
View File
@@ -1,6 +1,6 @@
import json
from pathlib import Path
from typing import List, Optional, Union
from typing import Dict, List, Optional, Union
import numpy as np
import yaml
@@ -57,20 +57,20 @@ def list_files_with_extensions(
return files_with_extensions
def read_txt_file(file_path: str, skip_empty: bool = False) -> List[str]:
def read_txt_file(file_path: Union[str, Path], skip_empty: bool = False) -> List[str]:
"""
Read a text file and return a list of strings without newline characters.
Optionally skip empty lines.
Args:
file_path (str): The path to the text file.
file_path (Union[str, Path]): The file path as a string or Path object.
skip_empty (bool): If True, skip lines that are empty or contain only
whitespace. Default is False.
Returns:
List[str]: A list of strings representing the lines in the text file.
"""
with open(file_path, "r") as file:
with open(str(file_path), "r") as file:
if skip_empty:
lines = [line.rstrip("\n") for line in file if line.strip()]
else:
@@ -79,71 +79,71 @@ def read_txt_file(file_path: str, skip_empty: bool = False) -> List[str]:
return lines
def save_text_file(lines: List[str], file_path: str):
def save_text_file(lines: List[str], file_path: Union[str, Path]) -> None:
"""
Write a list of strings to a text file, each string on a new line.
Args:
lines (List[str]): The list of strings to be written to the file.
file_path (str): The path to the text file.
file_path (Union[str, Path]): The file path as a string or Path object.
"""
with open(file_path, "w") as file:
with open(str(file_path), "w") as file:
for line in lines:
file.write(line + "\n")
def read_json_file(file_path: str) -> dict:
def read_json_file(file_path: Union[str, Path]) -> Dict:
"""
Read a json file and return a dict.
Args:
file_path (str): The path to the json file.
file_path (Union[str, Path]): The file path as a string or Path object.
Returns:
dict: A dict of annotations information
"""
with open(file_path, "r") as file:
with open(str(file_path), "r") as file:
data = json.load(file)
return data
def save_json_file(data: dict, file_path: str, indent: int = 3) -> None:
def save_json_file(data: dict, file_path: Union[str, Path], indent: int = 3) -> None:
"""
Write a dict to a json file.
Args:
indent:
data (dict): dict with unique keys and value as pair.
file_path (str): The path to the json file.
file_path (Union[str, Path]): The file path as a string or Path object.
"""
with open(file_path, "w") as fp:
with open(str(file_path), "w") as fp:
json.dump(data, fp, cls=NumpyJsonEncoder, indent=indent)
def read_yaml_file(file_path: str) -> dict:
def read_yaml_file(file_path: Union[str, Path]) -> Dict:
"""
Read a yaml file and return a dict.
Args:
file_path (str): The path to the yaml file.
file_path (Union[str, Path]): The file path as a string or Path object.
Returns:
dict: A dict of content information
"""
with open(file_path, "r") as file:
with open(str(file_path), "r") as file:
data = yaml.safe_load(file)
return data
def save_yaml_file(data: dict, file_path: str) -> None:
def save_yaml_file(data: dict, file_path: Union[str, Path]) -> None:
"""
Save a dict to a json file.
Args:
indent:
data (dict): dict with unique keys and value as pair.
file_path (str): The path to the json file.
file_path (Union[str, Path]): The file path as a string or Path object.
"""
with open(file_path, "w") as outfile:
with open(str(file_path), "w") as outfile:
yaml.dump(data, outfile, sort_keys=False, default_flow_style=None)
+33 -13
View File
@@ -14,8 +14,8 @@ from supervision.draw.color import Color, unify_to_bgr
from supervision.draw.utils import calculate_optimal_text_scale, draw_text
from supervision.geometry.core import Point
from supervision.utils.conversion import (
convert_for_image_processing,
cv2_to_pillow,
ensure_cv2_image_for_processing,
images_to_cv2,
)
from supervision.utils.iterables import create_batches, fill
@@ -25,7 +25,7 @@ RelativePosition = Literal["top", "bottom"]
MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3
@convert_for_image_processing
@ensure_cv2_image_for_processing
def crop_image(
image: ImageType,
xyxy: Union[npt.NDArray[int], List[int], Tuple[int, int, int, int]],
@@ -86,7 +86,7 @@ def crop_image(
return image[y_min:y_max, x_min:x_max]
@convert_for_image_processing
@ensure_cv2_image_for_processing
def scale_image(image: ImageType, scale_factor: float) -> ImageType:
"""
Scales the given image based on the given scale factor.
@@ -143,7 +143,7 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType:
return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
@convert_for_image_processing
@ensure_cv2_image_for_processing
def resize_image(
image: ImageType,
resolution_wh: Tuple[int, int],
@@ -158,7 +158,7 @@ def resize_image(
accepting either `numpy.ndarray` or `PIL.Image.Image`.
resolution_wh (Tuple[int, int]): The target resolution as
`(width, height)`.
keep_aspect_ratio (bool, optional): Flag to maintain the image's original
keep_aspect_ratio (bool): Flag to maintain the image's original
aspect ratio. Defaults to `False`.
Returns:
@@ -216,7 +216,7 @@ def resize_image(
return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
@convert_for_image_processing
@ensure_cv2_image_for_processing
def letterbox_image(
image: ImageType,
resolution_wh: Tuple[int, int],
@@ -270,6 +270,7 @@ def letterbox_image(
![letterbox_image](https://media.roboflow.com/supervision-docs/letterbox-image.png){ align=center width="800" }
""" # noqa E501 // docs
assert isinstance(image, np.ndarray)
color = unify_to_bgr(color=color)
resized_image = resize_image(
image=image, resolution_wh=resolution_wh, keep_aspect_ratio=True
@@ -279,7 +280,7 @@ def letterbox_image(
padding_bottom = resolution_wh[1] - height_new - padding_top
padding_left = (resolution_wh[0] - width_new) // 2
padding_right = resolution_wh[0] - width_new - padding_left
return cv2.copyMakeBorder(
image_with_borders = cv2.copyMakeBorder(
resized_image,
padding_top,
padding_bottom,
@@ -289,6 +290,14 @@ def letterbox_image(
value=color,
)
if image.shape[2] == 4:
image[:padding_top, :, 3] = 0
image[height_new - padding_bottom :, :, 3] = 0
image[:, :padding_left, 3] = 0
image[:, width_new - padding_right :, 3] = 0
return image_with_borders
def overlay_image(
image: npt.NDArray[np.uint8],
@@ -341,9 +350,20 @@ def overlay_image(
crop_x_max = image_width - max((anchor_x + image_width) - scene_width, 0)
crop_y_max = image_height - max((anchor_y + image_height) - scene_height, 0)
image[y_min:y_max, x_min:x_max] = overlay[
crop_y_min:crop_y_max, crop_x_min:crop_x_max
]
if overlay.shape[2] == 4:
b, g, r, alpha = cv2.split(
overlay[crop_y_min:crop_y_max, crop_x_min:crop_x_max]
)
alpha = alpha[:, :, None] / 255.0
overlay_color = cv2.merge((b, g, r))
roi = image[y_min:y_max, x_min:x_max]
roi[:] = roi * (1 - alpha) + overlay_color * alpha
image[y_min:y_max, x_min:x_max] = roi
else:
image[y_min:y_max, x_min:x_max] = overlay[
crop_y_min:crop_y_max, crop_x_min:crop_x_max
]
return image
@@ -360,9 +380,9 @@ class ImageSink:
Args:
target_dir_path (str): The target directory where images will be saved.
overwrite (bool, optional): Whether to overwrite the existing directory.
overwrite (bool): Whether to overwrite the existing directory.
Defaults to False.
image_name_pattern (str, optional): The image file name pattern.
image_name_pattern (str): The image file name pattern.
Defaults to "image_{:05d}.png".
Examples:
@@ -399,7 +419,7 @@ class ImageSink:
Args:
image (np.ndarray): The image to be saved. The image must be in BGR color
format.
image_name (str, optional): The name to use for the saved image.
image_name (Optional[str]): The name to use for the saved image.
If not provided, a name will be
generated using the `image_name_pattern`.
"""
+74 -17
View File
@@ -1,7 +1,8 @@
import functools
import inspect
import os
import warnings
from typing import Callable
from typing import Any, Callable, Generic, Optional, Set, TypeVar
class SupervisionWarnings(Warning):
@@ -30,6 +31,16 @@ else:
warnings.simplefilter("always", SupervisionWarnings)
def warn_deprecated(message: str):
"""
Issue a warning that a function is deprecated.
Args:
message (str): The message to display when the function is called.
"""
warnings.warn(message, category=SupervisionWarnings, stacklevel=2)
def deprecated_parameter(
old_parameter: str,
new_parameter: str,
@@ -45,9 +56,9 @@ def deprecated_parameter(
Parameters:
old_parameter (str): The name of the deprecated parameter.
new_parameter (str): The name of the parameter that should be used instead.
map_function (Callable, optional): A function used to map the value of the old
map_function (Callable): A function used to map the value of the old
parameter to the new parameter. Defaults to the identity function.
warning_message (str, optional): The warning message to be displayed when the
warning_message (str): The warning message to be displayed when the
deprecated parameter is used. Defaults to a generic warning message with
placeholders for the old parameter, new parameter, and function name.
**message_kwargs: Additional keyword arguments that can be used to customize
@@ -81,15 +92,13 @@ def deprecated_parameter(
else:
function_name = func.__name__
warnings.warn(
warn_deprecated(
message=warning_message.format(
function_name=function_name,
old_parameter=old_parameter,
new_parameter=new_parameter,
**message_kwargs,
),
category=SupervisionWarnings,
stacklevel=2,
)
)
kwargs[new_parameter] = map_function(kwargs.pop(old_parameter))
@@ -105,11 +114,7 @@ def deprecated(reason: str):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated: {reason}",
category=SupervisionWarnings,
stacklevel=2,
)
warn_deprecated(f"{func.__name__} is deprecated: {reason}")
return func(*args, **kwargs)
return wrapper
@@ -117,7 +122,10 @@ def deprecated(reason: str):
return decorator
class classproperty(property):
T = TypeVar("T")
class classproperty(Generic[T]):
"""
A decorator that combines @classmethod and @property.
It allows a method to be accessed as a property of the class,
@@ -129,15 +137,64 @@ class classproperty(property):
...
"""
def __get__(self, owner_self: object, owner_cls: type) -> object:
def __init__(self, fget: Callable[..., T]):
"""
Args:
The function that is called when the property is accessed.
"""
self.fget = fget
def __get__(self, owner_self: Any, owner_cls: Optional[type] = None) -> T:
"""
Override the __get__ method to return the result of the function call.
Args:
owner_self: The instance through which the attribute was accessed, or None.
owner_cls: The class through which the attribute was accessed.
owner_self: The instance through which the attribute was accessed, or None.
Irrelevant for class properties.
owner_cls: The class through which the attribute was accessed.
Returns:
The result of calling the function stored in 'fget' with 'owner_cls'.
The result of calling the function stored in 'fget' with 'owner_cls'.
"""
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(owner_cls)
def get_instance_variables(instance: Any, include_properties=False) -> Set[str]:
"""
Get the public variables of a class instance.
Args:
instance (Any): The instance of a class
include_properties (bool): Whether to include properties in the result
Usage:
```python
detections = Detections(xyxy=np.array([1,2,3,4]))
variables = get_class_variables(detections)
# ["xyxy", "mask", "confidence", ..., "data"]
```
"""
if isinstance(instance, type):
raise ValueError("Only class instances are supported, not classes.")
fields = set(
(
name
for name, val in inspect.getmembers(instance)
if not callable(val) and not name.startswith("_")
)
)
if not include_properties:
properties = set(
(
name
for name, val in inspect.getmembers(instance.__class__)
if isinstance(val, property)
)
)
fields -= properties
return fields
+14
View File
@@ -68,3 +68,17 @@ def fill(sequence: List[V], desired_size: int, content: V) -> List[V]:
missing_size = max(0, desired_size - len(sequence))
sequence.extend([content] * missing_size)
return sequence
def find_duplicates(sequence: List) -> List:
"""
Find all duplicate elements in the input sequence.
"""
seen = set()
duplicates = set()
for element in sequence:
if element in seen:
duplicates.add(element)
else:
seen.add(element)
return list(duplicates)
+1 -1
View File
@@ -17,7 +17,7 @@ def plot_image(
Args:
image (ImageType): The frame to be displayed ImageType
is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`.
size (Tuple[int, int]): The size of the plot.
size (Tuple[int, int]): The size of the plot in inches.
cmap (str): the colormap to use for single channel images.
Examples:
+25 -25
View File
@@ -8,8 +8,6 @@ from typing import Callable, Generator, Optional, Tuple
import cv2
import numpy as np
from supervision.utils.internal import deprecated
@dataclass
class VideoInfo:
@@ -21,7 +19,7 @@ class VideoInfo:
width (int): width of the video in pixels
height (int): height of the video in pixels
fps (int): frames per second of the video
total_frames (int, optional): total number of frames in the video,
total_frames (Optional[int]): total number of frames in the video,
default is None
Examples:
@@ -118,7 +116,9 @@ class VideoSink:
self.__writer.release()
def _validate_and_setup_video(source_path: str, start: int, end: Optional[int]):
def _validate_and_setup_video(
source_path: str, start: int, end: Optional[int], iterative_seek: bool = False
):
video = cv2.VideoCapture(source_path)
if not video.isOpened():
raise Exception(f"Could not open video at {source_path}")
@@ -127,12 +127,25 @@ def _validate_and_setup_video(source_path: str, start: int, end: Optional[int]):
raise Exception("Requested frames are outbound")
start = max(start, 0)
end = min(end, total_frames) if end is not None else total_frames
video.set(cv2.CAP_PROP_POS_FRAMES, start)
if iterative_seek:
while start > 0:
success = video.grab()
if not success:
break
start -= 1
elif start > 0:
video.set(cv2.CAP_PROP_POS_FRAMES, start)
return video, start, end
def get_video_frames_generator(
source_path: str, stride: int = 1, start: int = 0, end: Optional[int] = None
source_path: str,
stride: int = 1,
start: int = 0,
end: Optional[int] = None,
iterative_seek: bool = False,
) -> Generator[np.ndarray, None, None]:
"""
Get a generator that yields the frames of the video.
@@ -145,6 +158,9 @@ def get_video_frames_generator(
video should generate frames
end (Optional[int]): Indicates the ending position at which video
should stop generating frames. If None, video will be read to the end.
iterative_seek (bool): If True, the generator will seek to the
`start` frame by grabbing each frame, which is much slower. This is a
workaround for videos that don't open at all when you set the `start` value.
Returns:
(Generator[np.ndarray, None, None]): A generator that yields the
@@ -158,7 +174,9 @@ def get_video_frames_generator(
...
```
"""
video, start, end = _validate_and_setup_video(source_path, start, end)
video, start, end = _validate_and_setup_video(
source_path, start, end, iterative_seek
)
frame_position = start
while True:
success, frame = video.read()
@@ -239,24 +257,6 @@ class FPSMonitor:
""" # noqa: E501 // docs
self.all_timestamps = deque(maxlen=sample_size)
@deprecated(
"`FPSMonitor.__call__` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `FPSMonitor.fps` instead."
)
def __call__(self) -> float:
"""
!!! failure "Deprecated"
`FPSMonitor.__call__` is deprecated and will be removed in
`supervision-0.22.0`. Use `FPSMonitor.fps` instead.
Computes and returns the average FPS based on the stored time stamps.
Returns:
float: The average FPS. Returns 0.0 if no time stamps are stored.
"""
return self.fps
@property
def fps(self) -> float:
"""
+1 -2
View File
@@ -62,8 +62,7 @@ def validate_keypoint_confidence(confidence: Any, n: int, m: int) -> None:
if not is_valid:
raise ValueError(
f"confidence must be a 1D np.ndarray with shape {expected_shape}, but "
"got"
f"shape {actual_shape}"
f"got shape {actual_shape}"
)
+1 -1
View File
@@ -1,5 +1,4 @@
from contextlib import ExitStack as DoesNotRaise
from test.test_utils import mock_detections
from typing import Optional
import numpy as np
@@ -7,6 +6,7 @@ import pytest
from supervision.annotators.utils import ColorLookup, resolve_color_idx
from supervision.detection.core import Detections
from test.test_utils import mock_detections
@pytest.mark.parametrize(
+310 -54
View File
@@ -1,5 +1,5 @@
from contextlib import ExitStack as DoesNotRaise
from typing import Dict, List, Tuple
from typing import Dict, List, Tuple, Union
import numpy as np
import pytest
@@ -10,24 +10,30 @@ from supervision.dataset.formats.coco import (
classes_to_coco_categories,
coco_annotations_to_detections,
coco_categories_to_classes,
detections_to_coco_annotations,
group_coco_annotations_by_image_id,
)
def mock_cock_coco_annotation(
def mock_coco_annotation(
annotation_id: int = 0,
image_id: int = 0,
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,
iscrowd: bool = False,
) -> dict:
if not segmentation:
segmentation = []
return {
"id": annotation_id,
"image_id": image_id,
"category_id": category_id,
"bbox": list(bbox),
"area": area,
"iscrowd": 0,
"segmentation": segmentation,
"iscrowd": int(iscrowd),
}
@@ -101,74 +107,46 @@ def test_classes_to_coco_categories_and_back_to_classes(
[
([], {}, DoesNotRaise()), # empty coco annotations
(
[mock_cock_coco_annotation(annotation_id=0, image_id=0, category_id=0)],
{
0: [
mock_cock_coco_annotation(
annotation_id=0, image_id=0, category_id=0
)
]
},
[mock_coco_annotation(annotation_id=0, image_id=0, category_id=0)],
{0: [mock_coco_annotation(annotation_id=0, image_id=0, category_id=0)]},
DoesNotRaise(),
), # single coco annotation
(
[
mock_cock_coco_annotation(annotation_id=0, image_id=0, category_id=0),
mock_cock_coco_annotation(annotation_id=1, image_id=1, category_id=0),
mock_coco_annotation(annotation_id=0, image_id=0, category_id=0),
mock_coco_annotation(annotation_id=1, image_id=1, category_id=0),
],
{
0: [
mock_cock_coco_annotation(
annotation_id=0, image_id=0, category_id=0
)
],
1: [
mock_cock_coco_annotation(
annotation_id=1, image_id=1, category_id=0
)
],
0: [mock_coco_annotation(annotation_id=0, image_id=0, category_id=0)],
1: [mock_coco_annotation(annotation_id=1, image_id=1, category_id=0)],
},
DoesNotRaise(),
), # two coco annotations
(
[
mock_cock_coco_annotation(annotation_id=0, image_id=0, category_id=0),
mock_cock_coco_annotation(annotation_id=1, image_id=1, category_id=1),
mock_cock_coco_annotation(annotation_id=2, image_id=1, category_id=2),
mock_cock_coco_annotation(annotation_id=3, image_id=2, category_id=3),
mock_cock_coco_annotation(annotation_id=4, image_id=3, category_id=1),
mock_cock_coco_annotation(annotation_id=5, image_id=3, category_id=2),
mock_cock_coco_annotation(annotation_id=5, image_id=3, category_id=3),
mock_coco_annotation(annotation_id=0, image_id=0, category_id=0),
mock_coco_annotation(annotation_id=1, image_id=1, category_id=1),
mock_coco_annotation(annotation_id=2, image_id=1, category_id=2),
mock_coco_annotation(annotation_id=3, image_id=2, category_id=3),
mock_coco_annotation(annotation_id=4, image_id=3, category_id=1),
mock_coco_annotation(annotation_id=5, image_id=3, category_id=2),
mock_coco_annotation(annotation_id=5, image_id=3, category_id=3),
],
{
0: [
mock_cock_coco_annotation(
annotation_id=0, image_id=0, category_id=0
),
mock_coco_annotation(annotation_id=0, image_id=0, category_id=0),
],
1: [
mock_cock_coco_annotation(
annotation_id=1, image_id=1, category_id=1
),
mock_cock_coco_annotation(
annotation_id=2, image_id=1, category_id=2
),
mock_coco_annotation(annotation_id=1, image_id=1, category_id=1),
mock_coco_annotation(annotation_id=2, image_id=1, category_id=2),
],
2: [
mock_cock_coco_annotation(
annotation_id=3, image_id=2, category_id=3
),
mock_coco_annotation(annotation_id=3, image_id=2, category_id=3),
],
3: [
mock_cock_coco_annotation(
annotation_id=4, image_id=3, category_id=1
),
mock_cock_coco_annotation(
annotation_id=5, image_id=3, category_id=2
),
mock_cock_coco_annotation(
annotation_id=5, image_id=3, category_id=3
),
mock_coco_annotation(annotation_id=4, image_id=3, category_id=1),
mock_coco_annotation(annotation_id=5, image_id=3, category_id=2),
mock_coco_annotation(annotation_id=5, image_id=3, category_id=3),
],
},
DoesNotRaise(),
@@ -195,7 +173,7 @@ def test_group_coco_annotations_by_image_id(
), # empty image annotations
(
[
mock_cock_coco_annotation(
mock_coco_annotation(
category_id=0, bbox=(0, 0, 100, 100), area=100 * 100
)
],
@@ -209,10 +187,10 @@ def test_group_coco_annotations_by_image_id(
), # single image annotations
(
[
mock_cock_coco_annotation(
mock_coco_annotation(
category_id=0, bbox=(0, 0, 100, 100), area=100 * 100
),
mock_cock_coco_annotation(
mock_coco_annotation(
category_id=0, bbox=(100, 100, 100, 100), area=100 * 100
),
],
@@ -226,6 +204,156 @@ def test_group_coco_annotations_by_image_id(
),
DoesNotRaise(),
), # two image annotations
(
[
mock_coco_annotation(
category_id=0,
bbox=(0, 0, 5, 5),
area=5 * 5,
segmentation=[[0, 0, 2, 0, 2, 2, 4, 2, 4, 4, 0, 4]],
)
],
(5, 5),
True,
Detections(
xyxy=np.array([[0, 0, 5, 5]], dtype=np.float32),
class_id=np.array([0], dtype=int),
mask=np.array(
[
[
[1, 1, 1, 0, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
]
]
),
),
DoesNotRaise(),
), # single image annotations with mask as polygon
(
[
mock_coco_annotation(
category_id=0,
bbox=(0, 0, 5, 5),
area=5 * 5,
segmentation={
"size": [5, 5],
"counts": [0, 15, 2, 3, 2, 3],
},
iscrowd=True,
)
],
(5, 5),
True,
Detections(
xyxy=np.array([[0, 0, 5, 5]], dtype=np.float32),
class_id=np.array([0], dtype=int),
mask=np.array(
[
[
[1, 1, 1, 0, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
]
]
),
),
DoesNotRaise(),
), # single image annotations with mask, RLE segmentation mask
(
[
mock_coco_annotation(
category_id=0,
bbox=(0, 0, 5, 5),
area=5 * 5,
segmentation=[[0, 0, 2, 0, 2, 2, 4, 2, 4, 4, 0, 4]],
),
mock_coco_annotation(
category_id=0,
bbox=(3, 0, 2, 2),
area=2 * 2,
segmentation={
"size": [5, 5],
"counts": [15, 2, 3, 2, 3],
},
iscrowd=True,
),
],
(5, 5),
True,
Detections(
xyxy=np.array([[0, 0, 5, 5], [3, 0, 5, 2]], dtype=np.float32),
class_id=np.array([0, 0], dtype=int),
mask=np.array(
[
[
[1, 1, 1, 0, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
],
[
[0, 0, 0, 1, 1],
[0, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
],
]
),
),
DoesNotRaise(),
), # two image annotations with mask, one mask as polygon ans second as RLE
(
[
mock_coco_annotation(
category_id=0,
bbox=(3, 0, 2, 2),
area=2 * 2,
segmentation={
"size": [5, 5],
"counts": [15, 2, 3, 2, 3],
},
iscrowd=True,
),
mock_coco_annotation(
category_id=1,
bbox=(0, 0, 5, 5),
area=5 * 5,
segmentation=[[0, 0, 2, 0, 2, 2, 4, 2, 4, 4, 0, 4]],
),
],
(5, 5),
True,
Detections(
xyxy=np.array([[3, 0, 5, 2], [0, 0, 5, 5]], dtype=np.float32),
class_id=np.array([0, 1], dtype=int),
mask=np.array(
[
[
[0, 0, 0, 1, 1],
[0, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
],
[
[1, 1, 1, 0, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
],
]
),
),
DoesNotRaise(),
), # two image annotations with mask, first mask as RLE and second as polygon
],
)
def test_coco_annotations_to_detections(
@@ -301,3 +429,131 @@ def test_build_coco_class_index_mapping(
coco_categories=coco_categories, target_classes=target_classes
)
assert result == expected_result
@pytest.mark.parametrize(
"detections, image_id, annotation_id, expected_result, exception",
[
(
Detections(
xyxy=np.array([[0, 0, 100, 100]], dtype=np.float32),
class_id=np.array([0], dtype=int),
),
0,
0,
[
mock_coco_annotation(
category_id=0, bbox=(0, 0, 100, 100), area=100 * 100
)
],
DoesNotRaise(),
), # no segmentation mask
(
Detections(
xyxy=np.array([[0, 0, 4, 5]], dtype=np.float32),
class_id=np.array([0], dtype=int),
mask=np.array(
[
[
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 0],
]
]
),
),
0,
0,
[
mock_coco_annotation(
category_id=0,
bbox=(0, 0, 4, 5),
area=4 * 5,
segmentation=[[0, 0, 0, 4, 3, 4, 3, 0]],
)
],
DoesNotRaise(),
), # segmentation mask in single component,no holes in mask,
# expects polygon mask
(
Detections(
xyxy=np.array([[0, 0, 5, 5]], dtype=np.float32),
class_id=np.array([0], dtype=int),
mask=np.array(
[
[
[1, 1, 1, 0, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 0, 0],
[0, 0, 0, 1, 1],
[0, 0, 0, 1, 1],
]
]
),
),
0,
0,
[
mock_coco_annotation(
category_id=0,
bbox=(0, 0, 5, 5),
area=5 * 5,
segmentation={
"size": [5, 5],
"counts": [0, 3, 2, 3, 2, 3, 5, 2, 3, 2],
},
iscrowd=True,
)
],
DoesNotRaise(),
), # segmentation mask with 2 components, no holes in mask, expects RLE mask
(
Detections(
xyxy=np.array([[0, 0, 5, 5]], dtype=np.float32),
class_id=np.array([0], dtype=int),
mask=np.array(
[
[
[0, 1, 1, 1, 1],
[0, 1, 1, 1, 1],
[1, 1, 0, 0, 1],
[1, 1, 0, 0, 1],
[1, 1, 1, 1, 1],
]
]
),
),
0,
0,
[
mock_coco_annotation(
category_id=0,
bbox=(0, 0, 5, 5),
area=5 * 5,
segmentation={
"size": [5, 5],
"counts": [2, 10, 2, 3, 2, 6],
},
iscrowd=True,
)
],
DoesNotRaise(),
), # seg mask in single component, with holes in mask, expects RLE mask
],
)
def test_detections_to_coco_annotations(
detections: Detections,
image_id: int,
annotation_id: int,
expected_result: List[Dict],
exception: Exception,
) -> None:
with exception:
result, _ = detections_to_coco_annotations(
detections=detections,
image_id=image_id,
annotation_id=annotation_id,
)
assert result == expected_result
+6 -6
View File
@@ -1,16 +1,16 @@
from contextlib import ExitStack as DoesNotRaise
from test.test_utils import mock_detections
from typing import List, Optional
import defusedxml.ElementTree as ET
import numpy as np
import pytest
from defusedxml import ElementTree
from supervision.dataset.formats.pascal_voc import (
detections_from_xml_obj,
object_to_pascal_voc,
parse_polygon_points,
)
from test.test_utils import mock_detections
def are_xml_elements_equal(elem1, elem2):
@@ -36,7 +36,7 @@ def are_xml_elements_equal(elem1, elem2):
np.array([0, 0, 10, 10]),
"test",
None,
ET.fromstring(
ElementTree.fromstring(
"""<object><name>test</name><bndbox><xmin>1</xmin><ymin>1</ymin>
<xmax>11</xmax><ymax>11</ymax></bndbox></object>"""
),
@@ -46,7 +46,7 @@ def are_xml_elements_equal(elem1, elem2):
np.array([0, 0, 10, 10]),
"test",
np.array([[0, 0], [10, 0], [10, 10], [0, 10]]),
ET.fromstring(
ElementTree.fromstring(
"""<object><name>test</name><bndbox><xmin>1</xmin><ymin>1</ymin>
<xmax>11</xmax><ymax>11</ymax>
</bndbox><polygon><x1>1</x1><y1>1</y1><x2>11</x2>
@@ -73,7 +73,7 @@ def test_object_to_pascal_voc(
"polygon_element, expected_result, exception",
[
(
ET.fromstring(
ElementTree.fromstring(
"""<polygon><x1>0</x1><y1>0</y1><x2>10</x2><y2>0</y2><x3>10</x3>
<y3>10</y3><x4>0</x4><y4>10</y4></polygon>"""
),
@@ -160,6 +160,6 @@ def test_detections_from_xml_obj(
xml_string, classes, resolution_wh, force_masks, expected_result, exception
):
with exception:
root = ET.fromstring(xml_string)
root = ElementTree.fromstring(xml_string)
result, _ = detections_from_xml_obj(root, classes, resolution_wh, force_masks)
assert result == expected_result

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