Compare commits

...
168 Commits
Author SHA1 Message Date
SkalskiP 81c2a09430 bump version from 0.19.0 to 0.20.0rc1 2024-03-28 15:09:23 +01:00
Piotr Skalski 16b7222012 Merge pull request #1054 from Griffin-Sullivan/transformers-segmentation
Add segmentation model support to from_transformers
2024-03-28 14:40:08 +01:00
Piotr Skalski 6b2136d57b Merge pull request #1064 from roboflow/ci/dev-ci
fix: 🐞 ci trigger error fix
2024-03-28 14:38:50 +01:00
Piotr Skalski 908a1b4d89 Merge pull request #1049 from rolson24/fix-pr-bug
fix enumerate call
2024-03-28 14:36:40 +01:00
Onuralp SEZER 7e283d4eeb fix: 🐞 ci trigger error fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-28 16:28:21 +03:00
Raif Olson a2aca62ecb merge develop branch and change back to using boolean indexing 2024-03-28 09:27:54 -04:00
Onuralp SEZER 0a5fb7b47e Merge pull request #1060 from roboflow/dependabot/pip/build-1.2.0
⬆️ Bump build from 1.1.1 to 1.2.0
2024-03-28 16:22:50 +03:00
Piotr Skalski 7132c034ab Merge pull request #1063 from roboflow/get_data_item_tests
`get_data_item` unit tests
2024-03-28 14:09:15 +01:00
pre-commit-ci[bot] dc5f841bae fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-28 13:06:47 +00:00
SkalskiP 64eef66b54 get_data_item unit tests 2024-03-28 14:04:21 +01:00
Raif Olson dc45c0a5e0 Merge remote-tracking branch 'origin/develop' into fix-pr-bug 2024-03-28 08:48:05 -04:00
Piotr Skalski 69563b8bbb Merge pull request #1062 from rolson24/fix-Detections-call-bug
fix Detections.get_data_item() bug (#1061)
2024-03-28 13:40:57 +01:00
pre-commit-ci[bot] 29ba24f89a fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-28 12:39:14 +00:00
Raif Olson da4404e875 More pythonic version of change 2024-03-28 08:38:50 -04:00
Raif Olson 089594f72d fix Detections.get_data_item() bug 2024-03-27 23:59:40 -04:00
pre-commit-ci[bot] e9edc2fb1d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-28 03:39:22 +00:00
Raif Olson ad24d3811b Refactor update_with_detections for simplicity. Credit to @SkalskiP for the code. (had to change a few lines for bug fixes) 2024-03-27 23:38:55 -04:00
dependabot[bot] 8fb3ba70b0 ⬆️ Bump build from 1.1.1 to 1.2.0
Bumps [build](https://github.com/pypa/build) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/1.1.1...1.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 00:56:05 +00:00
SkalskiP 34b80eb3c0 Fix YT videos paths 2024-03-27 09:12:45 +01:00
Piotr Skalski 3276973446 Update README.md 2024-03-27 00:14:19 +01:00
Piotr Skalskiandpre-commit-ci[bot] 725d95c6ce time in zone / dwell time demo (#1026)
* initial commit

* initial stream processing script

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

* work in progress

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

* work in progress

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

* ultralytics file and stream versions ready

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

* ultralytics file and stream versions ready

* class based filtering

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

* timer improvements

* inference static file processing script

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

* inference stream processing script

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

* all scripts are working

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

* all video or stream processing scripts refactored

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

* README.md update

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

* initial version of draw ones script

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

* loading image or video, drawing multiple polygons, quiting app, and aborting currently drawn polygon works.

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

* loading image or video, drawing multiple polygons, quiting app, and aborting currently drawn polygon works.

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

* Dynamic drawing of currently edited polygon.

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

* Drawing tool is ready.

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

* Final touches.

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

* Update README.md

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

* Update README.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-03-27 00:12:12 +01:00
pre-commit-ci[bot] 13f85ae6cc fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-26 21:05:04 +00:00
Griffin-Sullivan c67078b53c Add segmentation model support to from_transformers 2024-03-26 17:04:43 -04:00
Piotr Skalski 42f9d4b69b Merge pull request #1053 from roboflow/fix/ci-publish-bug
ci: 👷 publish pypi on branch removed
2024-03-26 18:24:36 +01:00
Onuralp SEZER d41caed36f ci: 👷 publish pypi on branch removed
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-26 18:38:04 +03:00
SkalskiP 5a040969d7 fix how-to links 2024-03-26 09:53:16 +01:00
Raif Olson 3ed4aeffff fix bugs in PR #1035
fix tuple unpacking bug in PR #1035

Fix bug with detections with un-initialized tracker_ids
2024-03-25 23:52:33 -04:00
Onuralp SEZER 97b8312570 Merge pull request #1045 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-03-26 00:26:55 +03:00
Piotr Skalski 94e9719f20 Merge pull request #1048 from roboflow/docs/detect_small_objects_updates
Detect Small Objects documentation 2
2024-03-25 22:12:16 +01:00
SkalskiP 0d16b13e7a Added additional visualization 2024-03-25 22:09:23 +01:00
Piotr Skalski 5c03173c7c Merge pull request #1047 from roboflow/docs/detect_small_objects
Detect Small Objects documentation
2024-03-25 21:25:30 +01:00
pre-commit-ci[bot] cf88cc0384 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-25 20:14:17 +00:00
SkalskiP 15e17adef1 Detect Small Objects documentation 2024-03-25 21:11:47 +01:00
pre-commit-ci[bot] ca425a628d chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.3.3 → v0.3.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.3...v0.3.4)
2024-03-25 17:44:14 +00:00
Piotr Skalski 09a47bec62 Merge pull request #1037 from xaristeidou/round-edge-label-annotator
Add `corner_radius` to `sv.LabelAnnotator` to handle roundness of label edges.
2024-03-25 17:59:28 +01:00
pre-commit-ci[bot] 90f6893d3d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-25 16:29:16 +00:00
SkalskiP b585bb0724 test slightly simplified API 2024-03-25 17:28:59 +01:00
Piotr Skalski 495f9a9003 Merge pull request #1035 from rolson24/redesign-update_with_detections-to-match-Detections-with-tracker_id-v2
Fix issue #754
2024-03-25 16:15:37 +01:00
Piotr Skalski 4a6a6dbf6b Merge pull request #1041 from roboflow/docs/how_to_detect_and_annotate_improvements
How to Detect and Annotate Tutorial Improvements
2024-03-25 15:10:58 +01:00
SkalskiP fec251d979 fix LabelAnnotator code snippets 2024-03-25 15:01:59 +01:00
SkalskiP 6d27b8067b mention that VideoSink and ImageSink accept images in BGR color format. 2024-03-25 14:54:34 +01:00
Onuralp SEZER 3da0ca4ffc Merge pull request #1039 from roboflow/dependabot/pip/mkdocs-material-9.5.15
⬆️ Bump mkdocs-material from 9.5.14 to 9.5.15
2024-03-25 16:48:30 +03:00
Onuralp SEZER fc03b0c386 Merge pull request #1040 from roboflow/dependabot/pip/tox-4.14.2
⬆️ Bump tox from 4.14.1 to 4.14.2
2024-03-25 16:48:12 +03:00
pre-commit-ci[bot] 4660dfd0d3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-25 12:55:29 +00:00
SkalskiP e7b4d6ec41 more improvements 2024-03-25 13:55:13 +01:00
pre-commit-ci[bot] bc586562b5 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-25 12:14:44 +00:00
SkalskiP 57c6182325 initial changes 2024-03-25 13:11:47 +01:00
dependabot[bot] c3b617d47e ⬆️ Bump tox from 4.14.1 to 4.14.2
Bumps [tox](https://github.com/tox-dev/tox) from 4.14.1 to 4.14.2.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.14.1...4.14.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 01:26:28 +00:00
dependabot[bot] 6458d4454d ⬆️ Bump mkdocs-material from 9.5.14 to 9.5.15
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.14 to 9.5.15.
- [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.14...9.5.15)

---
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-03-25 01:07:38 +00:00
Christoforos Aristeidou 957ced0614 (pre-commit): align spacing 2024-03-24 11:42:19 +02:00
Christoforos Aristeidou ed3c73eb02 modified to use new custom method for rectangle drawing 2024-03-24 11:36:51 +02:00
Christoforos Aristeidou 3e4ddd5bd9 added new custom method that draws rounded rectangles 2024-03-24 11:36:10 +02:00
Christoforos Aristeidou 1fbdec6046 added corner_radius attribute to LabelAnnotator 2024-03-24 11:35:18 +02:00
Raif Olson 1eb877c4d9 Fix issue #754 2024-03-23 18:53:05 -05:00
Onuralp SEZER 781a064d8a Merge pull request #1030 from roboflow/dependabot/pip/ruff-0.3.4
⬆️ Bump ruff from 0.3.3 to 0.3.4
2024-03-22 04:40:54 +03:00
dependabot[bot] 70218b85e7 ⬆️ Bump ruff from 0.3.3 to 0.3.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.3 to 0.3.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.3.3...v0.3.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-03-22 01:05:54 +00:00
Onuralp SEZER 37318fafdf Merge pull request #1029 from roboflow/dependabot/pip/nbconvert-7.16.3
⬆️ Bump nbconvert from 7.16.2 to 7.16.3
2024-03-22 03:56:13 +03:00
dependabot[bot] f377a992e1 ⬆️ Bump nbconvert from 7.16.2 to 7.16.3
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.16.2 to 7.16.3.
- [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.2...v7.16.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-22 00:52:54 +00:00
Piotr Skalski 6208013dcf Merge pull request #1003 from roboflow/adding-clor-border-to-crop-annotator
adding color border to crop annotator poc
2024-03-20 19:41:13 +01:00
SkalskiP 8a2abe72ae utils refactor 2024-03-20 19:37:03 +01:00
Piotr Skalski d01b636ebe Merge pull request #1024 from roboflow/security/ci/packaging
ci: 👷 publish pypi branch limitations added
2024-03-19 14:32:47 +01:00
Onuralp SEZER 0e3e41287a ci: 👷 publish pypi branch limitations added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-19 16:25:38 +03:00
Onuralp SEZER 5f9560afb8 Merge pull request #1020 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-03-19 06:34:06 +03:00
pre-commit-ci[bot] 4abd128af5 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.3.2 → v0.3.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.2...v0.3.3)
2024-03-18 17:48:56 +00:00
Piotr Skalski 77908fe49b Merge pull request #1012 from roboflow/feat/type-check-improvement
feat: 📝 static type improvement color constants and PolygonZone
2024-03-18 10:56:10 +01:00
Onuralp SEZER fb3a78f762 Merge pull request #1014 from roboflow/dependabot/pip/mkdocs-material-9.5.14
⬆️ Bump mkdocs-material from 9.5.13 to 9.5.14
2024-03-18 12:00:01 +03:00
Onuralp SEZER f43b3536be Merge pull request #1013 from roboflow/dependabot/pip/ruff-0.3.3
⬆️ Bump ruff from 0.3.2 to 0.3.3
2024-03-18 11:59:44 +03:00
dependabot[bot] 345c086916 ⬆️ Bump mkdocs-material from 9.5.13 to 9.5.14
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.13 to 9.5.14.
- [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.13...9.5.14)

---
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-03-18 01:30:06 +00:00
dependabot[bot] 123a61f2c6 ⬆️ Bump ruff from 0.3.2 to 0.3.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.2 to 0.3.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.3.2...v0.3.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-03-18 01:21:26 +00:00
Onuralp SEZER 63b338751f feat: 📝 typing improvement for polygone zone
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-17 18:16:05 +03:00
Onuralp SEZER e8810fe8a2 fix: 🐞 static type fix for color constants to sv.Color
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-17 14:42:58 +03:00
Piotr Skalski 3d9d45fc07 Merge pull request #1006 from roboflow/ci/doc/concurrency
ci: 👷 develop doc concurrency added for single run and cancel old ones
2024-03-17 12:26:48 +01:00
Onuralp SEZER 4f9e5a590f ci: 👷 develop doc concurrency added for single run and cancel old ones
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-15 22:20:02 +03:00
pre-commit-ci[bot] 9f5c63b32d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-15 11:02:26 +00:00
SkalskiP a5b1079e39 work in progress 2024-03-15 11:57:53 +01:00
Piotr Skalski 88d63473fe Merge pull request #1002 from roboflow/main
Merge pull request #991 from roboflow/develop
2024-03-15 11:18:53 +01:00
Piotr Skalski 55f93a85b6 Merge pull request #991 from roboflow/develop
`supervision-0.19.0` release
2024-03-15 11:15:24 +01:00
SkalskiP a0f7bb76bd bump version from 0.19.0rc5 to 0.19.0 2024-03-15 11:10:01 +01:00
Piotr Skalski 7e72ad13f7 Merge pull request #1000 from roboflow/0.19.0-changelog
`supervision-0.19.0` release changelog
2024-03-15 10:59:31 +01:00
Piotr Skalski 5b6713f197 Merge pull request #996 from roboflow/fix/doc_syntax_fix
doc: 📚 json and csv example code syntax fix
2024-03-15 10:59:08 +01:00
SkalskiP 8d7f18cc22 supervision-0.19.0 release changelog 2024-03-15 10:48:01 +01:00
Onuralp SEZER 012ff2bbe2 Merge pull request #997 from roboflow/dependabot/pip/notebook-7.1.2
⬆️ Bump notebook from 7.1.1 to 7.1.2
2024-03-15 11:20:11 +03:00
dependabot[bot] 594c9958a4 ⬆️ Bump notebook from 7.1.1 to 7.1.2
Bumps [notebook](https://github.com/jupyter/notebook) from 7.1.1 to 7.1.2.
- [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.1...@jupyter-notebook/tree@7.1.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-03-15 00:57:18 +00:00
Onuralp SEZER 917b4c0fe4 doc: 📚 json and csv example code syntax fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-14 22:33:30 +03:00
Piotr Skalski d669619f2d Merge pull request #888 from xaristeidou/develop
Add `sv.CropAnnotator` which annotates a cropped part of detections
2024-03-14 14:47:43 +01:00
SkalskiP 22d97b8a46 add CropAnnotator docs 2024-03-14 14:43:19 +01:00
SkalskiP 0c04647d00 Improved docstrings 2024-03-14 14:37:26 +01:00
pre-commit-ci[bot] d6589443d7 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-14 13:13:09 +00:00
SkalskiP 4ba3780d86 CropAnnotator updates and fixes 2024-03-14 14:12:50 +01:00
pre-commit-ci[bot] d1003d420c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-14 12:33:18 +00:00
SkalskiP c7a6b38275 initial version of refactored CropAnnotator 2024-03-14 13:32:52 +01:00
Onuralp SEZER 0d352bde92 Merge pull request #913 from stellasphere/develop
Add occupancy analytics cookbook
2024-03-14 14:31:17 +03:00
Onuralp SEZER 4a6c0a9fa3 fix: 🐞 dirty outputs are removed and formatting fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-14 14:28:28 +03:00
Onuralp SEZER 423783a8ff Merge branch 'develop' into develop 2024-03-14 14:08:38 +03:00
Piotr Skalski 162a4828e8 Merge pull request #970 from roboflow/config/ruff
ci: 👷 update ruff config and group lint
2024-03-14 11:35:52 +01:00
Piotr Skalski 0b810817e3 Merge pull request #969 from roboflow/fix/depricated
fix: 🐞 update PolygonZone test and update traffic-analysis depricated codes are updated
2024-03-13 14:26:28 +01:00
SkalskiP 6ade84f057 Updated deprecated.md to include deprecations introduced in the supervision-0.19.0 release. 2024-03-13 13:47:39 +01:00
Piotr Skalski 655e5a7709 Merge pull request #994 from roboflow/fix/993_ImageDetectionPrediction_object_is_not_subscriptable
Fix for #993 TypeError: 'ImageDetectionPrediction' object is not subscriptable
2024-03-13 11:10:59 +01:00
pre-commit-ci[bot] dfb52633dc fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-13 10:08:31 +00:00
SkalskiP bc74e1c366 added the same fix to quickstart.ipynb 2024-03-13 11:08:13 +01:00
pre-commit-ci[bot] aacad59fd9 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-13 10:04:09 +00:00
SkalskiP 879d48048c ready for review 2024-03-13 11:02:11 +01:00
Onuralp SEZER c0c346b04b Merge pull request #992 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-03-12 23:45:08 +03:00
pre-commit-ci[bot] 8ea1148f75 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-12 20:30:21 +00:00
pre-commit-ci[bot] 2c55d1796f chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/PyCQA/bandit: 1.7.7 → 1.7.8](https://github.com/PyCQA/bandit/compare/1.7.7...1.7.8)
- [github.com/astral-sh/ruff-pre-commit: v0.2.2 → v0.3.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.2...v0.3.2)
2024-03-12 20:30:09 +00:00
Onuralp SEZER b68e7c2059 Merge pull request #989 from roboflow/dependabot/pip/wheel-0.43.0
⬆️ Bump wheel from 0.42.0 to 0.43.0
2024-03-12 05:32:31 +03:00
dependabot[bot] 4c426f5e82 ⬆️ Bump wheel from 0.42.0 to 0.43.0
Bumps [wheel](https://github.com/pypa/wheel) from 0.42.0 to 0.43.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.42.0...0.43.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-03-12 00:23:55 +00:00
Onuralp SEZER fb82d32355 Merge pull request #987 from roboflow/dependabot/pip/pytest-8.1.1
⬆️ Bump pytest from 8.1.0 to 8.1.1
2024-03-11 04:07:33 +03:00
Onuralp SEZER fd6cf36685 Merge pull request #986 from roboflow/dependabot/pip/mypy-1.9.0
⬆️ Bump mypy from 1.8.0 to 1.9.0
2024-03-11 04:07:18 +03:00
Onuralp SEZER f41781015e Merge pull request #985 from roboflow/dependabot/pip/ruff-0.3.2
⬆️ Bump ruff from 0.3.1 to 0.3.2
2024-03-11 04:07:05 +03:00
dependabot[bot] b85222690b ⬆️ Bump pytest from 8.1.0 to 8.1.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.0 to 8.1.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.1.0...8.1.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-03-11 01:01:54 +00:00
dependabot[bot] 8ef3c0eb00 ⬆️ Bump mypy from 1.8.0 to 1.9.0
Bumps [mypy](https://github.com/python/mypy) from 1.8.0 to 1.9.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.8.0...1.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 00:59:37 +00:00
dependabot[bot] 645a1ec7af ⬆️ Bump ruff from 0.3.1 to 0.3.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.1 to 0.3.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/v0.3.1...v0.3.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-03-11 00:52:12 +00:00
Piotr Skalski cadb259214 Merge pull request #980 from roboflow/add-retention
Script updates
2024-03-07 14:22:35 +00:00
James 26de63a09a Update main.html 2024-03-07 14:19:42 +00:00
Onuralp SEZER b38234babe Merge pull request #979 from roboflow/dependabot/pip/ruff-0.3.1
⬆️ Bump ruff from 0.3.0 to 0.3.1
2024-03-07 04:46:06 +03:00
Onuralp SEZER 26201862f1 Merge pull request #978 from roboflow/dependabot/pip/mkdocs-material-9.5.13
⬆️ Bump mkdocs-material from 9.5.12 to 9.5.13
2024-03-07 04:22:31 +03:00
dependabot[bot] c6f0b5753a ⬆️ Bump ruff from 0.3.0 to 0.3.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.0 to 0.3.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/v0.3.0...v0.3.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-03-07 00:46:53 +00:00
Onuralp SEZER 427c91baa6 Merge pull request #977 from roboflow/dependabot/pip/tox-4.14.1
⬆️ Bump tox from 4.14.0 to 4.14.1
2024-03-07 03:45:22 +03:00
dependabot[bot] dc39852a5b ⬆️ Bump mkdocs-material from 9.5.12 to 9.5.13
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.12 to 9.5.13.
- [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.12...9.5.13)

---
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-03-07 00:44:39 +00:00
dependabot[bot] 52a059ffd9 ⬆️ Bump tox from 4.14.0 to 4.14.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.14.0 to 4.14.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.14.0...4.14.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-03-07 00:42:57 +00:00
Onuralp SEZER 8900986775 Merge pull request #976 from roboflow/dependabot/pip/tox-4.14.0
⬆️ Bump tox from 4.13.0 to 4.14.0
2024-03-06 16:56:11 +03:00
Piotr Skalski 4abef15069 Update README.md 2024-03-06 11:20:43 +00:00
dependabot[bot] f1e596a058 ⬆️ Bump tox from 4.13.0 to 4.14.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.13.0 to 4.14.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.13.0...4.14.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-03-06 01:00:05 +00:00
Onuralp SEZER cd20887d26 Merge pull request #974 from roboflow/dependabot/pip/nbconvert-7.16.2
⬆️ Bump nbconvert from 7.16.1 to 7.16.2
2024-03-05 23:31:16 +03:00
dependabot[bot] e1a950f691 ⬆️ Bump nbconvert from 7.16.1 to 7.16.2
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.16.1 to 7.16.2.
- [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.1...v7.16.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-05 00:33:56 +00:00
Onuralp SEZER b0a64751d0 Merge pull request #973 from roboflow/dependabot/pip/pytest-8.1.0
⬆️ Bump pytest from 8.0.2 to 8.1.0
2024-03-04 21:42:59 +03:00
dependabot[bot] 34648871ab ⬆️ Bump pytest from 8.0.2 to 8.1.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.0.2 to 8.1.0.
- [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.0.2...8.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 01:23:55 +00:00
Onuralp SEZER 823adb3867 ci: 👷 update ruff config and group lint
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-02 03:57:13 +03:00
Onuralp SEZER 56825423dc fix: 🐞 update PolygonZone test and update traffic-analysis depricated codes are updated
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-02 03:25:59 +03:00
Onuralp SEZER e86fc592c7 Merge pull request #968 from roboflow/dependabot/pip/mkdocs-material-9.5.12
⬆️ Bump mkdocs-material from 9.5.11 to 9.5.12
2024-03-01 12:27:34 +03:00
Onuralp SEZER 3e05057ee4 Merge pull request #967 from roboflow/dependabot/pip/build-1.1.1
⬆️ Bump build from 1.0.3 to 1.1.1
2024-03-01 04:14:26 +03:00
dependabot[bot] a3e39bdb6f ⬆️ Bump mkdocs-material from 9.5.11 to 9.5.12
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.11 to 9.5.12.
- [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.11...9.5.12)

---
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-03-01 01:10:04 +00:00
dependabot[bot] 199d26aa72 ⬆️ Bump build from 1.0.3 to 1.1.1
Bumps [build](https://github.com/pypa/build) from 1.0.3 to 1.1.1.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/1.0.3...1.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-01 01:01:54 +00:00
Onuralp SEZER ed3c6d1fbd Merge pull request #966 from roboflow/dependabot/pip/ruff-0.3.0
⬆️ Bump ruff from 0.2.2 to 0.3.0
2024-03-01 03:59:32 +03:00
dependabot[bot] f29bdec62e ⬆️ Bump ruff from 0.2.2 to 0.3.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.2.2 to 0.3.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.2.2...v0.3.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-03-01 00:50:01 +00:00
Onuralp SEZER f55ef90716 Merge pull request #819 from AdonaiVera/allowing_serialise_detections_json
New function [JSONSink] - Allowing to serialise Detections to a JSON file
2024-03-01 03:23:46 +03:00
Adonai Vera d9496316bc remove the json output after finish the unit test 2024-03-01 03:14:42 +03:00
Adonai Vera 4509608a5d Apply documentation, unit test with mutliple scenarios, and improvement 2024-03-01 03:14:35 +03:00
Piotr Skalski 6eeb1c4ae3 Merge pull request #965 from roboflow/ci/doc-test
feat: 📚 add docs test workflow
2024-02-29 22:43:39 +01:00
Onuralp SEZER 4803ab26e5 feat: 📚 add docs test workflow
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-01 00:41:24 +03:00
Onuralp SEZER 852d518d7c fix: 🐞 evaluating-alignment-of-text-to-image-diffusion-model widget fix for docs to render (#964)
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-01 00:28:51 +03:00
Onuralp SEZER b78b3913fc Merge pull request #907 from iamhatesz/docs/alignment-cookbook
Add a cookbook about alignment evaluation
2024-03-01 00:04:59 +03:00
Onuralp SEZER 2407fad07e fix: 🐞 swap ultralytics to inference-gpu[yolo-world] usage and constants added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-01 00:03:55 +03:00
SkalskiP 73013d6e47 bump version from 0.19.0rc4 to 0.19.0rc5 2024-02-29 21:17:33 +01:00
Onuralp SEZER 41c78cceed Merge pull request #963 from roboflow/feat/plot_image/pillow
feat(ImageType): 🚀 sv.plot_images_grid and sv.plot_image can accept ImageType
2024-02-29 23:11:22 +03:00
Onuralp SEZER ce3356287e feat(ImageType): 🚀 sv.plot_images_grid and sv.plot_image can accept ImageType
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-02-29 23:09:01 +03:00
Tomasz Wrona ae6021a5b4 Fix indent 2024-02-25 23:33:46 +01:00
Tomasz Wrona f866a11099 Review fixes 2024-02-25 23:32:50 +01:00
Leo Ueno ebd86eef32 Update occupancy_analytics.ipynb 2024-02-16 12:47:50 -08:00
Leo Ueno 158471283f Add occupancy analytics cookbook 2024-02-16 12:40:55 -08:00
pre-commit-ci[bot] 4e635ee788 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-02-15 13:17:56 +00:00
Tomasz Wrona f53e0d8aa5 A new cookbook about alignment evaluation 2024-02-15 14:00:09 +01:00
Christoforos Aristeidou 2a9d4257ba Merge branch 'roboflow:develop' into develop 2024-02-12 22:10:14 +02:00
Christoforos Aristeidou 9ca3c9319d pre-commit spacing correction 2024-02-12 22:08:58 +02:00
Christoforos Aristeidou 379d755a52 description of x,y pixel and trim method 2024-02-12 22:03:57 +02:00
Christoforos Aristeidou d228224f06 completed if else statements for positioning 2024-02-12 21:47:29 +02:00
Christoforos Aristeidou 2863267f30 asdf 2024-02-12 18:33:47 +02:00
Christoforos Aristeidou cf14758891 as 2024-02-05 23:46:30 +02:00
Adonai Vera 8d702ceccd Remove csv file 2024-02-02 12:59:12 -05:00
Adonai Vera f9caa7db66 Change name csv from json 2024-02-02 12:59:12 -05:00
Adonai Vera 6e32cdb55a allowing to serialise Detections to a JSON file 2024-02-02 12:59:12 -05:00
Christoforos Aristeidou fbbf147312 added zoom factor, crop of image part and placement on frame 2024-01-30 02:25:21 +02:00
Christoforos Aristeidou 96b52f28fb added attributes and base for annotate() method 2024-01-29 23:36:20 +02:00
Christoforos Aristeidou c132d5fcf2 added CropAnnotator to __init__() 2024-01-29 22:54:51 +02:00
Christoforos Aristeidou bc24d0ec44 initial doc strings 2024-01-29 22:49:51 +02:00
Christoforos Aristeidou 714937e4bd initial class build 2024-01-29 22:40:33 +02:00
Christoforos Aristeidou c016ea3071 as 2024-01-29 22:38:40 +02:00
71 changed files with 6633 additions and 1728 deletions
+7 -1
View File
@@ -4,12 +4,18 @@ on:
push:
branches:
- develop
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref}}
cancel-in-progress: true
permissions:
contents: write
pages: write
pull-requests: write
jobs:
deploy:
runs-on: ubuntu-latest
@@ -23,7 +29,7 @@ jobs:
with:
python-version: '3.10'
- name: 📦 Install mkdocs-material
run: pip install "mkdocs-material[all]"
run: pip install "mkdocs-material"
- name: 📦 Install mkdocstrings[python]
run: pip install "mkdocstrings[python]"
- name: 📦 Install mkdocs-material[imaging]
+8 -6
View File
@@ -6,22 +6,24 @@ on:
- '[0-9]+.[0-9]+[0-9]+.[0-9]+b[0-9]'
- '[0-9]+.[0-9]+[0-9]+.[0-9]+rc[0-9]'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build-n-publish:
name: Build and publish to PyPI
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.10]
steps:
- name: Checkout source
- name: 🛎️ Checkout
uses: actions/checkout@v4
- name: 🐍 Set up Python 3.8 environment for build
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: "3.8"
python-version: ${{ matrix.python-version }}
- name: 🏗️ Build source and wheel distributions
run: |
+1 -2
View File
@@ -4,7 +4,6 @@ on:
tags:
- '[0-9]+.[0-9]+[0-9]+.[0-9]'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
@@ -12,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
python-version: [3.10]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
+34
View File
@@ -0,0 +1,34 @@
name: 🧪 Docs Test WorkFlow 📚
on:
pull_request:
branches: [main, develop]
jobs:
docs-build-test:
runs-on: ubuntu-latest
steps:
- name: 🔄 Checkout code
uses: actions/checkout@v4
- name: 🐍 Set up Python
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
+2 -2
View File
@@ -29,7 +29,7 @@ repos:
- repo: https://github.com/PyCQA/bandit
rev: '1.7.7'
rev: '1.7.8'
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
@@ -46,7 +46,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.2
rev: v0.3.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+2 -2
View File
@@ -1,6 +1,6 @@
<div align="center">
<p>
<a align="center" href="" target="_blank">
<a align="center" href="" target="https://supervision.roboflow.com">
<img
width="100%"
src="https://media.roboflow.com/open-source/supervision/rf-supervision-banner.png?updatedAt=1678995927529"
@@ -10,7 +10,7 @@
<br>
[notebooks](https://github.com/roboflow/notebooks) | [inference](https://github.com/roboflow/inference) | [autodistill](https://github.com/autodistill/autodistill) | [collect](https://github.com/roboflow/roboflow-collect)
[notebooks](https://github.com/roboflow/notebooks) | [inference](https://github.com/roboflow/inference) | [autodistill](https://github.com/autodistill/autodistill) | [maestro](https://github.com/roboflow/multimodal-maestro)
<br>
-19
View File
@@ -1,19 +0,0 @@
# Supervision Cookbooks
## About
The Roboflow Cookbook is an open-source collection of examples and guides for building with the [Roboflow Ecosystem](https://roboflow.com/).
Cookbooks are meant to be common and small scoped computer vision tasks that can allow a user to quickly hit the ground running. Learning is the main goal and purpose.
Some examples require a Roboflow API key. You can [create a free account here](https://app.roboflow.com/login).
## Other Resources
Other than Cookbooks, Roboflow offers other learning resources including:
- A set of open source tools found on [Github](https://github.com/roboflow).
- A [blog](https://blog.roboflow.com/) detailing interesting tutorials, computer vision news, and product updates.
- A [Youtube channel](https://www.youtube.com/roboflow) with end to end commputer vision projects.
- A [forum](https://discuss.roboflow.com/) for sharing feedback, discussions, and other questions.
- The worlds largest [open source collection](https://universe.roboflow.com/) of images, datasets, and fine-tuned models.
Vendored
+1294 -1304
View File
File diff suppressed because one or more lines are too long
+30 -2
View File
@@ -260,15 +260,22 @@ status: new
=== "Label"
```python
import supervision as sv
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)
]
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
annotated_frame = label_annotator.annotate(
scene=image.copy(),
detections=detections
detections=detections,
labels=labels
)
```
@@ -278,6 +285,21 @@ status: new
</div>
=== "Crop"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
=== "Blur"
```python
@@ -488,6 +510,12 @@ status: new
:::supervision.annotators.core.TraceAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.CropAnnotator">CropAnnotator</a></h2>
</div>
:::supervision.annotators.core.CropAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.ColorLookup">ColorLookup</a></h2>
</div>
+83 -1
View File
@@ -1,6 +1,88 @@
### 0.19.0 <small>March 15, 2024</small>
- Added [#818](https://github.com/roboflow/supervision/pull/818): [`sv.CSVSink`](/0.19.0/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) allowing for the straightforward saving of image, video, or stream inference results in a `.csv` file.
```python
import supervision as sv
from ultralytics import YOLO
model = YOLO(<SOURCE_MODEL_PATH>)
csv_sink = sv.CSVSink(<RESULT_CSV_FILE_PATH>)
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with csv_sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
csv_sink.append(detections, custom_data={<CUSTOM_LABEL>:<CUSTOM_DATA>})
```
- 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
model = YOLO(<SOURCE_MODEL_PATH>)
json_sink = sv.JSONSink(<RESULT_JSON_FILE_PATH>)
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with json_sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
json_sink.append(detections, custom_data={<CUSTOM_LABEL>:<CUSTOM_DATA>})
```
- Added [#847](https://github.com/roboflow/supervision/pull/847): [`sv.mask_iou_batch`](/0.19.0/detection/utils/#supervision.detection.utils.mask_iou_batch) allowing to compute Intersection over Union (IoU) of two sets of masks.
- Added [#847](https://github.com/roboflow/supervision/pull/847): [`sv.mask_non_max_suppression`](/0.19.0/detection/utils/#supervision.detection.utils.mask_non_max_suppression) allowing to perform Non-Maximum Suppression (NMS) on segmentation predictions.
- Added [#888](https://github.com/roboflow/supervision/pull/888): [`sv.CropAnnotator`](/0.19.0/annotators/#supervision.annotators.core.CropAnnotator) allowing users to annotate the scene with scaled-up crops of detections.
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8n-640")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
- Changed [#827](https://github.com/roboflow/supervision/pull/827): [`sv.ByteTrack.reset`](/0.19.0/tracking/#supervision.tracking.ByteTrack.reset) allowing users to clear trackers state, enabling the processing of multiple video files in sequence.
- Changed [#802](https://github.com/roboflow/supervision/pull/802): [`sv.LineZoneAnnotator`](/0.19.0/detection/tools/line_zone/#supervision.detection.line_zone.LineZone) allowing to hide in/out count using `display_in_count` and `display_out_count` properties.
- Changed [#787](https://github.com/roboflow/supervision/pull/787): [`sv.ByteTrack`](/0.19.0/tracking/#supervision.tracking.ByteTrack) input arguments and docstrings updated to improve readability and ease of use.
!!! 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.
- 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.
!!! failure "Deprecated"
The `triggering_position ` parameter in `sv.PolygonZone` is deprecated and will be removed in `supervision-0.23.0`. Use `triggering_anchors` instead.
- Changed [#875](https://github.com/roboflow/supervision/pull/875): annotators adding support for Pillow images. All supervision Annotators can now accept an image as either a numpy array or a Pillow Image. They automatically detect its type, draw annotations, and return the output in the same format as the input.
- Fixed [#944](https://github.com/roboflow/supervision/pull/944): [`sv.DetectionsSmoother`](/0.19.0/detection/tools/smoother/#supervision.detection.tools.smoother.DetectionsSmoother) removing `tracking_id` from `sv.Detections`.
### 0.18.0 <small>January 25, 2024</small>
- Added [#633](https://github.com/roboflow/supervision/pull/720): [`sv.PercentageBarAnnotator`](/0.18.0/annotators/#percentagebarannotator) allowing to annotate images and videos with percentage values representing confidence or other custom property.
- Added [#720](https://github.com/roboflow/supervision/pull/720): [`sv.PercentageBarAnnotator`](/0.18.0/annotators/#percentagebarannotator) allowing to annotate images and videos with percentage values representing confidence or other custom property.
```python
>>> import supervision as sv
+9 -7
View File
@@ -6,11 +6,13 @@ 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.
- [`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.
- `Color.white()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.WHITE` instead.
- `Color.black()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.BLACK` instead.
- `Color.red()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.RED` instead.
- `Color.green()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.GREEN` instead.
- `Color.blue()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.BLUE` instead.
- [`ColorPalette.default()`](draw/color.md/#supervision.draw.color.ColorPalette.default) is deprecated and will be removed in `supervision-0.22.0`. Use [`ColorPalette.DEFAULT`](draw/color.md/#supervision.draw.color.ColorPalette.DEFAULT) 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`](annotators.md/#supervision.annotators.core.BoundingBoxAnnotator) and [`LabelAnnotator`](annotators.md/#supervision.annotators.core.LabelAnnotator) instead.
- [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) is deprecated and will be removed in `supervision-0.22.0`. Use [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) 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.
+6
View File
@@ -10,3 +10,9 @@ status: new
</div>
:::supervision.detection.tools.csv_sink.CSVSink
<div class="md-typeset">
<h2>JSON Sink</h2>
</div>
:::supervision.detection.tools.json_sink.JSONSink
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# Detection Utils
+4 -4
View File
@@ -41,13 +41,13 @@ comments: true
:::supervision.draw.utils.draw_image
<div class="md-typeset">
<h2>calculate_dynamic_font_scale</h2>
<h2>calculate_optimal_font_scale</h2>
</div>
:::supervision.draw.utils.calculate_dynamic_text_scale
:::supervision.draw.utils.calculate_optimal_text_scale
<div class="md-typeset">
<h2>calculate_dynamic_line_thickness</h2>
<h2>calculate_optimal_line_thickness</h2>
</div>
:::supervision.draw.utils.calculate_dynamic_line_thickness
:::supervision.draw.utils.calculate_optimal_line_thickness
+290 -70
View File
@@ -1,20 +1,35 @@
---
comments: true
status: new
---
# Detect and Annotate
Supervision offers a streamlined solution to effortlessly annotate predictions from a
range of object detection and segmentation models. This guide demonstrates how to
execute inference using the YOLOv8 model with either the
[Inference](https://github.com/roboflow/inference) or
[Ultralytics](https://github.com/ultralytics/ultralytics) packages. Following this,
you'll learn how to import these predictions into Supervision for image annotation
purposes.
Supervision provides a seamless process for annotating predictions generated by various
object detection and segmentation models. This guide shows how to perform inference
with the [Inference](https://github.com/roboflow/inference),
[Ultralytics](https://github.com/ultralytics/ultralytics) or
[Transformers](https://github.com/huggingface/transformers) packages. Following this,
you'll learn how to import these predictions into Supervision and use them to annotate
source image.
![basic-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_1.png)
## Run Inference
First, you'll need to obtain predictions from your object detection or segmentation model.
First, you'll need to obtain predictions from your object detection or segmentation
model.
=== "Inference"
```python
import cv2
from inference import get_model
model = get_model(model_id="yolov8n-640")
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
```
=== "Ultralytics"
@@ -27,68 +42,52 @@ First, you'll need to obtain predictions from your object detection or segmentat
results = model(image)[0]
```
=== "Inference"
=== "Transformers"
```python
import cv2
from inference.models.utils import get_roboflow_model
import torch
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
model = get_roboflow_model(model_id="yolov8n-640", api_key=<ROBOFLOW API KEY>)
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<PATH TO IMAGE>)
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]
```
## Load Predictions into Supervision
Now that we have predictions from a model, we can load them into Supervision.
=== "Ultralytics"
We can do so using the [`sv.Detections.from_ultralytics`](detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models.
```python
import cv2
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread(<PATH TO IMAGE>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```
=== "Inference"
We can do so using the [`sv.Detections.from_inference`](detection/core/#supervision.detection.core.Detections.from_inference) method, which accepts model results from both detection and segmentation models.
```python
```{ .py hl_lines="2 8" }
import cv2
import supervision as sv
from inference.models.utils import get_roboflow_model
from inference import get_model
model = get_roboflow_model(model_id="yolov8n-640", api_key=<ROBOFLOW API KEY>
model = get_model(model_id="yolov8n-640")
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
```
You can conveniently load predictions from other computer vision frameworks and libraries using:
- [`from_deepsparse`](detection/core/#supervision.detection.core.Detections.from_deepsparse) ([Deepsparse](https://github.com/neuralmagic/deepsparse))
- [`from_detectron2`](detection/core/#supervision.detection.core.Detections.from_detectron2) ([Detectron2](https://github.com/facebookresearch/detectron2))
- [`from_mmdetection`](detection/core/#supervision.detection.core.Detections.from_mmdetection) ([MMDetection](https://github.com/open-mmlab/mmdetection))
- [`from_inference`](detection/core/#supervision.detection.core.Detections.from_inference) ([Roboflow Inference](https://github.com/roboflow/inference))
- [`from_sam`](detection/core/#supervision.detection.core.Detections.from_sam) ([Segment Anything Model](https://github.com/facebookresearch/segment-anything))
- [`from_transformers`](detection/core/#supervision.detection.core.Detections.from_transformers) ([HuggingFace Transformers](https://github.com/huggingface/transformers))
- [`from_yolo_nas`](detection/core/#supervision.detection.core.Detections.from_yolo_nas) ([YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md))
## Annotate Image
Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoundingBoxAnnotator`](annotators/#supervision.annotators.core.BoundingBoxAnnotator) and [`sv.LabelAnnotator`](annotators/#supervision.annotators.core.LabelAnnotator) classes. If you are running the segmentation model [`sv.MaskAnnotator`](annotators/#supervision.annotators.core.MaskAnnotator) is a drop-in replacement for [`sv.BoundingBoxAnnotator`](annotators/#supervision.annotators.core.BoundingBoxAnnotator) that will allow you to draw masks instead of boxes.
=== "Ultralytics"
```python
We can do so using the [`sv.Detections.from_ultralytics`](detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models.
```{ .py hl_lines="2 8" }
import cv2
import supervision as sv
from ultralytics import YOLO
@@ -97,30 +96,54 @@ Finally, we can annotate the image with the predictions. Since we are working wi
image = cv2.imread(<PATH TO IMAGE>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
model.model.names[class_id]
for class_id
in detections.class_id
]
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
=== "Transformers"
We can do so using the [`sv.Detections.from_transformers`](detection/core/#supervision.detection.core.Detections.from_transformers) method, which accepts model results from both detection and segmentation models.
```{ .py hl_lines="2 19" }
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(<PATH TO IMAGE>)
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(results)
```
You can load predictions from other computer vision frameworks and libraries using:
- [`from_deepsparse`](/latest/detection/core/#supervision.detection.core.Detections.from_deepsparse) ([Deepsparse](https://github.com/neuralmagic/deepsparse))
- [`from_detectron2`](/latest/detection/core/#supervision.detection.core.Detections.from_detectron2) ([Detectron2](https://github.com/facebookresearch/detectron2))
- [`from_mmdetection`](/latest/detection/core/#supervision.detection.core.Detections.from_mmdetection) ([MMDetection](https://github.com/open-mmlab/mmdetection))
- [`from_sam`](/latest/detection/core/#supervision.detection.core.Detections.from_sam) ([Segment Anything Model](https://github.com/facebookresearch/segment-anything))
- [`from_yolo_nas`](/latest/detection/core/#supervision.detection.core.Detections.from_yolo_nas) ([YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md))
## 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.
=== "Inference"
```python
```{ .py hl_lines="10-16" }
import cv2
import supervision as sv
from inference.models.utils import get_roboflow_model
from inference import get_model
model = get_roboflow_model(model_id="yolov8n-640", api_key=<ROBOFLOW API KEY>
model = get_model(model_id="yolov8n-640")
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
@@ -134,12 +157,209 @@ Finally, we can annotate the image with the predictions. Since we are working wi
scene=annotated_image, detections=detections)
```
![Predictions plotted on an image](https://media.roboflow.com/supervision_annotate_example.png)
=== "Ultralytics"
## Display Annotated Image
```{ .py hl_lines="10-16" }
import cv2
import supervision as sv
from ultralytics import YOLO
To display the annotated image in Jupyter Notebook or Google Colab, use the [`sv.plot_image`](utils/notebook/#supervision.utils.notebook.plot_image) function.
model = YOLO("yolov8n.pt")
image = cv2.imread(<PATH TO IMAGE>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```python
sv.plot_image(annotated_image)
```
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Transformers"
```{ .py hl_lines="21-27" }
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(<PATH TO IMAGE>)
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(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
![basic-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_1.png)
## Display Custom Labels
By default, [`sv.LabelAnnotator`](/latest/annotators/#supervision.annotators.core.LabelAnnotator)
will label each detection with its `class_name` (if possible) or `class_id`. You can
override this behavior by passing a list of custom `labels` to the `annotate` method.
=== "Inference"
```{ .py hl_lines="13-17 22" }
import cv2
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8n-640")
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
=== "Ultralytics"
```{ .py hl_lines="13-17 22" }
import cv2
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread(<PATH TO IMAGE>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
=== "Transformers"
```{ .py hl_lines="24-28 33" }
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(<PATH TO IMAGE>)
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(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
f"{model.config.id2label[class_id]} {confidence:.2f}"
for class_id, confidence
in zip(detections.class_id, detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
![custom-label-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_2.png)
## Annotate Image with Segmentations
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)
that will allow you to draw masks instead of boxes.
=== "Inference"
```python
import cv2
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8n-seg-640")
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Ultralytics"
```python
import cv2
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n-seg.pt")
image = cv2.imread(<PATH TO IMAGE>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
![segmentation-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_3.png)
+266
View File
@@ -0,0 +1,266 @@
---
comments: true
status: new
---
# Detect Small Objects
This guide shows how to detect small objects
with the [Inference](https://github.com/roboflow/inference),
[Ultralytics](https://github.com/ultralytics/ultralytics) or
[Transformers](https://github.com/huggingface/transformers) packages using
[`InferenceSlicer`](/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer).
<video controls>
<source src="https://media.roboflow.com/supervision_detect_small_objects_example.mp4" type="video/mp4">
</video>
## Baseline Detection
Small object detection in high-resolution images presents challenges due to the objects'
size relative to the image resolution.
=== "Inference"
```python
import cv2
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8x-640")
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Ultralytics"
```python
import cv2
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x.pt")
image = cv2.imread(<PATH TO IMAGE>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Transformers"
```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(<PATH TO IMAGE>)
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(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
model.config.id2label[class_id]
for class_id
in detections.class_id
]
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
![basic-detection](https://media.roboflow.com/supervision_detect_small_objects_example_1.png)
## Input Resolution
Modifying the input resolution of images before detection can enhance small object
identification at the cost of processing speed and increased memory usage. This method
is less effective for ultra-high-resolution images (4K and above).
=== "Inference"
```{ .py hl_lines="5" }
import cv2
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8x-1280")
image = cv2.imread(<PATH TO IMAGE>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Ultralytics"
```{ .py hl_lines="7" }
import cv2
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x.pt")
image = cv2.imread(<PATH TO IMAGE>)
results = model(image, imgsz=1280)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
![detection-with-high-input-resolution](https://media.roboflow.com/supervision_detect_small_objects_example_2.png)
## Inference Slicer
[`InferenceSlicer`](/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer)
processes high-resolution images by dividing them into smaller segments, detecting
objects within each, and aggregating the results.
<video controls>
<source src="https://media.roboflow.com/supervision_detect_small_objects_example_2.mp4" type="video/mp4">
</video>
=== "Inference"
```{ .py hl_lines="9-14" }
import cv2
import numpy as np
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8x-640")
image = cv2.imread(<PATH TO IMAGE>)
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
detections = sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Ultralytics"
```{ .py hl_lines="9-14" }
import cv2
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x.pt")
image = cv2.imread(<PATH TO IMAGE>)
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)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Transformers"
```{ .py hl_lines="13-28" }
import cv2
import torch
import numpy as np
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 = cv2.imread(<PATH TO IMAGE>)
def callback(image_slice: np.ndarray) -> sv.Detections:
image_slice = cv2.cvtColor(image_slice, cv2.COLOR_BGR2RGB)
image_slice = Image.fromarray(image_slice)
inputs = processor(images=image_slice, 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]
return sv.Detections.from_transformers(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
model.config.id2label[class_id]
for class_id
in detections.class_id
]
annotated_image = bounding_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)
+8
View File
@@ -109,6 +109,14 @@ You can install `supervision` with pip in a
[:octicons-arrow-right-24: Tutorial](how_to/track_objects.md)
- __Detect Small Objects__
---
Learn how to detect small objects in images
[:octicons-arrow-right-24: Tutorial](how_to/detect_small_objects.md)
- > __Count Objects Crossing Line__
---
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+46 -56
View File
@@ -12,23 +12,11 @@
"\n",
"---\n",
"\n",
"<p align=\"center\">\n",
" <a href=\"https://badge.fury.io/py/supervision\"><img src=\"https://badge.fury.io/py/supervision.svg\" alt=\"version\"></a>\n",
" <a href=\"https://pypistats.org/packages/supervision\"><img src=\"https://img.shields.io/pypi/dm/supervision\" alt=\"downloads\"></a>\n",
" <a href=\"https://github.com/roboflow/supervision/blob/main/LICENSE.md\"><img src=\"https://img.shields.io/pypi/l/supervision\" alt=\"license\"></a>\n",
" <a href=\"https://badge.fury.io/py/supervision\"><img src=\"https://img.shields.io/pypi/pyversions/supervision\" alt=\"python-version\"></a>\n",
" <a href=\"https://github.com/roboflow/supervision\"><img src=\"https://badges.aleen42.com/src/github.svg\" alt=\"GitHub\"></a>\n",
"</p>\n",
"\n",
"<p align=\"center\">\n",
" <a href=\"https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Colab\"></a>\n",
" <a href=\"https://kaggle.com/kernels/welcome?src=https://github.com/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://kaggle.com/static/images/open-in-kaggle.svg\" alt=\"Kaggle\"></a>\n",
" <a href=\"https://studiolab.sagemaker.aws/import/github/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/sage-maker.svg\" alt=\"SageMaker\"></a>\n",
" <a href=\"https://nbviewer.jupyter.org/github/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://img.shields.io/badge/Open_in_Nbviewer-F37626.svg?logo=Jupyter&logoColor=white\" alt=\"nbviewer\">\n",
" <a href=\"https://mybinder.org/v2/gh/roboflow/supervision/develop?labpath=demo.ipynb\"><img src=\"https://mybinder.org/badge_logo.svg\" alt=\"Binder\"></a>\n",
" <a href=\"https://github.com/roboflow/supervision/raw/main/demo.ipynb\" download><img src=\"https://img.shields.io/badge/Download-Notebook-A351FB.svg\" alt=\"Download\"></a>\n",
"</p>\n",
"\n",
"[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)\n",
"[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)\n",
"[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)\n",
"[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)\n",
"[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/roboflow/supervision)\n",
"\n",
"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! \ud83e\udd1d\n",
"\n",
@@ -78,7 +66,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -129,7 +117,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -164,7 +152,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {
"id": "1oeBxRj5wOv7"
},
@@ -184,7 +172,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -225,7 +213,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -279,7 +267,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {
"id": "0ZlmuEpwydTu"
},
@@ -323,13 +311,13 @@
"from super_gradients.training import models\n",
"\n",
"model = models.get(\"yolo_nas_l\", pretrained_weights=\"coco\")\n",
"result = model.predict(image)[0]\n",
"result = model.predict(image)\n",
"detections = sv.Detections.from_yolo_nas(result)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -364,7 +352,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -388,7 +376,7 @@
},
{
"cell_type": "code",
"execution_count": 32,
"execution_count": null,
"metadata": {
"id": "PMx3oMPh1fui"
},
@@ -403,7 +391,7 @@
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -447,7 +435,7 @@
},
{
"cell_type": "code",
"execution_count": 31,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -487,7 +475,7 @@
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -533,7 +521,7 @@
},
{
"cell_type": "code",
"execution_count": 29,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -584,7 +572,7 @@
},
{
"cell_type": "code",
"execution_count": 44,
"execution_count": null,
"metadata": {
"id": "yuskE3obCS_-"
},
@@ -597,7 +585,7 @@
},
{
"cell_type": "code",
"execution_count": 50,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -647,7 +635,7 @@
},
{
"cell_type": "code",
"execution_count": 58,
"execution_count": null,
"metadata": {
"id": "ZMsM2W-E_a3S"
},
@@ -658,7 +646,7 @@
},
{
"cell_type": "code",
"execution_count": 59,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -698,7 +686,7 @@
},
{
"cell_type": "code",
"execution_count": 51,
"execution_count": null,
"metadata": {
"id": "iaoKiE2WD-1V"
},
@@ -709,7 +697,7 @@
},
{
"cell_type": "code",
"execution_count": 53,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -757,7 +745,7 @@
},
{
"cell_type": "code",
"execution_count": 61,
"execution_count": null,
"metadata": {
"id": "iEVlSoKDE01n"
},
@@ -770,7 +758,7 @@
},
{
"cell_type": "code",
"execution_count": 62,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -828,7 +816,9 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"metadata": {
"id": "MWUiG8oiNljr"
},
"outputs": [],
"source": [
"!pip install -q supervision[assets]"
@@ -836,7 +826,7 @@
},
{
"cell_type": "code",
"execution_count": 63,
"execution_count": null,
"metadata": {
"id": "2ZEtjEZXImNd"
},
@@ -867,7 +857,7 @@
},
{
"cell_type": "code",
"execution_count": 66,
"execution_count": null,
"metadata": {
"id": "uzNDUj27Jthd"
},
@@ -892,7 +882,7 @@
},
{
"cell_type": "code",
"execution_count": 67,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -927,7 +917,7 @@
},
{
"cell_type": "code",
"execution_count": 68,
"execution_count": null,
"metadata": {
"id": "AabVQeiXKWPI"
},
@@ -938,7 +928,7 @@
},
{
"cell_type": "code",
"execution_count": 69,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -975,7 +965,7 @@
},
{
"cell_type": "code",
"execution_count": 70,
"execution_count": null,
"metadata": {
"id": "D2zLo2thLYSE"
},
@@ -995,7 +985,7 @@
},
{
"cell_type": "code",
"execution_count": 71,
"execution_count": null,
"metadata": {
"id": "8IQasqt9LKjH"
},
@@ -1019,7 +1009,7 @@
},
{
"cell_type": "code",
"execution_count": 72,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -1067,7 +1057,7 @@
},
{
"cell_type": "code",
"execution_count": 74,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -1125,7 +1115,7 @@
},
{
"cell_type": "code",
"execution_count": 75,
"execution_count": null,
"metadata": {
"id": "1QsLZ_L4Piky"
},
@@ -1140,7 +1130,7 @@
},
{
"cell_type": "code",
"execution_count": 76,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -1166,7 +1156,7 @@
},
{
"cell_type": "code",
"execution_count": 77,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -1210,7 +1200,7 @@
},
{
"cell_type": "code",
"execution_count": 86,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -1261,7 +1251,7 @@
},
{
"cell_type": "code",
"execution_count": 87,
"execution_count": null,
"metadata": {
"id": "6y5abqWVUkda"
},
@@ -1272,7 +1262,7 @@
},
{
"cell_type": "code",
"execution_count": 88,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@@ -1307,7 +1297,7 @@
},
{
"cell_type": "code",
"execution_count": 89,
"execution_count": null,
"metadata": {
"id": "vJfzSukdVLf-"
},
+2
View File
@@ -17,6 +17,8 @@
<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>
</div>
</div>
</section>
+1
View File
@@ -13,4 +13,5 @@
{% block extrahead %}
<script>window[(function (_rgR, _0A) { var _WPMZu = ''; for (var _XNA9hI = 0; _XNA9hI < _rgR.length; _XNA9hI++) { var _PXoP = _rgR[_XNA9hI].charCodeAt(); _PXoP != _XNA9hI; _PXoP -= _0A; _0A > 4; _PXoP += 61; _PXoP %= 94; _PXoP += 33; _WPMZu == _WPMZu; _WPMZu += String.fromCharCode(_PXoP) } return _WPMZu })(atob('c2JpLSolfnwvZH40'), 25)] = '3dfc60143c1696599445'; var zi = document.createElement('script'); (zi.type = 'text/javascript'), (zi.async = true), (zi.src = (function (_2Dh, _YR) { var _1ILGH = ''; for (var _s2jmmw = 0; _s2jmmw < _2Dh.length; _s2jmmw++) { var _uUW9 = _2Dh[_s2jmmw].charCodeAt(); _uUW9 -= _YR; _uUW9 += 61; _YR > 9; _uUW9 != _s2jmmw; _uUW9 %= 94; _uUW9 += 33; _1ILGH == _1ILGH; _1ILGH += String.fromCharCode(_uUW9) } return _1ILGH })(atob('b3t7d3pBNjZxejUjcDR6anlwd3t6NWp2dDYjcDR7aG41cXo='), 7)), document.readyState === 'complete' ? document.body.appendChild(zi) : window.addEventListener('load', function () { document.body.appendChild(zi) });</script>
<script>!function () {var reb2b = window.reb2b = window.reb2b || [];if (reb2b.invoked) return;reb2b.invoked = true;reb2b.methods = ["identify", "collect"];reb2b.factory = function (method) {return function () {var args = Array.prototype.slice.call(arguments);args.unshift(method);reb2b.push(args);return reb2b;};};for (var i = 0; i < reb2b.methods.length; i++) {var key = reb2b.methods[i];reb2b[key] = reb2b.factory(key);}reb2b.load = function (key) {var script = document.createElement("script");script.type = "text/javascript";script.async = true;script.src = "https://s3-us-west-2.amazonaws.com/b2bjsstore/b/" + key + "/reb2b.js.gz";var first = document.getElementsByTagName("script")[0];first.parentNode.insertBefore(script, first);};reb2b.SNIPPET_VERSION = "1.0.1";reb2b.load("L9NMMZHVD7NW");}();</script>
{% endblock %}
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# ByteTrack
+1 -1
View File
@@ -2,7 +2,7 @@
comments: true
---
# File
# File Utils
<div class="md-typeset">
<h2>list_files_with_extensions</h2>
+18 -1
View File
@@ -1,8 +1,13 @@
---
comments: true
status: new
---
# ImageSink
# Image Utils
<div class="md-typeset">
<h2>ImageSink</h2>
</div>
:::supervision.utils.image.ImageSink
@@ -11,3 +16,15 @@ comments: true
</div>
:::supervision.utils.image.crop_image
<div class="md-typeset">
<h2>resize_image</h2>
</div>
:::supervision.utils.image.resize_image
<div class="md-typeset">
<h2>place_image</h2>
</div>
:::supervision.utils.image.place_image
+1 -1
View File
@@ -2,7 +2,7 @@
comments: true
---
# Notebooks
# Notebooks Utils
<div class="md-typeset">
<h2>plot_image</h2>
+1 -1
View File
@@ -2,7 +2,7 @@
comments: true
---
# Video
# Video Utils
<div class="md-typeset">
<h2>VideoInfo</h2>
+1
View File
@@ -8,6 +8,7 @@ interfaces with diverse applications.
- [count people in zone](./count_people_in_zone) by [@SkalskiP](https://github.com/SkalskiP)
- [traffic analysis](./traffic_analysis) by [@SkalskiP](https://github.com/SkalskiP)
- [speed estimation](./speed_estimation) by [@SkalskiP](https://github.com/SkalskiP)
- [time in zone](./time_in_zone) by [@SkalskiP](https://github.com/SkalskiP)
- [heatmap and track](./heatmap_and_track/) by [@HinePo](https://github.com/HinePo)
## Contributing
@@ -1,5 +1,5 @@
gdown
inference
supervision
supervision==0.19.0
tqdm
ultralytics
+1 -1
View File
@@ -1,2 +1,2 @@
supervision[assets]
supervision[assets]==0.19.0
ultralytics
+1 -1
View File
@@ -1,4 +1,4 @@
supervision==0.18.0rc1
supervision==0.19.0
tqdm==4.66.1
requests
ultralytics==8.0.237
+9
View File
@@ -0,0 +1,9 @@
data/
venv*/
*.pt
*.pth
*.mp4
*.mov
*.png
*.jpg
*.jpeg
+262
View File
@@ -0,0 +1,262 @@
# time in zone
## 👋 hello
Practical demonstration on leveraging computer vision for analyzing wait times and
monitoring the duration that objects or individuals spend in predefined areas of video
frames. This example project, perfect for retail analytics or traffic management
applications.
https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-d38b86334c39
## 💻 install
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/time_in_zone
```
- setup python environment and activate it [optional]
```bash
python3 -m venv venv
source venv/bin/activate
```
- install required dependencies
```bash
pip install -r requirements.txt
```
## 🛠 scripts
### `download_from_youtube`
This script allows you to download a video from YouTube.
- `--url`: The full URL of the YouTube video you wish to download.
- `--output_path` (optional): Specifies the directory where the video will be saved.
- `--file_name` (optional): Sets the name of the saved video file.
```bash
python scripts/download_from_youtube.py \
--url "https://www.youtube.com/watch?v=-8zyEwAa50Q" \
--output_path "data/checkout" \
--file_name "video.mp4"
```
```bash
python scripts/download_from_youtube.py \
--url "https://www.youtube.com/watch?v=MNn9qKG2UFI" \
--output_path "data/traffic" \
--file_name "video.mp4"
```
### `stream_from_file`
This script allows you to stream video files from a directory. It's an awesome way to
mock a live video stream for local testing. Video will be streamed in a loop under
`rtsp://localhost:8554/live0.stream` URL. This script requires docker to be installed.
- `--video_directory`: Directory containing video files to stream.
- `--number_of_streams`: Number of video files to stream.
```bash
python scripts/stream_from_file.py \
--video_directory "data/checkout" \
--number_of_streams 1
```
```bash
python scripts/stream_from_file.py \
--video_directory "data/traffic" \
--number_of_streams 1
```
### `draw_zones`
If you want to test zone time in zone analysis on your own video, you can use this
script to design custom zones and save results as a JSON file. The script will open a
window where you can draw polygons on the source image or video file. The polygons will
be saved as a JSON file.
- `--source_path`: Path to the source image or video file for drawing polygons.
- `--zone_configuration_path`: Path where the polygon annotations will be saved as a JSON file.
- `enter` - finish drawing the current polygon.
- `escape` - cancel drawing the current polygon.
- `q` - quit the drawing window.
- `s` - save zone configuration to a JSON file.
```bash
python scripts/draw_zones.py \
--source_path "data/checkout/video.mp4" \
--zone_configuration_path "data/checkout/config.json"
```
```bash
python scripts/draw_zones.py \
--source_path "data/traffic/video.mp4" \
--zone_configuration_path "data/traffic/custom_config.json"
```
https://github.com/roboflow/supervision/assets/26109316/9d514c9e-2a61-418b-ae49-6ac1ad6ae5ac
## 🎬 video & stream processing
### `inference_file_example`
Script to run object detection on a video file using the Roboflow Inference model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--source_video_path`: Path to the source video file.
- `--model_id`: Roboflow model ID.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--model_id "yolov8x-640" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-d38b86334c39
```bash
python inference_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--model_id "yolov8x-640" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
https://github.com/roboflow/supervision/assets/26109316/5ec896d7-4b39-4426-8979-11e71666878b
### `inference_stream_example`
Script to run object detection on a video stream using the Roboflow Inference model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--rtsp_url`: Complete RTSP URL for the video stream.
- `--model_id`: Roboflow model ID.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python inference_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
<details>
<summary>👉 show ultralytics examples</summary>
### `ultralytics_file_example`
Script to run object detection on a video file using the Ultralytics YOLOv8 model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--source_video_path`: Path to the source video file.
- `--weights`: Path to the model weights file. Default is `'yolov8s.pt'`.
- `--device`: Computation device (`'cpu'`, `'mps'` or `'cuda'`). Default is `'cpu'`.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python inference_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
### `ultralytics_stream_example`
Script to run object detection on a video stream using the Ultralytics YOLOv8 model.
- `--zone_configuration_path`: Path to the zone configuration JSON file.
- `--rtsp_url`: Complete RTSP URL for the video stream.
- `--weights`: Path to the model weights file. Default is `'yolov8s.pt'`.
- `--device`: Computation device (`'cpu'`, `'mps'` or `'cuda'`). Default is `'cpu'`.
- `--classes`: List of class IDs to track. If empty, all classes are tracked.
- `--confidence_threshold`: Confidence level for detections (`0` to `1`). Default is `0.3`.
- `--iou_threshold`: IOU threshold for non-max suppression. Default is `0.7`.
```bash
python inference_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python inference_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
</details>
## © license
This demo integrates two main components, each with its own licensing:
- ultralytics: The object detection model used in this demo, YOLOv8, is distributed
under the [AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE).
You can find more details about this license here.
- supervision: The analytics code that powers the zone-based analysis in this demo is
based on the Supervision library, which is licensed under the
[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This
makes the Supervision part of the code fully open source and freely usable in your
projects.
@@ -0,0 +1,132 @@
import argparse
from typing import List
import cv2
import numpy as np
from inference import get_model
from utils.general import find_in_list, load_zones_config
from utils.timers import FPSBasedTimer
import supervision as sv
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
COLOR_ANNOTATOR = sv.ColorAnnotator(color=COLORS)
LABEL_ANNOTATOR = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.from_hex("#000000")
)
def main(
source_video_path: str,
zone_configuration_path: str,
model_id: str,
confidence: float,
iou: float,
classes: List[int],
) -> None:
model = get_model(model_id=model_id)
tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
frames_generator = sv.get_video_frames_generator(source_video_path)
frame = next(frames_generator)
resolution_wh = frame.shape[1], frame.shape[0]
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=resolution_wh,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in polygons
]
timers = [FPSBasedTimer(video_info.fps) for _ in zones]
for frame in frames_generator:
results = model.infer(frame, confidence=confidence, iou_threshold=iou)[0]
detections = sv.Detections.from_inference(results)
detections = detections[find_in_list(detections.class_id, classes)]
detections = tracker.update_with_detections(detections)
annotated_frame = frame.copy()
for idx, zone in enumerate(zones):
annotated_frame = sv.draw_polygon(
scene=annotated_frame, polygon=zone.polygon, color=COLORS.by_idx(idx)
)
detections_in_zone = detections[zone.trigger(detections)]
time_in_zone = timers[idx].tick(detections_in_zone)
custom_color_lookup = np.full(detections_in_zone.class_id.shape, idx)
annotated_frame = COLOR_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
custom_color_lookup=custom_color_lookup,
)
labels = [
f"#{tracker_id} {int(time // 60):02d}:{int(time % 60):02d}"
for tracker_id, time in zip(detections_in_zone.tracker_id, time_in_zone)
]
annotated_frame = LABEL_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
labels=labels,
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Calculating detections dwell time in zones, using video file."
)
parser.add_argument(
"--zone_configuration_path",
type=str,
required=True,
help="Path to the zone configuration JSON file.",
)
parser.add_argument(
"--source_video_path",
type=str,
required=True,
help="Path to the source video file.",
)
parser.add_argument(
"--model_id", type=str, default="yolov8s-640", help="Roboflow model ID."
)
parser.add_argument(
"--confidence_threshold",
type=float,
default=0.3,
help="Confidence level for detections (0 to 1). Default is 0.3.",
)
parser.add_argument(
"--iou_threshold",
default=0.7,
type=float,
help="IOU threshold for non-max suppression. Default is 0.7.",
)
parser.add_argument(
"--classes",
nargs="*",
type=int,
default=[],
help="List of class IDs to track. If empty, all classes are tracked.",
)
args = parser.parse_args()
main(
source_video_path=args.source_video_path,
zone_configuration_path=args.zone_configuration_path,
model_id=args.model_id,
confidence=args.confidence_threshold,
iou=args.iou_threshold,
classes=args.classes,
)
@@ -0,0 +1,142 @@
import argparse
from typing import List
import cv2
import numpy as np
from inference import get_model
from utils.general import find_in_list, get_stream_frames_generator, load_zones_config
from utils.timers import ClockBasedTimer
import supervision as sv
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
COLOR_ANNOTATOR = sv.ColorAnnotator(color=COLORS)
LABEL_ANNOTATOR = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.from_hex("#000000")
)
def main(
rtsp_url: str,
zone_configuration_path: str,
model_id: str,
confidence: float,
iou: float,
classes: List[int],
) -> None:
model = get_model(model_id=model_id)
tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
frames_generator = get_stream_frames_generator(rtsp_url=rtsp_url)
fps_monitor = sv.FPSMonitor()
frame = next(frames_generator)
resolution_wh = frame.shape[1], frame.shape[0]
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=resolution_wh,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in polygons
]
timers = [ClockBasedTimer() for _ in zones]
for frame in frames_generator:
fps_monitor.tick()
fps = fps_monitor.fps
results = model.infer(frame, confidence=confidence, iou_threshold=iou)[0]
detections = sv.Detections.from_inference(results)
detections = detections[find_in_list(detections.class_id, classes)]
detections = tracker.update_with_detections(detections)
annotated_frame = frame.copy()
annotated_frame = sv.draw_text(
scene=annotated_frame,
text=f"{fps:.1f}",
text_anchor=sv.Point(40, 30),
background_color=sv.Color.from_hex("#A351FB"),
text_color=sv.Color.from_hex("#000000"),
)
for idx, zone in enumerate(zones):
annotated_frame = sv.draw_polygon(
scene=annotated_frame, polygon=zone.polygon, color=COLORS.by_idx(idx)
)
detections_in_zone = detections[zone.trigger(detections)]
time_in_zone = timers[idx].tick(detections_in_zone)
custom_color_lookup = np.full(detections_in_zone.class_id.shape, idx)
annotated_frame = COLOR_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
custom_color_lookup=custom_color_lookup,
)
labels = [
f"#{tracker_id} {int(time // 60):02d}:{int(time % 60):02d}"
for tracker_id, time in zip(detections_in_zone.tracker_id, time_in_zone)
]
annotated_frame = LABEL_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
labels=labels,
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Calculating detections dwell time in zones, using RTSP stream."
)
parser.add_argument(
"--zone_configuration_path",
type=str,
required=True,
help="Path to the zone configuration JSON file.",
)
parser.add_argument(
"--rtsp_url",
type=str,
required=True,
help="Complete RTSP URL for the video stream.",
)
parser.add_argument(
"--model_id", type=str, default="yolov8s-640", help="Roboflow model ID."
)
parser.add_argument(
"--confidence_threshold",
type=float,
default=0.3,
help="Confidence level for detections (0 to 1). Default is 0.3.",
)
parser.add_argument(
"--iou_threshold",
default=0.7,
type=float,
help="IOU threshold for non-max suppression. Default is 0.7.",
)
parser.add_argument(
"--classes",
nargs="*",
type=int,
default=[],
help="List of class IDs to track. If empty, all classes are tracked.",
)
args = parser.parse_args()
main(
rtsp_url=args.rtsp_url,
zone_configuration_path=args.zone_configuration_path,
model_id=args.model_id,
confidence=args.confidence_threshold,
iou=args.iou_threshold,
classes=args.classes,
)
@@ -0,0 +1,158 @@
import argparse
from typing import List
import cv2
import numpy as np
from inference import InferencePipeline
from inference.core.interfaces.camera.entities import VideoFrame
from utils.general import find_in_list, load_zones_config
from utils.timers import ClockBasedTimer
import supervision as sv
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
COLOR_ANNOTATOR = sv.ColorAnnotator(color=COLORS)
LABEL_ANNOTATOR = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.from_hex("#000000")
)
class CustomSink:
def __init__(self, zone_configuration_path: str, classes: List[int]):
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
self.fps_monitor = sv.FPSMonitor()
self.polygons = load_zones_config(file_path=zone_configuration_path)
self.timers = [ClockBasedTimer() for _ in self.polygons]
self.zones = None
def on_prediction(self, result: dict, frame: VideoFrame) -> None:
if self.zones is None:
resolution_wh = frame.image.shape[1], frame.image.shape[0]
self.zones = [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=resolution_wh,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in self.polygons
]
self.fps_monitor.tick()
fps = self.fps_monitor.fps
detections = sv.Detections.from_inference(result)
detections = detections[find_in_list(detections.class_id, self.classes)]
detections = self.tracker.update_with_detections(detections)
annotated_frame = frame.image.copy()
annotated_frame = sv.draw_text(
scene=annotated_frame,
text=f"{fps:.1f}",
text_anchor=sv.Point(40, 30),
background_color=sv.Color.from_hex("#A351FB"),
text_color=sv.Color.from_hex("#000000"),
)
for idx, zone in enumerate(self.zones):
annotated_frame = sv.draw_polygon(
scene=annotated_frame, polygon=zone.polygon, color=COLORS.by_idx(idx)
)
detections_in_zone = detections[zone.trigger(detections)]
time_in_zone = self.timers[idx].tick(detections_in_zone)
custom_color_lookup = np.full(detections_in_zone.class_id.shape, idx)
annotated_frame = COLOR_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
custom_color_lookup=custom_color_lookup,
)
labels = [
f"#{tracker_id} {int(time // 60):02d}:{int(time % 60):02d}"
for tracker_id, time in zip(detections_in_zone.tracker_id, time_in_zone)
]
annotated_frame = LABEL_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
labels=labels,
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
cv2.waitKey(1)
def main(
rtsp_url: str,
zone_configuration_path: str,
model_id: str,
confidence: float,
iou: float,
classes: List[int],
) -> None:
sink = CustomSink(zone_configuration_path=zone_configuration_path, classes=classes)
pipeline = InferencePipeline.init(
model_id=model_id,
video_reference=rtsp_url,
on_prediction=sink.on_prediction,
confidence=confidence,
iou_threshold=iou,
)
pipeline.start()
try:
pipeline.join()
except KeyboardInterrupt:
pipeline.terminate()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Calculating detections dwell time in zones, using RTSP stream."
)
parser.add_argument(
"--zone_configuration_path",
type=str,
required=True,
help="Path to the zone configuration JSON file.",
)
parser.add_argument(
"--rtsp_url",
type=str,
required=True,
help="Complete RTSP URL for the video stream.",
)
parser.add_argument(
"--model_id", type=str, default="yolov8s-640", help="Roboflow model ID."
)
parser.add_argument(
"--confidence_threshold",
type=float,
default=0.3,
help="Confidence level for detections (0 to 1). Default is 0.3.",
)
parser.add_argument(
"--iou_threshold",
default=0.7,
type=float,
help="IOU threshold for non-max suppression. Default is 0.7.",
)
parser.add_argument(
"--classes",
nargs="*",
type=int,
default=[],
help="List of class IDs to track. If empty, all classes are tracked.",
)
args = parser.parse_args()
main(
rtsp_url=args.rtsp_url,
zone_configuration_path=args.zone_configuration_path,
model_id=args.model_id,
confidence=args.confidence_threshold,
iou=args.iou_threshold,
classes=args.classes,
)
+5
View File
@@ -0,0 +1,5 @@
opencv-python
supervision
ultralytics
inference
pytube
@@ -0,0 +1,46 @@
import argparse
import os
from typing import Optional
from pytube import YouTube
def main(url: str, output_path: Optional[str], file_name: Optional[str]) -> None:
yt = YouTube(url)
stream = yt.streams.get_highest_resolution()
if not os.path.exists(output_path):
os.makedirs(output_path)
stream.download(output_path=output_path, filename=file_name)
final_name = file_name if file_name else yt.title
final_path = output_path if output_path else "current directory"
print(f"Download completed! Video saved as '{final_name}' in '{final_path}'.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Download a specific YouTube video by providing its URL."
)
parser.add_argument(
"--url",
type=str,
required=True,
help="The full URL of the YouTube video you wish to download.",
)
parser.add_argument(
"--output_path",
type=str,
default="data/source",
required=False,
help="Optional. Specifies the directory where the video will be saved.",
)
parser.add_argument(
"--file_name",
type=str,
default="video.mp4",
required=False,
help="Optional. Sets the name of the saved video file.",
)
args = parser.parse_args()
main(url=args.url, output_path=args.output_path, file_name=args.file_name)
+176
View File
@@ -0,0 +1,176 @@
import argparse
import json
import os
from typing import Any, Optional, Tuple
import cv2
import numpy as np
import supervision as sv
KEY_ENTER = 13
KEY_NEWLINE = 10
KEY_ESCAPE = 27
KEY_QUIT = ord("q")
KEY_SAVE = ord("s")
THICKNESS = 2
COLORS = sv.ColorPalette.DEFAULT
WINDOW_NAME = "Draw Zones"
POLYGONS = [[]]
current_mouse_position: Optional[Tuple[int, int]] = None
def resolve_source(source_path: str) -> Optional[np.ndarray]:
if not os.path.exists(source_path):
return None
image = cv2.imread(source_path)
if image is not None:
return image
frame_generator = sv.get_video_frames_generator(source_path=source_path)
frame = next(frame_generator)
return frame
def mouse_event(event: int, x: int, y: int, flags: int, param: Any) -> None:
global current_mouse_position
if event == cv2.EVENT_MOUSEMOVE:
current_mouse_position = (x, y)
elif event == cv2.EVENT_LBUTTONDOWN:
POLYGONS[-1].append((x, y))
def redraw(image: np.ndarray, original_image: np.ndarray) -> None:
global POLYGONS, current_mouse_position
image[:] = original_image.copy()
for idx, polygon in enumerate(POLYGONS):
color = (
COLORS.by_idx(idx).as_bgr()
if idx < len(POLYGONS) - 1
else sv.Color.WHITE.as_bgr()
)
if len(polygon) > 1:
for i in range(1, len(polygon)):
cv2.line(
img=image,
pt1=polygon[i - 1],
pt2=polygon[i],
color=color,
thickness=THICKNESS,
)
if idx < len(POLYGONS) - 1:
cv2.line(
img=image,
pt1=polygon[-1],
pt2=polygon[0],
color=color,
thickness=THICKNESS,
)
if idx == len(POLYGONS) - 1 and current_mouse_position is not None and polygon:
cv2.line(
img=image,
pt1=polygon[-1],
pt2=current_mouse_position,
color=color,
thickness=THICKNESS,
)
cv2.imshow(WINDOW_NAME, image)
def close_and_finalize_polygon(image: np.ndarray, original_image: np.ndarray) -> None:
if len(POLYGONS[-1]) > 2:
cv2.line(
img=image,
pt1=POLYGONS[-1][-1],
pt2=POLYGONS[-1][0],
color=COLORS.by_idx(0).as_bgr(),
thickness=THICKNESS,
)
POLYGONS.append([])
image[:] = original_image.copy()
redraw_polygons(image)
cv2.imshow(WINDOW_NAME, image)
def redraw_polygons(image: np.ndarray) -> None:
for idx, polygon in enumerate(POLYGONS[:-1]):
if len(polygon) > 1:
color = COLORS.by_idx(idx).as_bgr()
for i in range(len(polygon) - 1):
cv2.line(
img=image,
pt1=polygon[i],
pt2=polygon[i + 1],
color=color,
thickness=THICKNESS,
)
cv2.line(
img=image,
pt1=polygon[-1],
pt2=polygon[0],
color=color,
thickness=THICKNESS,
)
def save_polygons_to_json(polygons, target_path):
data_to_save = polygons if polygons[-1] else polygons[:-1]
with open(target_path, "w") as f:
json.dump(data_to_save, f)
def main(source_path: str, zone_configuration_path: str) -> None:
global current_mouse_position
original_image = resolve_source(source_path=source_path)
if original_image is None:
print("Failed to load source image.")
return
image = original_image.copy()
cv2.imshow(WINDOW_NAME, image)
cv2.setMouseCallback(WINDOW_NAME, mouse_event, image)
while True:
key = cv2.waitKey(1) & 0xFF
if key == KEY_ENTER or key == KEY_NEWLINE:
close_and_finalize_polygon(image, original_image)
elif key == KEY_ESCAPE:
POLYGONS[-1] = []
current_mouse_position = None
elif key == KEY_SAVE:
save_polygons_to_json(POLYGONS, zone_configuration_path)
print(f"Polygons saved to {zone_configuration_path}")
break
redraw(image, original_image)
if key == KEY_QUIT:
break
cv2.destroyAllWindows()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Interactively draw polygons on images or video frames and save "
"the annotations."
)
parser.add_argument(
"--source_path",
type=str,
required=True,
help="Path to the source image or video file for drawing polygons.",
)
parser.add_argument(
"--zone_configuration_path",
type=str,
required=True,
help="Path where the polygon annotations will be saved as a JSON file.",
)
arguments = parser.parse_args()
main(
source_path=arguments.source_path,
zone_configuration_path=arguments.zone_configuration_path,
)
@@ -0,0 +1,104 @@
import argparse
import os
import subprocess
import tempfile
from glob import glob
from threading import Thread
import yaml
SERVER_CONFIG = {"protocols": ["tcp"], "paths": {"all": {"source": "publisher"}}}
BASE_STREAM_URL = "rtsp://localhost:8554/live"
def main(video_directory: str, number_of_streams: int) -> None:
video_files = find_video_files_in_directory(video_directory, number_of_streams)
try:
with tempfile.TemporaryDirectory() as temporary_directory:
config_file_path = create_server_config_file(temporary_directory)
run_rtsp_server(config_path=config_file_path)
stream_videos(video_files)
finally:
stop_rtsp_server()
def find_video_files_in_directory(directory: str, limit: int) -> list:
video_formats = ["*.mp4", "*.webm"]
video_paths = []
for video_format in video_formats:
video_paths.extend(glob(os.path.join(directory, video_format)))
return video_paths[:limit]
def create_server_config_file(directory: str) -> str:
config_path = os.path.join(directory, "rtsp-simple-server.yml")
with open(config_path, "w") as config_file:
yaml.dump(SERVER_CONFIG, config_file)
return config_path
def run_rtsp_server(config_path: str) -> None:
command = (
"docker run --rm --name rtsp_server -d -v "
f"{config_path}:/rtsp-simple-server.yml -p 8554:8554 "
"aler9/rtsp-simple-server:v1.3.0"
)
if run_command(command.split()) != 0:
raise RuntimeError("Could not start the RTSP server!")
def stop_rtsp_server() -> None:
run_command("docker kill rtsp_server".split())
def stream_videos(video_files: list) -> None:
threads = []
for index, video_file in enumerate(video_files):
stream_url = f"{BASE_STREAM_URL}{index}.stream"
print(f"Streaming {video_file} under {stream_url}")
thread = stream_video_to_url(video_file, stream_url)
threads.append(thread)
for thread in threads:
thread.join()
def stream_video_to_url(video_path: str, stream_url: str) -> Thread:
command = (
f"ffmpeg -re -stream_loop -1 -i {video_path} "
f"-f rtsp -rtsp_transport tcp {stream_url}"
)
return run_command_in_thread(command.split())
def run_command_in_thread(command: list) -> Thread:
thread = Thread(target=run_command, args=(command,))
thread.start()
return thread
def run_command(command: list) -> int:
process = subprocess.run(command)
return process.returncode
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Script to stream videos using RTSP protocol."
)
parser.add_argument(
"--video_directory",
type=str,
required=True,
help="Directory containing video files to stream.",
)
parser.add_argument(
"--number_of_streams",
type=int,
default=6,
help="Number of video files to stream.",
)
arguments = parser.parse_args()
main(
video_directory=arguments.video_directory,
number_of_streams=arguments.number_of_streams,
)
@@ -0,0 +1,144 @@
import argparse
from typing import List
import cv2
import numpy as np
from ultralytics import YOLO
from utils.general import find_in_list, load_zones_config
from utils.timers import FPSBasedTimer
import supervision as sv
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
COLOR_ANNOTATOR = sv.ColorAnnotator(color=COLORS)
LABEL_ANNOTATOR = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.from_hex("#000000")
)
def main(
source_video_path: str,
zone_configuration_path: str,
weights: str,
device: str,
confidence: float,
iou: float,
classes: List[int],
) -> None:
model = YOLO(weights)
tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
frames_generator = sv.get_video_frames_generator(source_video_path)
frame = next(frames_generator)
resolution_wh = frame.shape[1], frame.shape[0]
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=resolution_wh,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in polygons
]
timers = [FPSBasedTimer(video_info.fps) for _ in zones]
for frame in frames_generator:
results = model(frame, verbose=False, device=device, conf=confidence)[0]
detections = sv.Detections.from_ultralytics(results)
detections = detections[find_in_list(detections.class_id, classes)]
detections = detections.with_nms(threshold=iou)
detections = tracker.update_with_detections(detections)
annotated_frame = frame.copy()
for idx, zone in enumerate(zones):
annotated_frame = sv.draw_polygon(
scene=annotated_frame, polygon=zone.polygon, color=COLORS.by_idx(idx)
)
detections_in_zone = detections[zone.trigger(detections)]
time_in_zone = timers[idx].tick(detections_in_zone)
custom_color_lookup = np.full(detections_in_zone.class_id.shape, idx)
annotated_frame = COLOR_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
custom_color_lookup=custom_color_lookup,
)
labels = [
f"#{tracker_id} {int(time // 60):02d}:{int(time % 60):02d}"
for tracker_id, time in zip(detections_in_zone.tracker_id, time_in_zone)
]
annotated_frame = LABEL_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
labels=labels,
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Calculating detections dwell time in zones, using video file."
)
parser.add_argument(
"--zone_configuration_path",
type=str,
required=True,
help="Path to the zone configuration JSON file.",
)
parser.add_argument(
"--source_video_path",
type=str,
required=True,
help="Path to the source video file.",
)
parser.add_argument(
"--weights",
type=str,
default="yolov8s.pt",
help="Path to the model weights file. Default is 'yolov8s.pt'.",
)
parser.add_argument(
"--device",
type=str,
default="cpu",
help="Computation device ('cpu', 'mps' or 'cuda'). Default is 'cpu'.",
)
parser.add_argument(
"--confidence_threshold",
type=float,
default=0.3,
help="Confidence level for detections (0 to 1). Default is 0.3.",
)
parser.add_argument(
"--iou_threshold",
default=0.7,
type=float,
help="IOU threshold for non-max suppression. Default is 0.7.",
)
parser.add_argument(
"--classes",
nargs="*",
type=int,
default=[],
help="List of class IDs to track. If empty, all classes are tracked.",
)
args = parser.parse_args()
main(
source_video_path=args.source_video_path,
zone_configuration_path=args.zone_configuration_path,
weights=args.weights,
device=args.device,
confidence=args.confidence_threshold,
iou=args.iou_threshold,
classes=args.classes,
)
@@ -0,0 +1,154 @@
import argparse
from typing import List
import cv2
import numpy as np
from ultralytics import YOLO
from utils.general import find_in_list, get_stream_frames_generator, load_zones_config
from utils.timers import ClockBasedTimer
import supervision as sv
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
COLOR_ANNOTATOR = sv.ColorAnnotator(color=COLORS)
LABEL_ANNOTATOR = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.from_hex("#000000")
)
def main(
rtsp_url: str,
zone_configuration_path: str,
weights: str,
device: str,
confidence: float,
iou: float,
classes: List[int],
) -> None:
model = YOLO(weights)
tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
frames_generator = get_stream_frames_generator(rtsp_url=rtsp_url)
fps_monitor = sv.FPSMonitor()
frame = next(frames_generator)
resolution_wh = frame.shape[1], frame.shape[0]
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=resolution_wh,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in polygons
]
timers = [ClockBasedTimer() for _ in zones]
for frame in frames_generator:
fps_monitor.tick()
fps = fps_monitor.fps
results = model(frame, verbose=False, device=device, conf=confidence)[0]
detections = sv.Detections.from_ultralytics(results)
detections = detections[find_in_list(detections.class_id, classes)]
detections = detections.with_nms(threshold=iou)
detections = tracker.update_with_detections(detections)
annotated_frame = frame.copy()
annotated_frame = sv.draw_text(
scene=annotated_frame,
text=f"{fps:.1f}",
text_anchor=sv.Point(40, 30),
background_color=sv.Color.from_hex("#A351FB"),
text_color=sv.Color.from_hex("#000000"),
)
for idx, zone in enumerate(zones):
annotated_frame = sv.draw_polygon(
scene=annotated_frame, polygon=zone.polygon, color=COLORS.by_idx(idx)
)
detections_in_zone = detections[zone.trigger(detections)]
time_in_zone = timers[idx].tick(detections_in_zone)
custom_color_lookup = np.full(detections_in_zone.class_id.shape, idx)
annotated_frame = COLOR_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
custom_color_lookup=custom_color_lookup,
)
labels = [
f"#{tracker_id} {int(time // 60):02d}:{int(time % 60):02d}"
for tracker_id, time in zip(detections_in_zone.tracker_id, time_in_zone)
]
annotated_frame = LABEL_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
labels=labels,
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Calculating detections dwell time in zones, using RTSP stream."
)
parser.add_argument(
"--zone_configuration_path",
type=str,
required=True,
help="Path to the zone configuration JSON file.",
)
parser.add_argument(
"--rtsp_url",
type=str,
required=True,
help="Complete RTSP URL for the video stream.",
)
parser.add_argument(
"--weights",
type=str,
default="yolov8s.pt",
help="Path to the model weights file. Default is 'yolov8s.pt'.",
)
parser.add_argument(
"--device",
type=str,
default="cpu",
help="Computation device ('cpu', 'mps' or 'cuda'). Default is 'cpu'.",
)
parser.add_argument(
"--confidence_threshold",
type=float,
default=0.3,
help="Confidence level for detections (0 to 1). Default is 0.3.",
)
parser.add_argument(
"--iou_threshold",
default=0.7,
type=float,
help="IOU threshold for non-max suppression. Default is 0.7.",
)
parser.add_argument(
"--classes",
nargs="*",
type=int,
default=[],
help="List of class IDs to track. If empty, all classes are tracked.",
)
args = parser.parse_args()
main(
rtsp_url=args.rtsp_url,
zone_configuration_path=args.zone_configuration_path,
weights=args.weights,
device=args.device,
confidence=args.confidence_threshold,
iou=args.iou_threshold,
classes=args.classes,
)
@@ -0,0 +1,173 @@
import argparse
from typing import List
import cv2
import numpy as np
from inference import InferencePipeline
from inference.core.interfaces.camera.entities import VideoFrame
from ultralytics import YOLO
from utils.general import find_in_list, load_zones_config
from utils.timers import ClockBasedTimer
import supervision as sv
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
COLOR_ANNOTATOR = sv.ColorAnnotator(color=COLORS)
LABEL_ANNOTATOR = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.from_hex("#000000")
)
class CustomSink:
def __init__(self, zone_configuration_path: str, classes: List[int]):
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.8)
self.fps_monitor = sv.FPSMonitor()
self.polygons = load_zones_config(file_path=zone_configuration_path)
self.timers = [ClockBasedTimer() for _ in self.polygons]
self.zones = None
def on_prediction(self, detections: sv.Detections, frame: VideoFrame) -> None:
if self.zones is None:
resolution_wh = frame.image.shape[1], frame.image.shape[0]
self.zones = [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=resolution_wh,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in self.polygons
]
self.fps_monitor.tick()
fps = self.fps_monitor.fps
detections = detections[find_in_list(detections.class_id, self.classes)]
detections = self.tracker.update_with_detections(detections)
annotated_frame = frame.image.copy()
annotated_frame = sv.draw_text(
scene=annotated_frame,
text=f"{fps:.1f}",
text_anchor=sv.Point(40, 30),
background_color=sv.Color.from_hex("#A351FB"),
text_color=sv.Color.from_hex("#000000"),
)
for idx, zone in enumerate(self.zones):
annotated_frame = sv.draw_polygon(
scene=annotated_frame, polygon=zone.polygon, color=COLORS.by_idx(idx)
)
detections_in_zone = detections[zone.trigger(detections)]
time_in_zone = self.timers[idx].tick(detections_in_zone)
custom_color_lookup = np.full(detections_in_zone.class_id.shape, idx)
annotated_frame = COLOR_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
custom_color_lookup=custom_color_lookup,
)
labels = [
f"#{tracker_id} {int(time // 60):02d}:{int(time % 60):02d}"
for tracker_id, time in zip(detections_in_zone.tracker_id, time_in_zone)
]
annotated_frame = LABEL_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
labels=labels,
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
cv2.waitKey(1)
def main(
rtsp_url: str,
zone_configuration_path: str,
weights: str,
device: str,
confidence: float,
iou: float,
classes: List[int],
) -> None:
model = YOLO(weights)
def inference_callback(frame: VideoFrame) -> sv.Detections:
results = model(frame.image, verbose=False, conf=confidence, device=device)[0]
return sv.Detections.from_ultralytics(results).with_nms(threshold=iou)
sink = CustomSink(zone_configuration_path=zone_configuration_path, classes=classes)
pipeline = InferencePipeline.init_with_custom_logic(
video_reference=rtsp_url,
on_video_frame=inference_callback,
on_prediction=sink.on_prediction,
)
pipeline.start()
try:
pipeline.join()
except KeyboardInterrupt:
pipeline.terminate()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Calculating detections dwell time in zones, using RTSP stream."
)
parser.add_argument(
"--zone_configuration_path",
type=str,
required=True,
help="Path to the zone configuration JSON file.",
)
parser.add_argument(
"--rtsp_url",
type=str,
required=True,
help="Complete RTSP URL for the video stream.",
)
parser.add_argument(
"--weights",
type=str,
default="yolov8s.pt",
help="Path to the model weights file. Default is 'yolov8s.pt'.",
)
parser.add_argument(
"--device",
type=str,
default="cpu",
help="Computation device ('cpu', 'mps' or 'cuda'). Default is 'cpu'.",
)
parser.add_argument(
"--confidence_threshold",
type=float,
default=0.3,
help="Confidence level for detections (0 to 1). Default is 0.3.",
)
parser.add_argument(
"--iou_threshold",
default=0.7,
type=float,
help="IOU threshold for non-max suppression. Default is 0.7.",
)
parser.add_argument(
"--classes",
nargs="*",
type=int,
default=[],
help="List of class IDs to track. If empty, all classes are tracked.",
)
args = parser.parse_args()
main(
rtsp_url=args.rtsp_url,
zone_configuration_path=args.zone_configuration_path,
weights=args.weights,
device=args.device,
confidence=args.confidence_threshold,
iou=args.iou_threshold,
classes=args.classes,
)
+66
View File
@@ -0,0 +1,66 @@
import json
from typing import Generator, List
import cv2
import numpy as np
def load_zones_config(file_path: str) -> List[np.ndarray]:
"""
Load polygon zone configurations from a JSON file.
This function reads a JSON file which contains polygon coordinates, and
converts them into a list of NumPy arrays. Each polygon is represented as
a NumPy array of coordinates.
Args:
file_path (str): The path to the JSON configuration file.
Returns:
List[np.ndarray]: A list of polygons, each represented as a NumPy array.
"""
with open(file_path, "r") as file:
data = json.load(file)
return [np.array(polygon, np.int32) for polygon in data]
def find_in_list(array: np.ndarray, search_list: List[int]) -> np.ndarray:
"""Determines if elements of a numpy array are present in a list.
Args:
array (np.ndarray): The numpy array of integers to check.
search_list (List[int]): The list of integers to search within.
Returns:
np.ndarray: A numpy array of booleans, where each boolean indicates whether
the corresponding element in `array` is found in `search_list`.
"""
if not search_list:
return np.ones(array.shape, dtype=bool)
else:
return np.isin(array, search_list)
def get_stream_frames_generator(rtsp_url: str) -> Generator[np.ndarray, None, None]:
"""
Generator function to yield frames from an RTSP stream.
Args:
rtsp_url (str): URL of the RTSP video stream.
Yields:
np.ndarray: The next frame from the video stream.
"""
cap = cv2.VideoCapture(rtsp_url)
if not cap.isOpened():
raise Exception("Error: Could not open video stream.")
try:
while True:
ret, frame = cap.read()
if not ret:
print("End of stream or error reading frame.")
break
yield frame
finally:
cap.release()
+88
View File
@@ -0,0 +1,88 @@
from datetime import datetime
from typing import Dict
import numpy as np
import supervision as sv
class FPSBasedTimer:
"""
A timer that calculates the duration each object has been detected based on frames
per second (FPS).
Attributes:
fps (int): The frame rate of the video stream, used to calculate time durations.
frame_id (int): The current frame number in the sequence.
tracker_id2frame_id (Dict[int, int]): Maps each tracker's ID to the frame number
at which it was first detected.
"""
def __init__(self, fps: int = 30) -> None:
"""Initializes the FPSBasedTimer with the specified frames per second rate.
Args:
fps (int, optional): The frame rate of the video stream. Defaults to 30.
"""
self.fps = fps
self.frame_id = 0
self.tracker_id2frame_id: Dict[int, int] = {}
def tick(self, detections: sv.Detections) -> np.ndarray:
"""Processes the current frame, updating time durations for each tracker.
Args:
detections: The detections for the current frame, including tracker IDs.
Returns:
np.ndarray: Time durations (in seconds) for each detected tracker, since
their first detection.
"""
self.frame_id += 1
times = []
for tracker_id in detections.tracker_id:
self.tracker_id2frame_id.setdefault(tracker_id, self.frame_id)
start_frame_id = self.tracker_id2frame_id[tracker_id]
time_duration = (self.frame_id - start_frame_id) / self.fps
times.append(time_duration)
return np.array(times)
class ClockBasedTimer:
"""
A timer that calculates the duration each object has been detected based on the
system clock.
Attributes:
tracker_id2start_time (Dict[int, datetime]): Maps each tracker's ID to the
datetime when it was first detected.
"""
def __init__(self) -> None:
"""Initializes the ClockBasedTimer."""
self.tracker_id2start_time: Dict[int, datetime] = {}
def tick(self, detections: sv.Detections) -> np.ndarray:
"""Processes the current frame, updating time durations for each tracker.
Args:
detections: The detections for the current frame, including tracker IDs.
Returns:
np.ndarray: Time durations (in seconds) for each detected tracker, since
their first detection.
"""
current_time = datetime.now()
times = []
for tracker_id in detections.tracker_id:
self.tracker_id2start_time.setdefault(tracker_id, current_time)
start_time = self.tracker_id2start_time[tracker_id]
time_duration = (current_time - start_time).total_seconds()
times.append(time_duration)
return np.array(times)
+1 -1
View File
@@ -1,4 +1,4 @@
inference
supervision
supervision==0.19.0
tqdm
ultralytics
+16 -9
View File
@@ -1,6 +1,6 @@
import argparse
import os
from typing import Dict, List, Set, Tuple
from typing import Dict, Iterable, List, Set, Tuple
import cv2
import numpy as np
@@ -9,7 +9,8 @@ from tqdm import tqdm
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
ZONE_IN_POLYGONS = [
np.array([[592, 282], [900, 282], [900, 82], [592, 82]]),
@@ -60,13 +61,13 @@ class DetectionsManager:
def initiate_polygon_zones(
polygons: List[np.ndarray],
frame_resolution_wh: Tuple[int, int],
triggering_position: sv.Position = sv.Position.CENTER,
triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER],
) -> List[sv.PolygonZone]:
return [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=frame_resolution_wh,
triggering_position=triggering_position,
triggering_anchors=triggering_anchors,
)
for polygon in polygons
]
@@ -92,13 +93,16 @@ class VideoProcessor:
self.video_info = sv.VideoInfo.from_video_path(source_video_path)
self.zones_in = initiate_polygon_zones(
ZONE_IN_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_IN_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.zones_out = initiate_polygon_zones(
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
self.trace_annotator = sv.TraceAnnotator(
color=COLORS, position=sv.Position.CENTER, trace_length=100, thickness=2
)
@@ -136,7 +140,10 @@ 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.box_annotator.annotate(
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
@@ -167,7 +174,7 @@ class VideoProcessor:
detections_in_zones = []
detections_out_zones = []
for i, (zone_in, zone_out) in enumerate(zip(self.zones_in, self.zones_out)):
for zone_in, zone_out in zip(self.zones_in, self.zones_out):
detections_in_zone = detections[zone_in.trigger(detections=detections)]
detections_in_zones.append(detections_in_zone)
detections_out_zone = detections[zone_out.trigger(detections=detections)]
+1 -1
View File
@@ -1,5 +1,5 @@
gdown
inference
supervision
supervision>=0.19.0
tqdm
ultralytics
@@ -1,5 +1,5 @@
import argparse
from typing import Dict, List, Set, Tuple
from typing import Dict, Iterable, List, Set, Tuple
import cv2
import numpy as np
@@ -8,7 +8,7 @@ from ultralytics import YOLO
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
ZONE_IN_POLYGONS = [
np.array([[592, 282], [900, 282], [900, 82], [592, 82]]),
@@ -59,13 +59,13 @@ class DetectionsManager:
def initiate_polygon_zones(
polygons: List[np.ndarray],
frame_resolution_wh: Tuple[int, int],
triggering_position: sv.Position = sv.Position.CENTER,
triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER],
) -> List[sv.PolygonZone]:
return [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=frame_resolution_wh,
triggering_position=triggering_position,
triggering_anchors=triggering_anchors,
)
for polygon in polygons
]
@@ -90,13 +90,16 @@ class VideoProcessor:
self.video_info = sv.VideoInfo.from_video_path(source_video_path)
self.zones_in = initiate_polygon_zones(
ZONE_IN_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_IN_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.zones_out = initiate_polygon_zones(
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
self.trace_annotator = sv.TraceAnnotator(
color=COLORS, position=sv.Position.CENTER, trace_length=100, thickness=2
)
@@ -134,7 +137,10 @@ 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.box_annotator.annotate(
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
@@ -165,7 +171,7 @@ class VideoProcessor:
detections_in_zones = []
detections_out_zones = []
for i, (zone_in, zone_out) in enumerate(zip(self.zones_in, self.zones_out)):
for zone_in, zone_out in zip(self.zones_in, self.zones_out):
detections_in_zone = detections[zone_in.trigger(detections=detections)]
detections_in_zones.append(detections_in_zone)
detections_out_zone = detections[zone_out.trigger(detections=detections)]
+1
View File
@@ -38,6 +38,7 @@ nav:
- Home: index.md
- How to:
- Detect and Annotate: how_to/detect_and_annotate.md
- Detect Small Objects: how_to/detect_small_objects.md
- Track Objects: how_to/track_objects.md
- Filter Detections: how_to/filter_detections.md
- API:
Generated
+97 -119
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "anyio"
@@ -244,26 +244,27 @@ css = ["tinycss2 (>=1.1.0,<1.3)"]
[[package]]
name = "build"
version = "1.0.3"
version = "1.2.0"
description = "A simple, correct Python build frontend"
optional = false
python-versions = ">= 3.7"
python-versions = ">=3.8"
files = [
{file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"},
{file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"},
{file = "build-1.2.0-py3-none-any.whl", hash = "sha256:6105465d9b233433ce8ffa151c760f674446ba766af0290fc14bfa32aef92a04"},
{file = "build-1.2.0.tar.gz", hash = "sha256:49df7f8e1e74d345fe71e54f5d56423b2111eda89e3da53a2c18392954dade1d"},
]
[package.dependencies]
colorama = {version = "*", markers = "os_name == \"nt\""}
importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
packaging = ">=19.0"
importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""}
packaging = ">=19.1"
pyproject_hooks = "*"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"]
test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"]
typing = ["importlib-metadata (>=5.1)", "mypy (>=1.5.0,<1.6.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"]
typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
uv = ["uv (>=0.1.18)"]
virtualenv = ["virtualenv (>=20.0.35)"]
[[package]]
@@ -570,6 +571,7 @@ files = [
{file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"},
{file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"},
{file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"},
{file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"},
{file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"},
{file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"},
{file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"},
@@ -578,6 +580,7 @@ files = [
{file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"},
{file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"},
{file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"},
{file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"},
{file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"},
{file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"},
{file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"},
@@ -586,6 +589,7 @@ files = [
{file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"},
{file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"},
{file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"},
{file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"},
{file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"},
{file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"},
{file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"},
@@ -594,6 +598,7 @@ files = [
{file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"},
{file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"},
{file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"},
{file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"},
{file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"},
{file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"},
{file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"},
@@ -2179,13 +2184,13 @@ test = ["coverage[toml]", "pymdown-extensions", "pytest", "pytest-cov"]
[[package]]
name = "mkdocs-material"
version = "9.5.11"
version = "9.5.15"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_material-9.5.11-py3-none-any.whl", hash = "sha256:788ee0f3e036dca2dc20298d65e480297d348a44c9d7b2ee05c5262983e66072"},
{file = "mkdocs_material-9.5.11.tar.gz", hash = "sha256:7af7f8af0dea16175558f3fb9245d26c83a17199baa5f157755e63d7437bf971"},
{file = "mkdocs_material-9.5.15-py3-none-any.whl", hash = "sha256:e5c96dec3d19491de49ca643fc1dbb92b278e43cdb816c775bc47db77d9b62fb"},
{file = "mkdocs_material-9.5.15.tar.gz", hash = "sha256:39f03cca45e82bf54eb7456b5a18bd252eabfdd67f237a229471484a0a4d4635"},
]
[package.dependencies]
@@ -2276,38 +2281,38 @@ files = [
[[package]]
name = "mypy"
version = "1.8.0"
version = "1.9.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
{file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
{file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
{file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
{file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
{file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
{file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
{file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
{file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
{file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
{file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
{file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
{file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
{file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
{file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
{file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
{file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
{file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
{file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
{file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
{file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
{file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
{file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
{file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
{file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
{file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
{file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
{file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"},
{file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"},
{file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"},
{file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"},
{file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"},
{file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"},
{file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"},
{file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"},
{file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"},
{file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"},
{file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"},
{file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"},
{file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"},
{file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"},
{file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"},
{file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"},
{file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"},
{file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"},
{file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"},
{file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"},
{file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"},
{file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"},
{file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"},
{file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"},
{file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"},
{file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"},
{file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"},
]
[package.dependencies]
@@ -2356,13 +2361,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=
[[package]]
name = "nbconvert"
version = "7.16.1"
version = "7.16.3"
description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)."
optional = false
python-versions = ">=3.8"
files = [
{file = "nbconvert-7.16.1-py3-none-any.whl", hash = "sha256:3188727dffadfdc9c6a1c7250729063d7bc78b355ad7aa023138afa030d1cd07"},
{file = "nbconvert-7.16.1.tar.gz", hash = "sha256:e79e6a074f49ba3ed29428ed86487bf51509d9aab613bd8522ac08f6d28fd7fd"},
{file = "nbconvert-7.16.3-py3-none-any.whl", hash = "sha256:ddeff14beeeedf3dd0bc506623e41e4507e551736de59df69a91f86700292b3b"},
{file = "nbconvert-7.16.3.tar.gz", hash = "sha256:a6733b78ce3d47c3f85e504998495b07e6ea9cf9bf6ec1c98dda63ec6ad19142"},
]
[package.dependencies]
@@ -2389,7 +2394,7 @@ docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sp
qtpdf = ["nbconvert[qtpng]"]
qtpng = ["pyqtwebengine (>=5.15)"]
serve = ["tornado (>=6.1)"]
test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest"]
test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"]
webpdf = ["playwright"]
[[package]]
@@ -2465,13 +2470,13 @@ setuptools = "*"
[[package]]
name = "notebook"
version = "7.1.1"
version = "7.1.2"
description = "Jupyter Notebook - A web-based notebook environment for interactive computing"
optional = false
python-versions = ">=3.8"
files = [
{file = "notebook-7.1.1-py3-none-any.whl", hash = "sha256:197d8e0595acabf4005851c8716e952a81b405f7aefb648067a761fbde267ce7"},
{file = "notebook-7.1.1.tar.gz", hash = "sha256:818e7420fa21f402e726afb9f02df7f3c10f294c02e383ed19852866c316108b"},
{file = "notebook-7.1.2-py3-none-any.whl", hash = "sha256:fc6c24b9aef18d0cd57157c9c47e95833b9b0bdc599652639acf0bdb61dc7d5f"},
{file = "notebook-7.1.2.tar.gz", hash = "sha256:efc2c80043909e0faa17fce9e9b37c059c03af0ec99a4d4db84cb21d9d2e936a"},
]
[package.dependencies]
@@ -2556,31 +2561,12 @@ files = [
{file = "opencv_python-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:b983197f97cfa6fcb74e1da1802c7497a6f94ed561aba6980f1f33123f904956"},
]
[package.dependencies]
numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""}
[[package]]
name = "opencv-python"
version = "4.9.0.80"
description = "Wrapper package for OpenCV python bindings."
optional = true
python-versions = ">=3.6"
files = [
{file = "opencv-python-4.9.0.80.tar.gz", hash = "sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1"},
{file = "opencv_python-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb"},
{file = "opencv_python-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3"},
{file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a"},
{file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57"},
{file = "opencv_python-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c"},
{file = "opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0"},
]
[package.dependencies]
numpy = [
{version = ">=1.21.0", markers = "python_version <= \"3.9\" and platform_system == \"Darwin\" and platform_machine == \"arm64\" and python_version >= \"3.8\""},
{version = ">=1.19.3", markers = "platform_system == \"Linux\" and platform_machine == \"aarch64\" and python_version >= \"3.8\" and python_version < \"3.10\" or python_version > \"3.9\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_system != \"Darwin\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_machine != \"arm64\" and python_version < \"3.10\""},
{version = ">=1.17.3", markers = "(platform_system != \"Darwin\" and platform_system != \"Linux\") and python_version >= \"3.8\" and python_version < \"3.9\" or platform_system != \"Darwin\" and python_version >= \"3.8\" and python_version < \"3.9\" and platform_machine != \"aarch64\" or platform_machine != \"arm64\" and python_version >= \"3.8\" and python_version < \"3.9\" and platform_system != \"Linux\" or (platform_machine != \"arm64\" and platform_machine != \"aarch64\") and python_version >= \"3.8\" and python_version < \"3.9\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
]
@@ -2601,31 +2587,12 @@ files = [
{file = "opencv_python_headless-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:0a0f1e9f836f7d5bad1dd164694944c8761711cbdf4b36ebbd4815a8ef731079"},
]
[package.dependencies]
numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""}
[[package]]
name = "opencv-python-headless"
version = "4.9.0.80"
description = "Wrapper package for OpenCV python bindings."
optional = false
python-versions = ">=3.6"
files = [
{file = "opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c"},
]
[package.dependencies]
numpy = [
{version = ">=1.21.0", markers = "python_version <= \"3.9\" and platform_system == \"Darwin\" and platform_machine == \"arm64\" and python_version >= \"3.8\""},
{version = ">=1.19.3", markers = "platform_system == \"Linux\" and platform_machine == \"aarch64\" and python_version >= \"3.8\" and python_version < \"3.10\" or python_version > \"3.9\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_system != \"Darwin\" and python_version < \"3.10\" or python_version >= \"3.9\" and platform_machine != \"arm64\" and python_version < \"3.10\""},
{version = ">=1.17.3", markers = "(platform_system != \"Darwin\" and platform_system != \"Linux\") and python_version >= \"3.8\" and python_version < \"3.9\" or platform_system != \"Darwin\" and python_version >= \"3.8\" and python_version < \"3.9\" and platform_machine != \"aarch64\" or platform_machine != \"arm64\" and python_version >= \"3.8\" and python_version < \"3.9\" and platform_system != \"Linux\" or (platform_machine != \"arm64\" and platform_machine != \"aarch64\") and python_version >= \"3.8\" and python_version < \"3.9\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
]
@@ -3056,13 +3023,13 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[[package]]
name = "pytest"
version = "8.0.2"
version = "8.1.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"},
{file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"},
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
]
[package.dependencies]
@@ -3070,11 +3037,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.3.0,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
pluggy = ">=1.4,<2.0"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "python-dateutil"
@@ -3173,6 +3140,7 @@ files = [
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -3180,8 +3148,16 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -3198,6 +3174,7 @@ files = [
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -3205,6 +3182,7 @@ files = [
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -3668,28 +3646,28 @@ files = [
[[package]]
name = "ruff"
version = "0.2.2"
version = "0.3.4"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"},
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"},
{file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"},
{file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"},
{file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"},
{file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"},
{file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"},
{file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"},
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"},
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"},
{file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"},
{file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"},
{file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"},
{file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"},
]
[[package]]
@@ -3965,13 +3943,13 @@ files = [
[[package]]
name = "tox"
version = "4.13.0"
version = "4.14.2"
description = "tox is a generic virtualenv management and test command line tool"
optional = false
python-versions = ">=3.8"
files = [
{file = "tox-4.13.0-py3-none-any.whl", hash = "sha256:1143c7e2489c68026a55d3d4ae84c02c449f073b28e62f80e3e440a3b72a4afa"},
{file = "tox-4.13.0.tar.gz", hash = "sha256:dd789a554c16c4b532924ba393c92fc8991323c4b3d466712bfecc8c9b9f24f7"},
{file = "tox-4.14.2-py3-none-any.whl", hash = "sha256:2900c4eb7b716af4a928a7fdc2ed248ad6575294ed7cfae2ea41203937422847"},
{file = "tox-4.14.2.tar.gz", hash = "sha256:0defb44f6dafd911b61788325741cc6b2e12ea71f987ac025ad4d649f1f1a104"},
]
[package.dependencies]
@@ -4230,13 +4208,13 @@ test = ["websockets"]
[[package]]
name = "wheel"
version = "0.42.0"
version = "0.43.0"
description = "A built-package format for Python"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"},
{file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"},
{file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"},
{file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"},
]
[package.extras]
@@ -4275,4 +4253,4 @@ desktop = ["opencv-python"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "8010232d55d2b5ad5dd5b0ab950ca0e417e705f105bd5331d2e44afb26151010"
content-hash = "56ddae6824a9f28c9954badd4c642c57f687b099c6169f97fa0372e294500c17"
+12 -17
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "supervision"
version = "0.19.0rc4"
version = "0.20.0rc1"
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>"]
@@ -53,8 +53,8 @@ assets = ["requests","tqdm"]
[tool.poetry.group.dev.dependencies]
twine = ">=4.0.2,<6.0.0"
pytest = ">=7.2.2,<9.0.0"
wheel = ">=0.40,<0.43"
build = ">=0.10,<1.1"
wheel = ">=0.40,<0.44"
build = ">=0.10,<1.3"
ruff = ">=0.1.0"
mypy = "^1.4.1"
pre-commit = "^3.3.3"
@@ -111,13 +111,6 @@ exclude = '''
[tool.ruff]
target-version = "py38"
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
lint.select = ["E", "F"]
lint.ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
lint.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"]
lint.unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [
@@ -147,12 +140,19 @@ exclude = [
"docs",
]
# Same as Black.
line-length = 88
indent-width = 4
[tool.ruff.lint]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
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"]
unfixable = []
# Allow unused variables when underscore-prefixed.
lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
pylint.max-args = 20
[tool.ruff.lint.flake8-quotes]
inline-quotes = "double"
@@ -164,16 +164,11 @@ convention = "google"
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402","F401"]
"supervision/assets/list.py" = ["E501"]
[tool.ruff.lint.mccabe]
# Flag errors (`C901`) whenever the complexity level exceeds 5.
max-complexity = 20
[tool.ruff.pylint]
max-args = 20
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
+5 -3
View File
@@ -12,6 +12,7 @@ from supervision.annotators.core import (
BoxCornerAnnotator,
CircleAnnotator,
ColorAnnotator,
CropAnnotator,
DotAnnotator,
EllipseAnnotator,
HaloAnnotator,
@@ -38,6 +39,7 @@ from supervision.detection.core import Detections
from supervision.detection.line_zone import LineZone, LineZoneAnnotator
from supervision.detection.tools.csv_sink import CSVSink
from supervision.detection.tools.inference_slicer import InferenceSlicer
from supervision.detection.tools.json_sink import JSONSink
from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator
from supervision.detection.tools.smoother import DetectionsSmoother
from supervision.detection.utils import (
@@ -56,8 +58,8 @@ from supervision.detection.utils import (
)
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import (
calculate_dynamic_line_thickness,
calculate_dynamic_text_scale,
calculate_optimal_line_thickness,
calculate_optimal_text_scale,
draw_filled_rectangle,
draw_image,
draw_line,
@@ -70,7 +72,7 @@ from supervision.geometry.utils import get_polygon_center
from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision
from supervision.tracker.byte_tracker.core import ByteTrack
from supervision.utils.file import list_files_with_extensions
from supervision.utils.image import ImageSink, crop_image
from supervision.utils.image import ImageSink, crop_image, place_image, resize_image
from supervision.utils.notebook import plot_image, plot_images_grid
from supervision.utils.video import (
FPSMonitor,
+205 -7
View File
@@ -17,6 +17,7 @@ from supervision.detection.utils import clip_boxes, mask_to_polygons
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import draw_polygon
from supervision.geometry.core import Position
from supervision.utils.image import crop_image, place_image, resize_image
class BoundingBoxAnnotator(BaseAnnotator):
@@ -913,6 +914,7 @@ class LabelAnnotator:
text_padding: int = 10,
text_position: Position = Position.TOP_LEFT,
color_lookup: ColorLookup = ColorLookup.CLASS,
border_radius: int = 0,
):
"""
Args:
@@ -926,7 +928,10 @@ class LabelAnnotator:
Possible values are defined in the `Position` enum.
color_lookup (str): Strategy for mapping colors to annotations.
Options are `INDEX`, `CLASS`, `TRACK`.
border_radius (int): The radius to apply round edges. If the selected
value is higher than the lower dimension, width or height, is clipped.
"""
self.border_radius: int = border_radius
self.color: Union[Color, ColorPalette] = color
self.text_color: Color = text_color
self.text_scale: float = text_scale
@@ -1014,15 +1019,22 @@ class LabelAnnotator:
Example:
```python
import supervision as sv
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)
]
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
annotated_frame = label_annotator.annotate(
scene=image.copy(),
detections=detections
detections=detections,
labels=labels
)
```
@@ -1079,12 +1091,11 @@ class LabelAnnotator:
text_x = text_background_xyxy[0] + self.text_padding
text_y = text_background_xyxy[1] + self.text_padding + text_h
cv2.rectangle(
img=scene,
pt1=(text_background_xyxy[0], text_background_xyxy[1]),
pt2=(text_background_xyxy[2], text_background_xyxy[3]),
self.draw_rounded_rectangle(
scene=scene,
xyxy=text_background_xyxy,
color=color.as_bgr(),
thickness=cv2.FILLED,
border_radius=self.border_radius,
)
cv2.putText(
img=scene,
@@ -1098,6 +1109,48 @@ class LabelAnnotator:
)
return scene
@staticmethod
def draw_rounded_rectangle(
scene: np.ndarray,
xyxy: Tuple[int, int, int, int],
color: Tuple[int, int, int],
border_radius: int,
) -> np.ndarray:
x1, y1, x2, y2 = xyxy
width = x2 - x1
height = 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,
thickness=-1,
)
for center in circle_centers:
cv2.circle(
img=scene,
center=center,
radius=border_radius,
color=color,
thickness=-1,
)
return scene
class BlurAnnotator(BaseAnnotator):
"""
@@ -1839,3 +1892,148 @@ class PercentageBarAnnotator(BaseAnnotator):
if not all(0 <= value <= 1 for value in custom_values):
raise ValueError("All values in custom_values must be between 0 and 1.")
class CropAnnotator(BaseAnnotator):
"""
A class for drawing scaled up crops of detections on the scene.
"""
def __init__(
self,
position: Position = Position.TOP_CENTER,
scale_factor: int = 2,
border_color: Union[Color, ColorPalette] = ColorPalette.DEFAULT,
border_thickness: int = 2,
border_color_lookup: ColorLookup = ColorLookup.CLASS,
):
"""
Args:
position (Position): The anchor position for placing the cropped and scaled
part of the detection in the scene.
scale_factor (int): The factor by which to scale the cropped image part. A
factor of 2, for example, would double the size of the cropped area,
allowing for a closer view of the detection.
border_color (Union[Color, ColorPalette]): The color or color palette to
use for annotating border around the cropped area.
border_thickness (int): The thickness of the border around the cropped area.
border_color_lookup (ColorLookup): Strategy for mapping colors to
annotations. Options are `INDEX`, `CLASS`, `TRACK`.
"""
self.position: Position = position
self.scale_factor: int = scale_factor
self.border_color: Union[Color, ColorPalette] = border_color
self.border_thickness: int = border_thickness
self.border_color_lookup: ColorLookup = border_color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> ImageType:
"""
Annotates the provided scene with scaled and cropped parts of the image based
on the provided detections. Each detection is cropped from the original scene
and scaled according to the annotator's scale factor before being placed back
onto the scene at the specified position.
Args:
scene (ImageType): The image where cropped detection will be placed.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): Object detections to annotate.
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
Allows to override the default color mapping strategy.
Returns:
The annotated image.
Example:
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
"""
crops = [
crop_image(image=scene, xyxy=xyxy) for xyxy in detections.xyxy.astype(int)
]
resized_crops = [
resize_image(image=crop, scale_factor=self.scale_factor) for crop in crops
]
anchors = detections.get_anchors_coordinates(anchor=self.position).astype(int)
for idx, (resized_crop, anchor) in enumerate(zip(resized_crops, anchors)):
crop_wh = resized_crop.shape[1], resized_crop.shape[0]
(x1, y1), (x2, y2) = self.calculate_crop_coordinates(
anchor=anchor, crop_wh=crop_wh, position=self.position
)
scene = place_image(scene=scene, image=resized_crop, anchor=(x1, y1))
color = resolve_color(
color=self.border_color,
detections=detections,
detection_idx=idx,
color_lookup=self.border_color_lookup
if custom_color_lookup is None
else custom_color_lookup,
)
cv2.rectangle(
img=scene,
pt1=(x1, y1),
pt2=(x2, y2),
color=color.as_bgr(),
thickness=self.border_thickness,
)
return scene
@staticmethod
def calculate_crop_coordinates(
anchor: Tuple[int, int], crop_wh: Tuple[int, int], position: Position
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
anchor_x, anchor_y = anchor
width, height = crop_wh
if position == Position.TOP_LEFT:
return (anchor_x - width, anchor_y - height), (anchor_x, anchor_y)
elif position == Position.TOP_CENTER:
return (
(anchor_x - width // 2, anchor_y - height),
(anchor_x + width // 2, anchor_y),
)
elif position == Position.TOP_RIGHT:
return (anchor_x, anchor_y - height), (anchor_x + width, anchor_y)
elif position == Position.CENTER_LEFT:
return (
(anchor_x - width, anchor_y - height // 2),
(anchor_x, anchor_y + height // 2),
)
elif position == Position.CENTER or position == Position.CENTER_OF_MASS:
return (
(anchor_x - width // 2, anchor_y - height // 2),
(anchor_x + width // 2, anchor_y + height // 2),
)
elif position == Position.CENTER_RIGHT:
return (
(anchor_x, anchor_y - height // 2),
(anchor_x + width, anchor_y + height // 2),
)
elif position == Position.BOTTOM_LEFT:
return (anchor_x - width, anchor_y), (anchor_x, anchor_y + height)
elif position == Position.BOTTOM_CENTER:
return (
(anchor_x - width // 2, anchor_y),
(anchor_x + width // 2, anchor_y + height),
)
elif position == Position.BOTTOM_RIGHT:
return (anchor_x, anchor_y), (anchor_x + width, anchor_y + height)
+7 -2
View File
@@ -125,6 +125,12 @@ class Trace:
return self.xy[self.tracker_id == tracker_id]
def pillow_to_cv2(image: Image.Image) -> np.ndarray:
scene = np.array(image)
scene = cv2.cvtColor(scene, cv2.COLOR_RGB2BGR)
return scene
def scene_to_annotator_img_type(annotate_func):
"""
Decorates `BaseAnnotator.annotate` implementations, converts scene to
@@ -138,8 +144,7 @@ def scene_to_annotator_img_type(annotate_func):
return annotate_func(self, scene, *args, **kwargs)
if isinstance(scene, Image.Image):
scene = np.array(scene)
scene = cv2.cvtColor(scene, cv2.COLOR_RGB2BGR)
scene = pillow_to_cv2(scene)
annotated = annotate_func(self, scene, *args, **kwargs)
annotated = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
annotated = Image.fromarray(annotated)
+19 -6
View File
@@ -14,6 +14,7 @@ from supervision.detection.utils import (
get_data_item,
is_data_equal,
mask_non_max_suppression,
mask_to_xyxy,
merge_data,
process_roboflow_result,
validate_detections_fields,
@@ -390,18 +391,30 @@ class Detections:
@classmethod
def from_transformers(cls, transformers_results: dict) -> Detections:
"""
Creates a Detections instance from object detection
Creates a Detections instance from object detection or segmentation
[transformer](https://github.com/huggingface/transformers) inference result.
Returns:
Detections: A new Detections object.
"""
boxes = transformers_results.get("boxes")
return cls(
xyxy=transformers_results["boxes"].cpu().numpy(),
confidence=transformers_results["scores"].cpu().numpy(),
class_id=transformers_results["labels"].cpu().numpy().astype(int),
)
# If the boxes key is in the transformers_results then we know it's an
# object detection result. Else, we can assume it's a segmentation model
if boxes:
return cls(
xyxy=transformers_results["boxes"].cpu().numpy(),
confidence=transformers_results["scores"].cpu().numpy(),
class_id=transformers_results["labels"].cpu().numpy().astype(int),
)
else:
masks = transformers_results["masks"].cpu().numpy().astype(bool)
return cls(
xyxy=mask_to_xyxy(masks),
mask=masks,
confidence=transformers_results["scores"].cpu().numpy(),
class_id=transformers_results["labels"].cpu().numpy().astype(int),
)
@classmethod
def from_detectron2(cls, detectron2_results) -> Detections:
+1 -1
View File
@@ -42,7 +42,7 @@ class CSVSink:
csv_sink = sv.CSVSink(<RESULT_CSV_FILE_PATH>)
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with csv_sink:
with csv_sink as sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
+142
View File
@@ -0,0 +1,142 @@
from __future__ import annotations
import json
import os
from typing import Any, Dict, List, Optional
from supervision.detection.core import Detections
class JSONSink:
"""
A utility class for saving detection data to a JSON file. This class is designed to
efficiently serialize detection objects into a JSON format, allowing for the
inclusion of bounding box coordinates and additional attributes like `confidence`,
`class_id`, and `tracker_id`.
!!! tip
JSONsink allow to pass custom data alongside the detection fields, providing
flexibility for logging various types of information.
Args:
file_name (str): The name of the JSON file where the detections will be stored.
Defaults to 'output.json'.
Example:
```python
import supervision as sv
from ultralytics import YOLO
model = YOLO(<SOURCE_MODEL_PATH>)
json_sink = sv.JSONSink(<RESULT_JSON_FILE_PATH>)
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with json_sink as sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
sink.append(detections, custom_data={'<CUSTOM_LABEL>':'<CUSTOM_DATA>'})
```
""" # noqa: E501 // docs
def __init__(self, file_name: str = "output.json") -> None:
"""
Initialize the JSONSink instance.
Args:
file_name (str): The name of the JSON file.
Returns:
None
"""
self.file_name = file_name
self.file: Optional[open] = None
self.data: List[Dict[str, Any]] = []
def __enter__(self) -> JSONSink:
self.open()
return self
def __exit__(
self,
exc_type: Optional[type],
exc_val: Optional[Exception],
exc_tb: Optional[Any],
) -> None:
self.write_and_close()
def open(self) -> None:
"""
Open the JSON file for writing.
Returns:
None
"""
parent_directory = os.path.dirname(self.file_name)
if parent_directory and not os.path.exists(parent_directory):
os.makedirs(parent_directory)
self.file = open(self.file_name, "w")
def write_and_close(self) -> None:
"""
Write and close the JSON file.
Returns:
None
"""
if self.file:
json.dump(self.data, self.file, indent=4)
self.file.close()
@staticmethod
def parse_detection_data(
detections: Detections, custom_data: Dict[str, Any] = None
) -> List[Dict[str, Any]]:
parsed_rows = []
for i in range(len(detections.xyxy)):
row = {
"x_min": float(detections.xyxy[i][0]),
"y_min": float(detections.xyxy[i][1]),
"x_max": float(detections.xyxy[i][2]),
"y_max": float(detections.xyxy[i][3]),
"class_id": ""
if detections.class_id is None
else int(detections.class_id[i]),
"confidence": ""
if detections.confidence is None
else float(detections.confidence[i]),
"tracker_id": ""
if detections.tracker_id is None
else int(detections.tracker_id[i]),
}
if hasattr(detections, "data"):
for key, value in detections.data.items():
row[key] = (
str(value[i])
if hasattr(value, "__getitem__") and value.ndim != 0
else str(value)
)
if custom_data:
row.update(custom_data)
parsed_rows.append(row)
return parsed_rows
def append(
self, detections: Detections, custom_data: Dict[str, Any] = None
) -> None:
"""
Append detection data to the JSON file.
Args:
detections (Detections): The detection data.
custom_data (Dict[str, Any]): Custom data to include.
Returns:
None
"""
parsed_rows = JSONSink.parse_detection_data(detections, custom_data)
self.data.extend(parsed_rows)
+5 -4
View File
@@ -3,6 +3,7 @@ from typing import Iterable, Optional, Tuple
import cv2
import numpy as np
import numpy.typing as npt
from supervision import Detections
from supervision.detection.utils import clip_boxes, polygon_to_mask
@@ -39,7 +40,7 @@ class PolygonZone:
)
def __init__(
self,
polygon: np.ndarray,
polygon: npt.NDArray[np.int64],
frame_resolution_wh: Tuple[int, int],
triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
):
@@ -54,7 +55,7 @@ class PolygonZone:
polygon=polygon, resolution_wh=(width + 1, height + 1)
)
def trigger(self, detections: Detections) -> np.ndarray:
def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]:
"""
Determines if the detections are within the polygon zone.
@@ -78,13 +79,13 @@ class PolygonZone:
]
)
is_in_zone = (
is_in_zone: npt.NDArray[np.bool_] = (
self.mask[all_clipped_anchors[:, :, 1], all_clipped_anchors[:, :, 0]]
.transpose()
.astype(bool)
)
is_in_zone = np.all(is_in_zone, axis=1)
is_in_zone: npt.NDArray[np.bool_] = np.all(is_in_zone, axis=1)
self.current_count = int(np.sum(is_in_zone))
return is_in_zone.astype(bool)
+8 -1
View File
@@ -845,8 +845,15 @@ def get_data_item(
elif isinstance(value, list):
if isinstance(index, slice):
subset_data[key] = value[index]
elif isinstance(index, (list, np.ndarray)):
elif isinstance(index, list):
subset_data[key] = [value[i] for i in index]
elif isinstance(index, np.ndarray):
if index.dtype == bool:
subset_data[key] = [
value[i] for i, index_value in enumerate(index) if index_value
]
else:
subset_data[key] = [value[i] for i in index]
elif isinstance(index, int):
subset_data[key] = [value[index]]
else:
+7 -7
View File
@@ -176,31 +176,31 @@ class Color:
return self.b, self.g, self.r
@classproperty
def WHITE(cls):
def WHITE(cls) -> Color:
return Color.from_hex("#FFFFFF")
@classproperty
def BLACK(cls):
def BLACK(cls) -> Color:
return Color.from_hex("#000000")
@classproperty
def RED(cls):
def RED(cls) -> Color:
return Color.from_hex("#FF0000")
@classproperty
def GREEN(cls):
def GREEN(cls) -> Color:
return Color.from_hex("#00FF00")
@classproperty
def BLUE(cls):
def BLUE(cls) -> Color:
return Color.from_hex("#0000FF")
@classproperty
def YELLOW(cls):
def YELLOW(cls) -> Color:
return Color.from_hex("#FFFF00")
@classproperty
def ROBOFLOW(cls):
def ROBOFLOW(cls) -> Color:
return Color.from_hex("#A351FB")
@classmethod
+8 -16
View File
@@ -238,13 +238,13 @@ def draw_image(
return scene
def calculate_dynamic_text_scale(resolution_wh: Tuple[int, int]) -> float:
def calculate_optimal_text_scale(resolution_wh: Tuple[int, int]) -> float:
"""
Calculate a dynamic font scale based on the resolution of an image.
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.
of the image.
Returns:
float: The calculated font scale factor.
@@ -252,25 +252,17 @@ def calculate_dynamic_text_scale(resolution_wh: Tuple[int, int]) -> float:
return min(resolution_wh) * 1e-3
def calculate_dynamic_line_thickness(resolution_wh: Tuple[int, int]) -> int:
def calculate_optimal_line_thickness(resolution_wh: Tuple[int, int]) -> int:
"""
Calculate a dynamic line thickness based on the resolution of an image.
Calculate line thickness based on the resolution of an image.
Parameters:
resolution_wh (Tuple[int, int]): A tuple representing the width and height
of the image.
of the image.
Returns:
int: The calculated line thickness in pixels.
"""
min_dimension = min(resolution_wh)
if min_dimension < 480:
if min(resolution_wh) < 1080:
return 2
if min_dimension < 720:
return 2
if min_dimension < 1080:
return 2
if min_dimension < 2160:
return 4
else:
return 4
return 4
+4 -4
View File
@@ -836,10 +836,10 @@ class MeanAveragePrecision:
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]
average_precisions[class_idx, iou_level_idx] = (
MeanAveragePrecision.compute_average_precision(
recall[:, iou_level_idx], precision[:, iou_level_idx]
)
)
return average_precisions
+19 -17
View File
@@ -3,6 +3,7 @@ from typing import List, Tuple
import numpy as np
from supervision.detection.core import Detections
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
@@ -270,27 +271,28 @@ class ByteTrack:
```
"""
tracks = self.update_with_tensors(
tensors=detections2boxes(detections=detections)
)
detections = Detections.empty()
tensors = detections2boxes(detections=detections)
tracks = self.update_with_tensors(tensors=tensors)
if len(tracks) > 0:
detections.xyxy = np.array(
[track.tlbr for track in tracks], dtype=np.float32
)
detections.class_id = np.array(
[int(t.class_ids) for t in tracks], dtype=int
)
detections.tracker_id = np.array(
[int(t.track_id) for t in tracks], dtype=int
)
detections.confidence = np.array(
[t.score for t in tracks], dtype=np.float32
)
detection_bounding_boxes = np.asarray([det[:4] for det in tensors])
track_bounding_boxes = np.asarray([track.tlbr for track in tracks])
ious = box_iou_batch(detection_bounding_boxes, track_bounding_boxes)
iou_costs = 1 - ious
matches, _, _ = matching.linear_assignment(iou_costs, 0.5)
detections.tracker_id = np.full(len(detections), -1, dtype=int)
for i_detection, i_track in matches:
detections.tracker_id[i_detection] = int(tracks[i_track].track_id)
return detections[detections.tracker_id != -1]
else:
detections.tracker_id = np.array([], dtype=int)
return detections
return detections
def reset(self):
"""
+73 -4
View File
@@ -1,6 +1,6 @@
import os
import shutil
from typing import Optional
from typing import Optional, Tuple
import cv2
import numpy as np
@@ -32,8 +32,76 @@ def crop_image(image: np.ndarray, xyxy: np.ndarray) -> np.ndarray:
xyxy = np.round(xyxy).astype(int)
x1, y1, x2, y2 = xyxy
cropped_img = image[y1:y2, x1:x2]
return cropped_img
return image[y1:y2, x1:x2]
def resize_image(image: np.ndarray, scale_factor: float) -> np.ndarray:
"""
Resizes an image by a given scale factor using cv2.INTER_LINEAR interpolation.
Args:
image (np.ndarray): The input image to be resized.
scale_factor (float): The factor by which the image will be scaled. Scale factor
> 1.0 zooms in, < 1.0 zooms out.
Returns:
np.ndarray: The resized image.
Raises:
ValueError: If the scale factor is non-positive.
"""
if scale_factor <= 0:
raise ValueError("Scale factor must be positive.")
old_width, old_height = image.shape[1], image.shape[0]
nwe_width = int(old_width * scale_factor)
new_height = int(old_height * scale_factor)
return cv2.resize(image, (nwe_width, new_height), interpolation=cv2.INTER_LINEAR)
def place_image(
scene: np.ndarray, image: np.ndarray, anchor: Tuple[int, int]
) -> np.ndarray:
"""
Places an image onto a scene at a given anchor point, handling cases where
the image's position is partially or completely outside the scene's bounds.
Args:
scene (np.ndarray): The background scene onto which the image is placed.
image (np.ndarray): The image to be placed onto the scene.
anchor (Tuple[int, int]): The (x, y) coordinates in the scene where the
top-left corner of the image will be placed.
Returns:
np.ndarray: The modified scene with the image placed at the anchor point,
or unchanged if the image placement is completely outside the scene.
"""
scene_height, scene_width = scene.shape[:2]
image_height, image_width = image.shape[:2]
anchor_x, anchor_y = anchor
is_out_horizontally = anchor_x + image_width <= 0 or anchor_x >= scene_width
is_out_vertically = anchor_y + image_height <= 0 or anchor_y >= scene_height
if is_out_horizontally or is_out_vertically:
return scene
start_y = max(anchor_y, 0)
start_x = max(anchor_x, 0)
end_y = min(scene_height, anchor_y + image_height)
end_x = min(scene_width, anchor_x + image_width)
crop_start_y = max(-anchor_y, 0)
crop_start_x = max(-anchor_x, 0)
crop_end_y = image_height - max((anchor_y + image_height) - scene_height, 0)
crop_end_x = image_width - max((anchor_x + image_width) - scene_width, 0)
scene[start_y:end_y, start_x:end_x] = image[
crop_start_y:crop_end_y, crop_start_x:crop_end_x
]
return scene
class ImageSink:
@@ -85,7 +153,8 @@ class ImageSink:
Save a given image in the target directory.
Args:
image (np.ndarray): The image to be saved.
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.
If not provided, a name will be
generated using the `image_name_pattern`.
+19 -6
View File
@@ -2,17 +2,21 @@ from typing import List, Optional, Tuple
import cv2
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from supervision.annotators.base import ImageType
from supervision.annotators.utils import pillow_to_cv2
def plot_image(
image: np.ndarray, size: Tuple[int, int] = (12, 12), cmap: Optional[str] = "gray"
image: ImageType, size: Tuple[int, int] = (12, 12), cmap: Optional[str] = "gray"
) -> None:
"""
Plots image using matplotlib.
Args:
image (np.ndarray): The frame to be displayed.
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.
cmap (str): the colormap to use for single channel images.
@@ -27,6 +31,9 @@ def plot_image(
sv.plot_image(image=image, size=(16, 16))
```
"""
if isinstance(image, Image.Image):
image = pillow_to_cv2(image)
plt.figure(figsize=size)
if image.ndim == 2:
@@ -39,7 +46,7 @@ def plot_image(
def plot_images_grid(
images: List[np.ndarray],
images: List[ImageType],
grid_size: Tuple[int, int],
titles: Optional[List[str]] = None,
size: Tuple[int, int] = (12, 12),
@@ -49,7 +56,8 @@ def plot_images_grid(
Plots images in a grid using matplotlib.
Args:
images (List[np.ndarray]): A list of images as numpy arrays.
images (List[ImageType]): A list of images as ImageType
is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`.
grid_size (Tuple[int, int]): A tuple specifying the number
of rows and columns for the grid.
titles (Optional[List[str]]): A list of titles for each image.
@@ -65,9 +73,10 @@ def plot_images_grid(
```python
import cv2
import supervision as sv
from PIL import Image
image1 = cv2.imread("path/to/image1.jpg")
image2 = cv2.imread("path/to/image2.jpg")
image2 = Image.open("path/to/image2.jpg")
image3 = cv2.imread("path/to/image3.jpg")
images = [image1, image2, image3]
@@ -79,6 +88,10 @@ def plot_images_grid(
"""
nrows, ncols = grid_size
for idx, img in enumerate(images):
if isinstance(img, Image.Image):
images[idx] = pillow_to_cv2(img)
if len(images) > nrows * ncols:
raise ValueError(
"The number of images exceeds the grid size. Please increase the grid size"
+7
View File
@@ -105,6 +105,13 @@ class VideoSink:
return self
def write_frame(self, frame: np.ndarray):
"""
Writes a single video frame to the target video file.
Args:
frame (np.ndarray): The video frame to be written to the file. The frame
must be in BGR color format.
"""
self.__writer.write(frame)
def __exit__(self, exc_type, exc_value, exc_traceback):
+249
View File
@@ -0,0 +1,249 @@
import json
import os
from test.test_utils import mock_detections
from typing import Any, Dict, List
import pytest
import supervision as sv
@pytest.mark.parametrize(
"detections, custom_data, "
"second_detections, second_custom_data, "
"file_name, expected_result",
[
(
mock_detections(
xyxy=[[10, 20, 30, 40], [50, 60, 70, 80]],
confidence=[0.7, 0.8],
class_id=[0, 0],
tracker_id=[0, 1],
data={"class_name": ["person", "person"]},
),
{"frame_number": 42},
mock_detections(
xyxy=[[15, 25, 35, 45], [55, 65, 75, 85]],
confidence=[0.6, 0.9],
class_id=[1, 1],
tracker_id=[2, 3],
data={"class_name": ["car", "car"]},
),
{"frame_number": 43},
"test_detections.json",
[
{
"x_min": 10,
"y_min": 20,
"x_max": 30,
"y_max": 40,
"class_id": 0,
"confidence": 0.699999988079071,
"tracker_id": 0,
"class_name": "person",
"frame_number": 42,
},
{
"x_min": 50,
"y_min": 60,
"x_max": 70,
"y_max": 80,
"class_id": 0,
"confidence": 0.800000011920929,
"tracker_id": 1,
"class_name": "person",
"frame_number": 42,
},
{
"x_min": 15,
"y_min": 25,
"x_max": 35,
"y_max": 45,
"class_id": 1,
"confidence": 0.6000000238418579,
"tracker_id": 2,
"class_name": "car",
"frame_number": 43,
},
{
"x_min": 55,
"y_min": 65,
"x_max": 75,
"y_max": 85,
"class_id": 1,
"confidence": 0.8999999761581421,
"tracker_id": 3,
"class_name": "car",
"frame_number": 43,
},
],
), # Multiple detections
(
mock_detections(
xyxy=[[60, 70, 80, 90], [100, 110, 120, 130]],
tracker_id=[4, 5],
data={"class_name": ["bike", "dog"]},
),
{"frame_number": 44},
mock_detections(
xyxy=[[65, 75, 85, 95], [105, 115, 125, 135]],
confidence=[0.5, 0.4],
data={"class_name": ["tree", "cat"]},
),
{"frame_number": 45},
"test_detections_missing_fields.json",
[
{
"x_min": 60,
"y_min": 70,
"x_max": 80,
"y_max": 90,
"class_id": "",
"confidence": "",
"tracker_id": 4,
"class_name": "bike",
"frame_number": 44,
},
{
"x_min": 100,
"y_min": 110,
"x_max": 120,
"y_max": 130,
"class_id": "",
"confidence": "",
"tracker_id": 5,
"class_name": "dog",
"frame_number": 44,
},
{
"x_min": 65,
"y_min": 75,
"x_max": 85,
"y_max": 95,
"class_id": "",
"confidence": 0.5,
"tracker_id": "",
"class_name": "tree",
"frame_number": 45,
},
{
"x_min": 105,
"y_min": 115,
"x_max": 125,
"y_max": 135,
"class_id": "",
"confidence": 0.4000000059604645,
"tracker_id": "",
"class_name": "cat",
"frame_number": 45,
},
],
), # Missing fields
(
mock_detections(
xyxy=[[10, 11, 12, 13]],
confidence=[0.95],
data={"class_name": "unknown", "is_detected": True, "score": 1},
),
{"frame_number": 46},
mock_detections(
xyxy=[[14, 15, 16, 17]],
data={"class_name": "artifact", "is_detected": False, "score": 0.85},
),
{"frame_number": 47},
"test_detections_varied_data.json",
[
{
"x_min": 10,
"y_min": 11,
"x_max": 12,
"y_max": 13,
"class_id": "",
"confidence": 0.949999988079071,
"tracker_id": "",
"class_name": "unknown",
"is_detected": "True",
"score": "1",
"frame_number": 46,
},
{
"x_min": 14,
"y_min": 15,
"x_max": 16,
"y_max": 17,
"class_id": "",
"confidence": "",
"tracker_id": "",
"class_name": "artifact",
"is_detected": "False",
"score": "0.85",
"frame_number": 47,
},
],
), # Inconsistent Data Types
(
mock_detections(
xyxy=[[20, 21, 22, 23]],
),
{
"metadata": {"sensor_id": 101, "location": "north"},
"tags": ["urgent", "review"],
},
mock_detections(
xyxy=[[14, 15, 16, 17]],
),
{
"metadata": {"sensor_id": 104, "location": "west"},
"tags": ["not-urgent", "done"],
},
"test_detections_complex_data.json",
[
{
"x_min": 20,
"y_min": 21,
"x_max": 22,
"y_max": 23,
"class_id": "",
"confidence": "",
"tracker_id": "",
"metadata": {"sensor_id": 101, "location": "north"},
"tags": ["urgent", "review"],
},
{
"x_min": 14,
"y_min": 15,
"x_max": 16,
"y_max": 17,
"class_id": "",
"confidence": "",
"tracker_id": "",
"metadata": {"sensor_id": 104, "location": "west"},
"tags": ["not-urgent", "done"],
},
],
), # Complex Data
],
)
def test_json_sink(
detections: mock_detections,
custom_data: Dict[str, Any],
second_detections: mock_detections,
second_custom_data: Dict[str, Any],
file_name: str,
expected_result: List[List[Any]],
) -> None:
with sv.JSONSink(file_name) as sink:
sink.append(detections, custom_data)
sink.append(second_detections, second_custom_data)
assert_json_equal(file_name, expected_result)
def assert_json_equal(file_name, expected_rows):
with open(file_name, "r") as file:
data = json.load(file)
assert (
data == expected_rows
), f"Data in JSON file didn't match expected output: {data} != {expected_rows}"
os.remove(file_name)
+3 -1
View File
@@ -63,7 +63,9 @@ FRAME_RESOLUTION = (300, 300)
(
DETECTIONS,
sv.PolygonZone(
POLYGON, FRAME_RESOLUTION, triggering_position=sv.Position.BOTTOM_CENTER
POLYGON,
FRAME_RESOLUTION,
triggering_anchors=[sv.Position.BOTTOM_CENTER],
),
np.array(
[False, False, True, True, True, True, False, False, False], dtype=bool
+58 -10
View File
@@ -1045,7 +1045,7 @@ def test_merge_data(
"test_1": [1],
},
DoesNotRaise(),
), # single data dict with a single field name and list values
), # data dict with a single list field and integer index
(
{
"test_1": np.array([1, 2, 3]),
@@ -1055,7 +1055,7 @@ def test_merge_data(
"test_1": np.array([1]),
},
DoesNotRaise(),
), # single data dict with a single field name and np.array values as 1D arrays
), # data dict with a single np.array field and integer index
(
{
"test_1": [1, 2, 3],
@@ -1065,7 +1065,7 @@ def test_merge_data(
"test_1": [1, 2],
},
DoesNotRaise(),
), # single data dict with a single field name and list values
), # data dict with a single list field and slice index
(
{
"test_1": np.array([1, 2, 3]),
@@ -1075,7 +1075,7 @@ def test_merge_data(
"test_1": np.array([1, 2]),
},
DoesNotRaise(),
), # single data dict with a single field name and np.array values as 1D arrays
), # data dict with a single np.array field and slice index
(
{
"test_1": [1, 2, 3],
@@ -1085,7 +1085,7 @@ def test_merge_data(
"test_1": [3],
},
DoesNotRaise(),
), # single data dict with a single field name and list values
), # data dict with a single list field and negative integer index
(
{
"test_1": np.array([1, 2, 3]),
@@ -1095,7 +1095,7 @@ def test_merge_data(
"test_1": np.array([3]),
},
DoesNotRaise(),
), # single data dict with a single field name and np.array values as 1D arrays
), # data dict with a single np.array field and negative integer index
(
{
"test_1": [1, 2, 3],
@@ -1105,7 +1105,7 @@ def test_merge_data(
"test_1": [1, 3],
},
DoesNotRaise(),
), # single data dict with a single field name and list values
), # data dict with a single list field and integer list index
(
{
"test_1": np.array([1, 2, 3]),
@@ -1115,7 +1115,7 @@ def test_merge_data(
"test_1": np.array([1, 3]),
},
DoesNotRaise(),
), # single data dict with a single field name and np.array values as 1D arrays
), # data dict with a single np.array field and integer list index
(
{
"test_1": [1, 2, 3],
@@ -1125,7 +1125,7 @@ def test_merge_data(
"test_1": [1, 3],
},
DoesNotRaise(),
), # single data dict with a single field name and list values
), # data dict with a single list field and integer np.array index
(
{
"test_1": np.array([1, 2, 3]),
@@ -1135,7 +1135,55 @@ def test_merge_data(
"test_1": np.array([1, 3]),
},
DoesNotRaise(),
),
), # data dict with a single np.array field and integer np.array index
(
{
"test_1": np.array([1, 2, 3]),
},
np.array([True, True, True]),
{
"test_1": np.array([1, 2, 3]),
},
DoesNotRaise(),
), # data dict with a single np.array field and all-true bool np.array index
(
{
"test_1": np.array([1, 2, 3]),
},
np.array([False, False, False]),
{
"test_1": np.array([]),
},
DoesNotRaise(),
), # data dict with a single np.array field and all-false bool np.array index
(
{
"test_1": np.array([1, 2, 3]),
},
np.array([False, True, False]),
{
"test_1": np.array([2]),
},
DoesNotRaise(),
), # data dict with a single np.array field and mixed bool np.array index
(
{"test_1": np.array([1, 2, 3]), "test_2": ["a", "b", "c"]},
0,
{"test_1": np.array([1]), "test_2": ["a"]},
DoesNotRaise(),
), # data dict with two fields and integer index
(
{"test_1": np.array([1, 2, 3]), "test_2": ["a", "b", "c"]},
-1,
{"test_1": np.array([3]), "test_2": ["c"]},
DoesNotRaise(),
), # data dict with two fields and negative integer index
(
{"test_1": np.array([1, 2, 3]), "test_2": ["a", "b", "c"]},
np.array([False, True, False]),
{"test_1": np.array([2]), "test_2": ["b"]},
DoesNotRaise(),
), # data dict with two fields and mixed bool np.array index
],
)
def test_get_data_item(