Compare commits

...
647 Commits
Author SHA1 Message Date
Piotr Skalski e50c761b83 Merge pull request #1258 from roboflow/develop
`supervision-0.21.0` release
2024-06-06 08:11:08 +02:00
SkalskiP 0200fd307b mark InferenceSlicer with new status 2024-06-05 18:00:54 +02:00
Piotr Skalski 6a199c5d7e Merge pull request #1260 from roboflow/final-changes-before-supervision-0.21.0-release
final changes before `supervision-0.21.0`
2024-06-05 17:55:51 +02:00
Piotr Skalski d19c44c991 Merge pull request #1261 from roboflow/docs/new-status-tags-for-0.21.0
Update "New" tags in docs, fixed typo in one heading
2024-06-05 17:55:32 +02:00
SkalskiP 2d5de66a9b code snippets updates 2024-06-05 17:53:38 +02:00
LinasKo 13b46dda67 Update "New" tags in docs, fix typo in one heading 2024-06-05 17:44:25 +02:00
SkalskiP a3f299ceb0 update docs headers 2024-06-05 17:40:39 +02:00
SkalskiP 9a42372ff6 changelog updated 2024-06-05 17:37:01 +02:00
SkalskiP 47b9ab8f96 Merge remote-tracking branch 'origin/final-changes-before-supervision-0.21.0-release' into final-changes-before-supervision-0.21.0-release 2024-06-05 16:52:27 +02:00
SkalskiP b476acc1f3 bump version from 0.21.0rc5 to 0.21.0 2024-06-05 16:52:18 +02:00
pre-commit-ci[bot] 3cee266f71 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-06-05 14:33:09 +00:00
Piotr Skalski 111d4c6c91 Merge pull request #1206 from tc360950/line-zone-unit-tests
Add LineZone unit tests
2024-06-05 16:30:20 +02:00
LinasKo a77a2ba8aa LineZone: uncomment unit tests, set expected results 2024-06-05 16:27:53 +02:00
Piotr Skalski fb9024bc75 Merge pull request #1255 from roboflow/fix/detections-merge-revert-and-filter-out-empty
Revert flexible merging, Filter out empty detections
2024-06-05 16:01:38 +02:00
LinasKo ceecb8bbd7 Cleanup: Remove print statements from tests 2024-06-05 15:42:00 +02:00
SkalskiP c28c93bd4f non-max-merging visualization + from_lmm updates 2024-06-05 15:40:28 +02:00
LinasKo ca482967c4 Add note that merge ignores empty detections 2024-06-05 12:28:09 +02:00
LinasKo 57fa637eea Move is_empty into Detections 2024-06-05 12:18:57 +02:00
Linas Kondrackis 59c5ab5252 Merge pull request #1228 from tc360950/vectorize-line-zone
Vectorize line zone
2024-06-05 10:26:23 +02:00
LinasKo c27a28235e LineZone: Remove comment 2024-06-05 10:24:25 +02:00
Onuralp SEZER cfbb6681cc Merge pull request #1257 from roboflow/dependabot/pip/pytest-8.2.2
⬆️ Bump pytest from 8.2.1 to 8.2.2
2024-06-05 10:44:46 +03:00
dependabot[bot] 24b1db9e25 ⬆️ Bump pytest from 8.2.1 to 8.2.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.1 to 8.2.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.2.1...8.2.2)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 01:14:18 +00:00
dependabot[bot] 612ac6145a ⬆️ Bump mkdocs-git-revision-date-localized-plugin
Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases)
- [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.2.5...v1.2.6)

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-28 00:31:55 +00:00
Piotr Skalski a0d0d45890 Merge pull request #500 from mario-dg/add_nmm_to_detections
Add Non-Maximum Merging (NMM) to Detections
2024-05-27 21:34:49 +02:00
SkalskiP 4a365fb907 add LMM to __init__.py 2024-05-27 21:31:25 +02:00
Onuralp SEZER a60cfe08e2 Merge pull request #1233 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-05-27 20:50:48 +03:00
pre-commit-ci[bot] c1ebb81d47 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.4.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.4.5)
2024-05-27 17:42:51 +00:00
Linas Kondrackis 2ee9e08446 Renamed to group_overlapping_boxes 2024-05-27 16:21:40 +03:00
Linas Kondrackis 530e1d01e1 Address comments 2024-05-27 16:17:27 +03:00
tc360950 404be16126 Code reformatting 2024-05-24 18:14:18 +02:00
tc360950 72d677ad89 Merge branch 'develop' of https://github.com/tc360950/supervision into line-zone-unit-tests 2024-05-24 18:11:17 +02:00
tc360950 d6ae86e97b Merge remote-tracking branch 'origin/vectorize-line-zone' into vectorize-line-zone 2024-05-24 18:04:54 +02:00
tc360950 ed5c26527a Code reformatting 2024-05-24 18:04:43 +02:00
pre-commit-ci[bot] d23cc008f8 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-24 16:02:31 +00:00
tc360950 9d54d91791 Pull changes from develop 2024-05-24 18:02:13 +02:00
tc360950 80feb2b12f Add comments 2024-05-24 17:43:02 +02:00
tc360950 fb30bab0fd Vectorize line zone 2024-05-24 16:50:42 +02:00
tc360950 a09b6fac6f Merge remote-tracking branch 'origin/line-zone-unit-tests' into line-zone-unit-tests
# Conflicts:
#	test/detection/test_line_counter.py
2024-05-24 16:43:03 +02:00
tc360950 4e2eb0afc2 Code reformatting 2024-05-24 16:42:40 +02:00
pre-commit-ci[bot] 1a021d5792 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-24 14:40:32 +00:00
tc360950 909ae2a8ff Merge remote-tracking branch 'origin/line-zone-unit-tests' into line-zone-unit-tests 2024-05-24 16:39:43 +02:00
tc360950 7d0488efc0 Add unit tests for negative coordinates and empty anchors 2024-05-24 16:39:30 +02:00
Piotr Skalski 2723735b01 Merge pull request #1221 from roboflow/from_paligemma_support
initial commit adding support for `from_lmm` and specifically for Pal…
2024-05-24 15:09:40 +02:00
Onuralp SEZER 7757d86abc Merge pull request #1225 from roboflow/dependabot/pip/ruff-0.4.5
⬆️ Bump ruff from 0.4.4 to 0.4.5
2024-05-24 13:55:40 +03:00
Piotr Skalski f40dd38b1a Merge pull request #1214 from roboflow/fix/remove-none-tracker-id-contents-checks
tracker_id contents are never `None`
2024-05-24 09:17:47 +02:00
Piotr Skalski ad7e643c83 Merge pull request #1180 from rolson24/fix_NaN_values_in_ByteTrack
Fix Cost Matrix containing NaN Values in ByteTrack
2024-05-24 09:01:12 +02:00
Piotr Skalski eae4040b17 Merge pull request #1224 from roboflow/docs/how-to-track-rename-title
Docs: shorter title for object tracking how-to
2024-05-24 07:59:41 +02:00
dependabot[bot] 9afca17854 ⬆️ Bump ruff from 0.4.4 to 0.4.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.4...v0.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-24 00:13:26 +00:00
Linas Kondrackis 19296a73f6 ious: Replace 0-area check with nan conversion 2024-05-23 18:45:23 +03:00
Linas Kondrackis 0721bc289b Remove _set_at_index 2024-05-23 16:21:54 +03:00
pre-commit-ci[bot] db1b4737fe fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-23 13:04:11 +00:00
Linas Kondrackis 6fbca8333e Address review comments, simplify merge
* Reintroduced iou check before response - necessary for algorithm
2024-05-23 16:01:34 +03:00
pre-commit-ci[bot] ad2220bc1d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-23 07:12:19 +00:00
SkalskiP 0115ef8b7d small fix when mask is empty 2024-05-23 09:11:47 +02:00
Onuralp SEZER d4541e1469 Merge pull request #1222 from roboflow/fix/yolo-obb
fix: 🐞 yolo obb condition check set or for newer and older version
2024-05-23 01:37:08 +03:00
Linas Kondrackis c9c2dad316 Docs: shorter title for object tracking how-to 2024-05-22 23:47:45 +03:00
Onuralp SEZER e59e2822d8 Merge pull request #1223 from roboflow/docs/how-to-track-rename-title
minor docs change: rename How To - Track Objects
2024-05-22 23:32:10 +03:00
Linas Kondrackis 99854a5b66 minor docs change: rename How To - Track Objects 2024-05-22 23:14:17 +03:00
Onuralp SEZER 251185ba67 fix: 🐞 yolo obb hasstr added into condition
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-05-22 23:08:04 +03:00
Linas Kondrackis 8b4884ec9e tracker, smoother: SupervisionWarnings 2024-05-22 21:33:34 +03:00
SkalskiP 07c36c6223 make linter happy 2024-05-22 17:47:52 +02:00
pre-commit-ci[bot] bf43d6566b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-22 14:29:23 +00:00
SkalskiP b59d17fdae Merge remote-tracking branch 'origin/from_paligemma_support' into from_paligemma_support
# Conflicts:
#	supervision/detection/lmm.py
#	test/detection/test_lmm.py
2024-05-22 16:29:04 +02:00
SkalskiP b81c5e8475 update to allow multi-word class names 2024-05-22 16:28:24 +02:00
pre-commit-ci[bot] 7eb918282c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-22 12:27:56 +00:00
SkalskiP 36a73f7d49 clean up 2024-05-22 14:26:13 +02:00
SkalskiP a2195aa378 initial commit adding support for from_lmm and specifically for PaliGemma 2024-05-22 14:24:52 +02:00
Onuralp SEZER 2bd4348e5d Merge pull request #1220 from roboflow/dependabot/pip/requests-2.32.2
⬆️ Bump requests from 2.32.1 to 2.32.2
2024-05-22 04:00:05 +03:00
dependabot[bot] 6b7861d4d1 ---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 01:02:35 +00:00
dependabot[bot] 414d3e9afb ---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 01:21:27 +00:00
pre-commit-ci[bot] 14b57345a7 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-12 21:31:18 +00:00
magda skoczen d292cbf80e Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervision into 1114_RLE_support_for_COCO 2024-05-12 23:02:45 +02:00
magda skoczen 8c857dd9a6 speed up rle to mask 2024-05-12 23:02:22 +02:00
Onuralp SEZER acfabc247f Merge pull request #1185 from roboflow/dependabot/pip/ruff-0.4.4
⬆️ Bump ruff from 0.4.3 to 0.4.4
2024-05-11 23:54:29 +03:00
Onuralp SEZER 61a63c83d9 Merge pull request #1188 from roboflow/docs/missing-return-in-slicer-callback
Minor docs update: missing return in inference slicer callback
2024-05-11 21:56:13 +03:00
Linas Kondrackis 528c960587 Minor docs update: missing return in inference slicer callback 2024-05-11 13:38:59 +03:00
Linas Kondrackis 6c6171b5d5 Simpify passing of shape 2024-05-10 18:25:02 +03:00
Linas Kondrackis b8efa25e93 Merge branch 'develop' into feat/inference-slicer-segmentation 2024-05-10 17:18:07 +03:00
SkalskiP 17c8e41fb3 bump version from 0.21.0rc3 to 0.21.0rc4 2024-05-10 15:50:03 +02:00
Piotr Skalski 0c3594432c Merge pull request #1177 from roboflow/fix/detections-merge-flexibility
More flexibility in Detections.merge
2024-05-10 15:40:06 +02:00
SkalskiP a8c44cfaff ready for merge 2024-05-10 15:36:44 +02:00
pre-commit-ci[bot] 3d0c3d9182 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-10 13:08:35 +00:00
SkalskiP 2364abf481 small error message update + few more test cases for merge_data 2024-05-10 15:08:18 +02:00
pre-commit-ci[bot] 7bd92b096f fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-10 08:18:38 +00:00
magda skoczen 39477feaf6 Merge branch '1114_RLE_support_for_COCO' of github.com:emSko/supervision into 1114_RLE_support_for_COCO 2024-05-10 10:17:13 +02:00
magda skoczen e4e66f8a9a faster mask to rle 2024-05-10 10:16:34 +02:00
dependabot[bot] 3b61ee674e ⬆️ Bump ruff from 0.4.3 to 0.4.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.3...v0.4.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-10 00:28:34 +00:00
Linas Kondrackis 01f7eb5be6 Retain type info by not excluding empty detections 2024-05-09 18:19:51 +03:00
pre-commit-ci[bot] 4cd1fbcef9 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-09 14:15:31 +00:00
Linas Kondrackis b6a55694f2 Move type resolution logic to loop where it's used 2024-05-09 17:12:53 +03:00
Linas Kondrackis 8c58ebe30d Roll back flex-merge on data, merge when key missing 2024-05-09 17:06:51 +03:00
Piotr Skalski 77034700dd Merge pull request #767 from DrawingProcess/mmdetectionmask
Mmdetection mask
2024-05-09 13:19:00 +02:00
Linas Kondrackis 1ebbe3a8d1 Removed comments, deindented, removed unused var 2024-05-09 09:17:37 +03:00
Onuralp SEZER 617bd30c5f Merge pull request #1175 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-05-08 22:50:20 +03:00
Onuralp SEZER 2e8720f156 Merge pull request #1176 from roboflow/dependabot/pip/jinja2-3.1.4
⬆️ Bump jinja2 from 3.1.3 to 3.1.4
2024-05-08 22:49:59 +03:00
Raif Olson c47b0ce990 filter out detections with zero area. 2024-05-08 11:07:14 -04:00
Linas Kondrackis 45563dfddf InferenceSlicer: Now works with segmentation 2024-05-08 14:07:37 +03:00
pre-commit-ci[bot] 008711aac4 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-08 10:50:32 +00:00
magda skoczen 649e27323d fix the line lengths 2024-05-08 12:49:25 +02:00
pre-commit-ci[bot] 36a301e7fc fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-07 16:01:37 +00:00
magda skoczen aac1d8862a assertion for valid mask in mask_to_rle function 2024-05-07 18:01:14 +02:00
pre-commit-ci[bot] d59ec0f223 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-07 15:36:47 +00:00
magda skoczen 3618ac35ec merge 2024-05-07 17:36:26 +02:00
magda skoczen a9f821c28a Assertion error when number of pixel in RLE does not match the nuber of pixels computed as width*height 2024-05-07 17:34:13 +02:00
Linas Kondrackis 7bb94ce966 Detections.merge merges None and []
* Detections.merge is much friendlier now. If there's a None
  or an empty array, it will merge it happily rather than
  complaining that everything needs to be either None or [].
* Data merge follows suit.
2024-05-07 17:30:12 +03:00
Linas Kondrackis fb49be8197 Tests for the new merge function 2024-05-07 16:09:05 +03:00
Piotr Skalski d7b6af84e6 Update README.md
fix errors in example commands
2024-05-07 12:25:41 +02:00
Piotr Skalski d3b845fea5 Merge pull request #1172 from ManzarIMalik/develop
Update README.md
2024-05-07 12:24:35 +02:00
Piotr Skalski 330eae4411 Merge pull request #1166 from tc360950/fix-inreasing-memory-consumption-in-bytetrack
Fix ever increasing memory consumption in ByteTrack
2024-05-07 12:20:18 +02:00
pre-commit-ci[bot] 6b5dacd8e3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 22:22:49 +00:00
magda skoczen 88a43cc795 unit tests for rle_to_mask and mask_to_rle functions 2024-05-07 00:22:24 +02:00
pre-commit-ci[bot] a941583bb0 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-05-06 21:07:51 +00:00
magda skoczen 2727152465 merge with upstream 2024-05-06 23:07:27 +02:00
dependabot[bot] 4d25437459 ⬆️ Bump jinja2 from 3.1.3 to 3.1.4
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 01:18:43 +00:00
Onuralp SEZER a75efbbf73 Merge pull request #1170 from roboflow/dependabot/pip/mkdocstrings-0.25.1
⬆️ Bump mkdocstrings from 0.25.0 to 0.25.1
2024-05-06 04:16:35 +03:00
Onuralp SEZER e9c8a80823 Merge pull request #1167 from roboflow/dependabot/pip/mike-2.1.1
⬆️ Bump mike from 2.1.0 to 2.1.1
2024-05-06 04:16:01 +03:00
Onuralp SEZER 0b92417948 Merge pull request #1168 from roboflow/dependabot/pip/mkdocs-material-9.5.21
⬆️ Bump mkdocs-material from 9.5.20 to 9.5.21
2024-05-06 04:15:20 +03:00
dependabot[bot] aebbd01830 ⬆️ Bump mkdocstrings from 0.25.0 to 0.25.1
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.25.0 to 0.25.1.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.25.0...0.25.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 01:15:08 +00:00
Onuralp SEZER c3ebccabe8 Merge pull request #1169 from roboflow/dependabot/pip/jupytext-1.16.2
⬆️ Bump jupytext from 1.16.1 to 1.16.2
2024-05-06 04:14:43 +03:00
dependabot[bot] ff52352b00 ⬆️ Bump jupytext from 1.16.1 to 1.16.2
Bumps [jupytext](https://github.com/mwouts/jupytext) from 1.16.1 to 1.16.2.
- [Release notes](https://github.com/mwouts/jupytext/releases)
- [Changelog](https://github.com/mwouts/jupytext/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mwouts/jupytext/compare/v1.16.1...v1.16.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 01:12:45 +00:00
dependabot[bot] f7d21acd3a ⬆️ Bump mkdocs-material from 9.5.20 to 9.5.21
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.20 to 9.5.21.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.20...9.5.21)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-30 00:33:04 +00:00
SkalskiP 4732b6f1d0 bump version from 0.21.0rc1 to 0.21.0rc2 2024-04-29 22:58:13 +02:00
Piotr Skalski c024b62020 Merge pull request #1154 from roboflow/fix/pin_inference_version_in_examples_to_fix_issues_related_to_braking_changes
pin inference version in examples
2024-04-29 21:50:37 +02:00
SkalskiP d4b1375db7 pin inference version 2024-04-29 21:46:07 +02:00
Piotr Skalski 71200a5942 Merge pull request #1147 from roboflow/feat/keypoints-from-inference
Add `from_inference` to KeyPoints
2024-04-29 20:40:17 +02:00
SkalskiP 66f6a86bbf add Roboflow API KEY to example 2024-04-29 20:37:56 +02:00
SkalskiP a70588284a add Roboflow API example 2024-04-29 20:36:05 +02:00
pre-commit-ci[bot] 67160c96b0 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.1...v0.4.2)
2024-04-29 17:52:51 +00:00
LinasKo 7331577e69 keypoints, from_inference: removed class_name option 2024-04-29 13:37:37 +03:00
Onuralp SEZER 5f00d8b595 Merge pull request #1151 from roboflow/dependabot/pip/tox-4.15.0
⬆️ Bump tox from 4.14.2 to 4.15.0
2024-04-29 12:03:30 +03:00
Onuralp SEZER 88209f233d Merge pull request #1150 from roboflow/dependabot/pip/mkdocstrings-0.25.0
⬆️ Bump mkdocstrings from 0.24.3 to 0.25.0
2024-04-29 12:03:09 +03:00
Onuralp SEZER d51067164a Merge pull request #1149 from roboflow/dependabot/pip/pytest-8.2.0
⬆️ Bump pytest from 8.1.1 to 8.2.0
2024-04-29 12:02:46 +03:00
pre-commit-ci[bot] 0dd8d7dca8 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-29 06:53:36 +00:00
Linas Kondrackis 7c39d9dc8d Merge branch 'develop' into feat/keypoints-from-inference 2024-04-29 09:53:22 +03:00
Linas Kondrackis d2d69f986e from_infernece: Example suggests users find their own model
* Example incorrectly suggested yolov8s-640
* Replaced with "<POSE_MODEL_ID>" until we have a COCO human pose model
  on prod. I don't think "horse-pose/3" is helpful - the use case is
  uncommon and the training quality is poor.
2024-04-29 09:46:12 +03:00
dependabot[bot] df0b2df2f6 ⬆️ Bump tox from 4.14.2 to 4.15.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.14.2 to 4.15.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.14.2...4.15.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-04-29 01:25:37 +00:00
dependabot[bot] 31844c165b ⬆️ Bump mkdocstrings from 0.24.3 to 0.25.0
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.24.3 to 0.25.0.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.24.3...0.25.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 01:24:07 +00:00
dependabot[bot] c5a2f87bb9 ⬆️ Bump pytest from 8.1.1 to 8.2.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.1 to 8.2.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.1.1...8.2.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-04-29 01:20:56 +00:00
Piotr Skalski 6ef9ba7390 Merge pull request #1148 from roboflow/fix/code_examples_after_supervision_0.20.0_release
fix/code examples after supervision 0.20.0 release
2024-04-26 17:59:25 +02:00
SkalskiP a532670611 bump version from 0.20.0 to 0.21.0rc1 2024-04-26 17:55:34 +02:00
SkalskiP ba2d30d110 calculate_dynamic_line_thickness -> calculate_optimal_line_thickness
`calculate_dynamic_text_scale` -> `calculate_optimal_text_scale`
2024-04-26 17:36:50 +02:00
Linas Kondrackis 619da9575a pre-commit tidy-up 2024-04-26 17:55:17 +03:00
LinasKo 25bdb560eb from_inference, support direct inference results 2024-04-26 17:53:19 +03:00
Onuralp SEZER 9b447296bf Merge pull request #1142 from roboflow/dependabot/pip/ruff-0.4.2
⬆️ Bump ruff from 0.4.1 to 0.4.2
2024-04-26 03:42:20 +03:00
Onuralp SEZER 4a16d66bfb Merge pull request #1141 from roboflow/dependabot/pip/mkdocs-material-9.5.19
⬆️ Bump mkdocs-material from 9.5.18 to 9.5.19
2024-04-26 03:41:54 +03:00
dependabot[bot] 8436f52f8a ⬆️ Bump ruff from 0.4.1 to 0.4.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.1 to 0.4.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.4.1...v0.4.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-04-26 00:33:10 +00:00
dependabot[bot] 059583e2ba ⬆️ Bump mkdocs-material from 9.5.18 to 9.5.19
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.18 to 9.5.19.
- [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.18...9.5.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-26 00:31:25 +00:00
pre-commit-ci[bot] 4bd593fe3c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-25 12:02:23 +00:00
Linas Kondrackis 399342513a Keypoints: rm NAS skeleton, improve docs examples 2024-04-25 15:00:01 +03:00
Linas Kondrackis c8c2ff5c45 Minor styling 2024-04-25 12:53:09 +03:00
Linas Kondrackis 18b3c17ed4 Styling 2024-04-25 12:42:33 +03:00
Linas Kondrackis cab06f2c2b Implementation of from_yolo_nas for keypoints
* Missing batch image support
2024-04-25 12:29:08 +03:00
Onuralp SEZER 2b53216a0b Merge pull request #1137 from roboflow/dependabot/pip/mypy-1.10.0
⬆️ Bump mypy from 1.9.0 to 1.10.0
2024-04-25 03:51:18 +03:00
dependabot[bot] e253f86e8f ⬆️ Bump mypy from 1.9.0 to 1.10.0
Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.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-04-25 00:46:24 +00:00
Piotr Skalski df4c546c10 Merge pull request #1076 from rolson24/add-minimum_consecutive_frames-to-bytetrack-better
add minimum_consecutive_frames to bytetrack
2024-04-25 00:25:18 +02:00
Piotr Skalski 25fc1ab6fa Merge pull request #1115 from jeslinpjames/update-examples
Update examples to remove `frame_resloution_wh` in [PolygonZone]
2024-04-24 22:43:47 +02:00
SkalskiP a45341ce63 final cleanup; setting supervision>=0.20.0 in requirements.txt and refactoring stream processing scripts 2024-04-24 22:41:59 +02:00
Piotr Skalski e93ef041d4 Merge pull request #1133 from roboflow/main
Merge pull request #1127 from roboflow/develop
2024-04-24 22:22:42 +02:00
Piotr Skalski f7f40f09ba Merge pull request #1127 from roboflow/develop
`supervision-0.20.0` release
2024-04-24 19:34:57 +02:00
Piotr Skalski c5f92a544b Merge pull request #1132 from roboflow/docs/supervision-0.20.0-changelog
docs/supervision 0.20.0 change log
2024-04-24 19:32:32 +02:00
SkalskiP d6a59cb851 Merge remote-tracking branch 'origin/docs/supervision-0.20.0-changelog' into docs/supervision-0.20.0-changelog 2024-04-24 19:25:54 +02:00
SkalskiP 5c4ed2650e drop accidentally committed from_inference 2024-04-24 19:25:45 +02:00
pre-commit-ci[bot] 6ae99444b5 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-24 17:24:18 +00:00
SkalskiP 7a90eefd5b changelog updated version bump from 0.20.0rc2 to 0.20.0 2024-04-24 19:22:59 +02:00
SkalskiP 65ddf9d67d Merge remote-tracking branch 'origin/develop' into develop 2024-04-24 16:57:44 +02:00
Piotr Skalski b0840911fc Merge pull request #1131 from roboflow/docs/refactor_before_0.20.0_release
docs/refactor before supervision 0.20.0 release
2024-04-24 16:47:06 +02:00
pre-commit-ci[bot] f4fca3339d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-24 14:45:20 +00:00
SkalskiP d7da00c55b make pre-commit happy 2024-04-24 16:43:40 +02:00
pre-commit-ci[bot] 383c1a1d8d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-24 14:39:03 +00:00
SkalskiP e5d1ac7238 Merge remote-tracking branch 'origin/docs/refactor_before_0.20.0_release' into docs/refactor_before_0.20.0_release 2024-04-24 16:38:47 +02:00
SkalskiP 895562d444 make pre-commit happy 2024-04-24 16:37:41 +02:00
pre-commit-ci[bot] 263420758e fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-24 14:36:17 +00:00
SkalskiP 1c42838ca1 improved docs linking 2024-04-24 16:35:34 +02:00
SkalskiP 908f3d14c6 improved docs linking 2024-04-24 16:28:48 +02:00
SkalskiP 389453777e vertex and edge annotators visualizations added 2024-04-24 16:18:21 +02:00
SkalskiP 77793571b0 major docs refactor 2024-04-24 15:39:08 +02:00
Linas Kondrackis 48809c1332 Add a basic from_supervision function to KeyPoints 2024-04-24 16:37:41 +03:00
Piotr Skalski 2c90a1ef1e Merge pull request #1129 from roboflow/fix/keypoints-no-detections
Fix keypoint crash with no detections
2024-04-24 14:07:49 +02:00
Linas Kondrackis 4b89bece4f Fix keypoint crash with no detections 2024-04-24 14:59:57 +03:00
Piotr Skalski 80e5413402 Merge pull request #1128 from roboflow/feat/dev/keypoints-annotated
initial key point support and annotators
2024-04-24 12:25:58 +02:00
Linas Kondrackis 741309fd09 Remove Skeleton from __init__, small renames, consts 2024-04-24 13:13:10 +03:00
pre-commit-ci[bot] 08d7a6b4cd fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-24 09:53:36 +00:00
Linas Kondrackis d1c103ee16 Best skeleton cache ever, obviously 2024-04-24 12:50:19 +03:00
pre-commit-ci[bot] 0f6ad236eb fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-24 09:45:47 +00:00
Linas Kondrackis 1c27bf7fea EdgeAnnotator, warn and not crash when None 2024-04-24 12:44:29 +03:00
pre-commit-ci[bot] 0749efed8f fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-24 06:52:19 +00:00
Linas Kondrackis d8681b8b4f remove xy_all, turn to for xy in keypoints.xy 2024-04-24 09:51:40 +03:00
pre-commit-ci[bot] 9ad35bcfb9 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-23 22:44:02 +00:00
SkalskiP 102c5adc38 test simplified skeleton API 2024-04-24 00:43:46 +02:00
SkalskiP a2dbbcce63 Merge remote-tracking branch 'origin/feat/dev/keypoints-annotated' into feat/dev/keypoints-annotated 2024-04-24 00:24:29 +02:00
SkalskiP 397b2249a7 fix typo 2024-04-24 00:24:21 +02:00
pre-commit-ci[bot] e2865ecda7 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-23 22:21:49 +00:00
SkalskiP 0ff1cfac07 Merge remote-tracking branch 'origin/feat/dev/keypoints-annotated' into feat/dev/keypoints-annotated 2024-04-24 00:21:30 +02:00
SkalskiP 2745c70ae0 docs plugged into mkdocs.yml 2024-04-24 00:21:19 +02:00
pre-commit-ci[bot] e2fabc1cfe fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-23 21:57:30 +00:00
SkalskiP a8d5e05885 Merge remote-tracking branch 'origin/feat/dev/keypoints-annotated' into feat/dev/keypoints-annotated 2024-04-23 23:57:16 +02:00
SkalskiP 9789ff4043 solve CI tests 2024-04-23 23:57:08 +02:00
SkalskiP 0274c45dbc solve CI tests 2024-04-23 23:12:54 +02:00
SkalskiP 6a541845ba Merge branch 'develop' into feat/dev/keypoints-annotated 2024-04-23 23:07:37 +02:00
pre-commit-ci[bot] 409dcbd9da fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-23 20:49:06 +00:00
Linas Kondrackis 07482a347c Keypoints: Multi-person annotators 2024-04-23 20:43:14 +03:00
Linas Kondrackis 7f0ecbbd0a Keypoints: Smart skellies 2024-04-23 20:19:28 +03:00
Linas Kondrackis a00604bfb2 Keypoints: Expose KeyPoints, annotators in __init__ 2024-04-23 19:28:34 +03:00
Linas Kondrackis d079ce92df Keypoints: Separate-out skeleton annotator 2024-04-23 19:26:12 +03:00
Linas Kondrackis a6b563cf4d Add keypoints skeleton, 2x annotators 2024-04-23 17:18:34 +03:00
Onuralp SEZER 7c2aad194a Merge pull request #1124 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-04-23 00:47:00 +03:00
pre-commit-ci[bot] feae6d3f78 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.3.7 → v0.4.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.7...v0.4.1)
2024-04-22 17:43:51 +00:00
Onuralp SEZER 4ebace283c Merge pull request #1123 from roboflow/dependabot/pip/ruff-0.4.1
⬆️ Bump ruff from 0.4.0 to 0.4.1
2024-04-22 04:03:29 +03:00
dependabot[bot] 98493c2ae1 ⬆️ Bump ruff from 0.4.0 to 0.4.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.0 to 0.4.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.4.0...v0.4.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-04-22 00:45:54 +00:00
Onuralp SEZER 8b32815648 Merge pull request #1122 from roboflow/dependabot/pip/ruff-0.4.0
⬆️ Bump ruff from 0.3.7 to 0.4.0
2024-04-19 13:36:40 +03:00
dependabot[bot] a1a66e7078 ⬆️ Bump ruff from 0.3.7 to 0.4.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.7 to 0.4.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.3.7...v0.4.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-04-19 00:17:17 +00:00
Onuralp SEZER fd4ef9960a Merge pull request #1119 from roboflow/dependabot/pip/mkdocs-material-9.5.18
⬆️ Bump mkdocs-material from 9.5.17 to 9.5.18
2024-04-18 08:39:53 +03:00
Onuralp SEZER 6c54da2afa Merge pull request #1121 from roboflow/dependabot/pip/notebook-7.1.3
⬆️ Bump notebook from 7.1.2 to 7.1.3
2024-04-18 08:39:31 +03:00
dependabot[bot] 53eb76922d ⬆️ Bump notebook from 7.1.2 to 7.1.3
Bumps [notebook](https://github.com/jupyter/notebook) from 7.1.2 to 7.1.3.
- [Release notes](https://github.com/jupyter/notebook/releases)
- [Changelog](https://github.com/jupyter/notebook/blob/@jupyter-notebook/tree@7.1.3/CHANGELOG.md)
- [Commits](https://github.com/jupyter/notebook/compare/@jupyter-notebook/tree@7.1.2...@jupyter-notebook/tree@7.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-18 00:42:40 +00:00
dependabot[bot] b3f022b15a ⬆️ Bump mkdocs-material from 9.5.17 to 9.5.18
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.17 to 9.5.18.
- [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.17...9.5.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-17 00:29:27 +00:00
Onuralp SEZER 91e03ce903 Merge pull request #1118 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-04-15 23:25:35 +03:00
pre-commit-ci[bot] 2af86e56f0 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.3.5 → v0.3.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.5...v0.3.7)
2024-04-15 17:52:01 +00:00
Onuralp SEZER af0ece26d7 Merge pull request #1117 from roboflow/dependabot/pip/ruff-0.3.7
⬆️ Bump ruff from 0.3.6 to 0.3.7
2024-04-15 14:15:29 +03:00
dependabot[bot] 77d3276ad6 ⬆️ Bump ruff from 0.3.6 to 0.3.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.6 to 0.3.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.6...v0.3.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 01:13:38 +00:00
Jeslin P James df4a6f857c added docs for RichLabelAnnotator 2024-04-13 18:58:18 +05:30
pre-commit-ci[bot] cb220479eb fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-13 13:16:16 +00:00
Jeslin P James 8bb8ce0316 changed error message and formatted code 2024-04-13 18:03:08 +05:30
Jeslin P James 8255bcb1ca Moved resolve_text_background_xyxy() function into utils.py 2024-04-13 17:43:52 +05:30
Jeslin P James 8329d155e4 Use default font when font_path isnt specified 2024-04-13 17:27:27 +05:30
Jeslin P James a41eea7c45 Merge branch 'develop' of github.com:jeslinpjames/supervision into RichLabelAnnotator 2024-04-13 13:36:26 +05:30
pre-commit-ci[bot] d531c59f85 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-13 05:39:05 +00:00
Jeslin P James 15b1473c2f Updated examples to remove frame_resolution_wh from PolygonZone 2024-04-13 10:28:11 +05:30
Jeslin P James 26c2a7ef9c updated examples 2024-04-13 10:18:30 +05:30
Piotr Skalski c8d2a54ed8 Merge pull request #1078 from roboflow/docs/ci/release-docs
ci: 👷 supervision documentation 📚 release CI added
2024-04-12 17:49:26 +02:00
Onuralp SEZER 9d9acd7e58 Merge pull request #1112 from roboflow/dependabot/pip/idna-3.7
⬆️ Bump idna from 3.6 to 3.7
2024-04-12 17:26:41 +03:00
Piotr Skalski f9ed80f91b Merge pull request #1109 from jeslinpjames/polygonzone-update
Dropped frame_resolution_wh from [PolygonZone]
2024-04-12 16:23:56 +02:00
SkalskiP dfd9fc8e6d fix too long string 2024-04-12 16:17:29 +02:00
SkalskiP 2c62f0b26d drop frame_resolution_wh from docs 2024-04-12 16:15:10 +02:00
SkalskiP 0ad3796571 bug fix 2024-04-12 16:13:09 +02:00
Onuralp SEZER da236aa565 Merge pull request #1110 from roboflow/dependabot/pip/ruff-0.3.6
⬆️ Bump ruff from 0.3.5 to 0.3.6
2024-04-12 16:19:30 +03:00
Jeslin P James 0800b0569f Merge branch 'polygonzone-update' of github.com:jeslinpjames/supervision into polygonzone-update 2024-04-12 16:46:02 +05:30
Jeslin P James 510df54572 Updated order of parameters in PolygonZone __init__() 2024-04-12 16:16:48 +05:30
dependabot[bot] 89d84d1f74 ⬆️ Bump idna from 3.6 to 3.7
Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-12 04:14:49 +00:00
dependabot[bot] 7126adc74a ⬆️ Bump ruff from 0.3.5 to 0.3.6
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.5 to 0.3.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.5...v0.3.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-12 00:13:05 +00:00
pre-commit-ci[bot] 743e42080b fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-11 13:57:50 +00:00
Jeslin P James b64c61c388 formated code 2024-04-11 19:04:06 +05:30
Jeslin P James 28baf18cb6 updated PolygonZone Attributes docs 2024-04-11 18:59:48 +05:30
pre-commit-ci[bot] 361e444f53 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-11 13:27:43 +00:00
Jeslin P James b7607c6f15 Updated PolyZone class to compute frame_resolution_wh based on polygon coordinates 2024-04-11 18:57:30 +05:30
Raif Olson 07756328db Merge branch 'add-minimum_consecutive_frames-to-bytetrack-better' of https://github.com/rolson24/supervision into add-minimum_consecutive_frames-to-bytetrack-better 2024-04-11 09:27:28 -04:00
Raif Olson f98ded49a2 fix 2024-04-11 09:27:24 -04:00
pre-commit-ci[bot] bcb31f8809 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-11 13:21:51 +00:00
Raif Olson b1d5d4faf0 fix to exactly match existing release version when minimum_consecutive_frames = 1 2024-04-11 09:21:32 -04:00
Piotr Skalski d3dbe7c597 Update README.md 2024-04-11 14:54:22 +02:00
Piotr Skalski 9284bfdcd9 Update README.md 2024-04-11 14:44:50 +02:00
Piotr Skalski 263421d145 Update README.md 2024-04-11 14:40:43 +02:00
Raif Olson 7924a13dc5 only assign external track_id once 2024-04-11 08:28:08 -04:00
pre-commit-ci[bot] 755f292ed1 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-11 12:21:46 +00:00
Raif Olson 6166d92458 fix reassigning track_id after a track is re-activated. 2024-04-11 08:21:26 -04:00
Piotr Skalski ed225187b2 Update README.md 2024-04-11 14:09:02 +02:00
mario-dg 166a8da9a0 Implement Feedback 2024-04-11 12:22:44 +02:00
pre-commit-ci[bot] 2c9a1f69c5 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-10 17:57:46 -04:00
pre-commit-ci[bot] a2ab113f21 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-10 17:57:46 -04:00
Raif Olson eaff97f902 add external and internal track_ids to keep valid track ids sequential 2024-04-10 17:57:45 -04:00
Onuralp SEZER 855f9191c5 Merge pull request #1107 from LinasKo/feature/new-video-beach
Add beach drone footage asset
2024-04-10 09:46:25 +03:00
Linas Kondrackis 98b64f5e7d Add beach drone footage asset 2024-04-10 09:34:44 +03:00
Onuralp SEZER 048d1a8b98 Merge pull request #1106 from roboflow/dependabot/pip/mkdocs-jupyter-0.24.7
⬆️ Bump mkdocs-jupyter from 0.24.3 to 0.24.7
2024-04-10 03:35:14 +03:00
dependabot[bot] 2fd5452181 ⬆️ Bump mkdocs-jupyter from 0.24.3 to 0.24.7
Bumps [mkdocs-jupyter](https://github.com/danielfrg/mkdocs-jupyter) from 0.24.3 to 0.24.7.
- [Changelog](https://github.com/danielfrg/mkdocs-jupyter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/danielfrg/mkdocs-jupyter/compare/0.24.3...0.24.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-10 00:14:47 +00:00
Piotr Skalski 10843c9989 Merge pull request #1104 from roboflow/letterbox_pr_docs_improvements
docs improvements related to #1028 PR
2024-04-09 23:19:44 +02:00
SkalskiP eb1c7716ef make ruff happy 2024-04-09 23:18:02 +02:00
pre-commit-ci[bot] c27d9eaf22 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 21:16:21 +00:00
SkalskiP 5eb43a25c9 Merge remote-tracking branch 'origin/letterbox_pr_docs_improvements' into letterbox_pr_docs_improvements 2024-04-09 23:15:57 +02:00
SkalskiP e0d5f00ae3 initial version of How to Save Detections guide done 2024-04-09 23:15:49 +02:00
pre-commit-ci[bot] 99fbd4b601 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 20:45:09 +00:00
SkalskiP d155592f45 Merge remote-tracking branch 'origin/letterbox_pr_docs_improvements' into letterbox_pr_docs_improvements 2024-04-09 22:44:54 +02:00
SkalskiP d929d6f58b How to Save Detections guide update 2024-04-09 22:44:10 +02:00
pre-commit-ci[bot] 6248fa7d17 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 20:08:13 +00:00
SkalskiP 7ad50cc7c2 Merge remote-tracking branch 'origin/letterbox_pr_docs_improvements' into letterbox_pr_docs_improvements 2024-04-09 22:07:58 +02:00
SkalskiP c30e377eb5 more docs improvements 2024-04-09 22:07:12 +02:00
pre-commit-ci[bot] f872fddaff fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 19:24:04 +00:00
SkalskiP 079e62fb0c rename place_image to overlay image 2024-04-09 21:23:23 +02:00
mario-dg 5f0dcc2cb2 Merge branch 'develop' into add_nmm_to_detections to resolve conflicts 2024-04-09 18:50:55 +02:00
Jeslin P James 9e906c78f5 annotate function added in RichLabelAnnotator 2024-04-09 22:14:36 +05:30
SkalskiP 774ac2ed4f update letterbox_image docs 2024-04-09 17:59:19 +02:00
pre-commit-ci[bot] a7dccfcc0d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 15:48:33 +00:00
Onuralp SEZER 15a4555b6a Merge pull request #1102 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-04-09 18:48:21 +03:00
SkalskiP 233d25d4c5 fix resize_image tests after rename 2024-04-09 17:48:13 +02:00
Jeslin P James 2417b36006 resolve_text_background_xyxy() function added to RichLabelAnnotator 2024-04-09 20:39:53 +05:30
pre-commit-ci[bot] ad94689362 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 14:46:48 +00:00
SkalskiP 94e06ab0d6 scale_image, resize_image changes 2024-04-09 16:46:05 +02:00
Onuralp SEZER b63379749e fix(keypoint): 🚀 getitem and setitem funcs added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-04-09 17:43:40 +03:00
Onuralp SEZER 5d43e1380d feat(test): 🚀 mock keypoints added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-04-09 17:42:31 +03:00
Onuralp SEZER 7d0cf8a723 feat: 🚀 initial sv.Keypoints features added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-04-09 15:51:16 +03:00
pre-commit-ci[bot] f2e6d882fb fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 12:50:50 +00:00
SkalskiP 2115c9a152 crop_image improvements 2024-04-09 14:50:32 +02:00
pre-commit-ci[bot] 5b1ee8f870 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-09 12:31:32 +00:00
SkalskiP 574c61b6b0 initial commit 2024-04-09 14:28:40 +02:00
Jeslin P James 1a7309e7c9 RichLabelAnnotator class added 2024-04-09 16:51:41 +05:30
pre-commit-ci[bot] 6b682f00ae chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0)
- [github.com/astral-sh/ruff-pre-commit: v0.3.4 → v0.3.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.4...v0.3.5)
2024-04-08 17:55:20 +00:00
Onuralp SEZER 4729e20a94 Merge pull request #1099 from roboflow/dependabot/pip/mkdocstrings-0.24.3
⬆️ Bump mkdocstrings from 0.24.2 to 0.24.3
2024-04-08 04:16:40 +03:00
dependabot[bot] b5ccc99537 ⬆️ Bump mkdocstrings from 0.24.2 to 0.24.3
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.24.2 to 0.24.3.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.24.2...0.24.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 01:11:06 +00:00
Onuralp SEZER 922ebb237a Merge pull request #1097 from pirnerjonas/fix/formatting-in-docs
fix: formatting in notebook.md
2024-04-07 09:54:33 +03:00
Jonas Pirner 0ae2c1206f fix: formatting in notebook.md 2024-04-06 21:40:19 +02:00
Piotr Skalski 372e703a7a Merge pull request #1028 from roboflow/feature/add_tiles_generator
Add util to create image tiles
2024-04-05 18:37:19 +02:00
pre-commit-ci[bot] 0a919db9fd fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-05 16:02:57 +00:00
SkalskiP 473f140133 create_batches and fill docs added 2024-04-05 18:01:17 +02:00
pre-commit-ci[bot] e93224570f fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-05 15:51:35 +00:00
SkalskiP 5e9c3fdfa5 refactor tests to use @pytest.mark.parametrize 2024-04-05 17:51:08 +02:00
Paweł Pęczek b76f864f70 Rename decorators to convert images to acceptable format 2024-04-05 17:45:49 +02:00
SkalskiP c9095881ae docs updates 2024-04-05 17:33:52 +02:00
Piotr Skalski 299c933c4c Merge pull request #1093 from macc-n/ultralytics-results
Fixed obb key check in ultralytics_results
2024-04-05 13:38:06 +02:00
Piotr Skalski 9ae69c71d9 Merge pull request #1084 from sharingan000/geometry-utils-update-polygon-center-getter
Geometry utils update polygon center getter
2024-04-05 13:07:44 +02:00
Piotr Skalski a60884d94f Merge pull request #1046 from jeslinpjames/feature/transformers-support
Enhanced from_transformers to support class name extraction
2024-04-05 12:12:30 +02:00
SkalskiP e2f1d5812e docs improvements 2024-04-05 12:10:10 +02:00
pre-commit-ci[bot] dd7fddb4f5 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-05 09:50:00 +00:00
SkalskiP ee38ba36c4 docs improvements 2024-04-05 11:49:43 +02:00
macc-n 79da48fc49 If conditions merged 2024-04-04 18:45:42 +02:00
pre-commit-ci[bot] 7e21aaaa1a fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-04 16:16:35 +00:00
sharingan000 57335ef986 just comments 2024-04-05 02:14:44 +10:00
sharingan000 c605687f82 Ehe fix :) 2024-04-05 02:06:03 +10:00
sharingan000 6001e4033e Merge remote-tracking branch 'origin/geometry-utils-update-polygon-center-getter' into geometry-utils-update-polygon-center-getter 2024-04-05 02:04:03 +10:00
sharingan000 761fb89241 Fixed test/geometry/test_utils 2024-04-05 02:03:53 +10:00
pre-commit-ci[bot] 5f0082048c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-04 16:01:22 +00:00
sharingan000 0b86478417 Only solid algorithm geometry/utils 2024-04-05 02:00:54 +10:00
macc-n c8ea42af6d Fixed obb key check in ultralytics_results 2024-04-04 17:32:41 +02:00
sharingan000 40200ddd8d Merge remote-tracking branch 'origin/develop' into geometry-utils-update-polygon-center-getter 2024-04-04 15:01:49 +10:00
pre-commit-ci[bot] 8de73738c7 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-04 04:59:31 +00:00
sharingan000 0cf31d7e55 Merge remote-tracking branch 'origin/geometry-utils-update-polygon-center-getter' into geometry-utils-update-polygon-center-getter
# Conflicts:
#	supervision/geometry/utils.py
2024-04-04 14:58:36 +10:00
sharingan000 eb3317694e Include all three algorithms 2024-04-04 14:57:57 +10:00
pre-commit-ci[bot] 960e2529b1 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-03 10:44:27 +00:00
sharingan000 b7d18681b1 Merge remote-tracking branch 'origin/geometry-utils-update-polygon-center-getter' into geometry-utils-update-polygon-center-getter 2024-04-03 20:42:48 +10:00
sharingan000 cd81c1cef4 Updated algorithm 2024-04-03 20:40:54 +10:00
Onuralp SEZER fe1e0d9bec Merge pull request #1088 from roboflow/dependabot/pip/mkdocs-material-9.5.17
⬆️ Bump mkdocs-material from 9.5.16 to 9.5.17
2024-04-03 03:44:53 +03:00
Onuralp SEZER 275c7c6cc3 Merge pull request #1087 from roboflow/dependabot/pip/mkdocstrings-0.24.2
⬆️ Bump mkdocstrings from 0.24.1 to 0.24.2
2024-04-03 03:44:39 +03:00
dependabot[bot] 79c731b431 ⬆️ Bump mkdocs-material from 9.5.16 to 9.5.17
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.16 to 9.5.17.
- [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.16...9.5.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 00:35:04 +00:00
dependabot[bot] f15a6224ff ⬆️ Bump mkdocstrings from 0.24.1 to 0.24.2
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.24.1 to 0.24.2.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.24.1...0.24.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 00:30:06 +00:00
jeslinpjames b6b72eddce add defensive checks to from_transformers 2024-04-02 18:53:42 +05:30
jeslinpjames b02e8664a1 mark id2label as Optional in from_transformers docs 2024-04-02 18:39:24 +05:30
jeslinpjames c7a0f62dea Merge branch 'develop' of github.com:jeslinpjames/supervision into feature/transformers-support 2024-04-02 18:20:53 +05:30
pre-commit-ci[bot] dcad812c5c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-04-02 07:15:15 +00:00
sharingan000 7ded0bd493 Change the method of finding polygon center and create test for new function 2024-04-02 16:55:03 +10:00
sharingan000 a38ae3f840 Change the method of finding polygon center and create test for new function 2024-04-02 16:54:34 +10:00
Onuralp SEZER 175dd7d0f5 Merge pull request #1082 from roboflow/dependabot/pip/ruff-0.3.5
⬆️ Bump ruff from 0.3.4 to 0.3.5
2024-04-02 04:06:40 +03:00
Onuralp SEZER 173a5d63a2 Merge pull request #1083 from roboflow/dependabot/pip/pillow-10.3.0
⬆️ Bump pillow from 10.2.0 to 10.3.0
2024-04-02 04:06:06 +03:00
dependabot[bot] f21597446b ⬆️ Bump pillow from 10.2.0 to 10.3.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.2.0 to 10.3.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.2.0...10.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 00:47:42 +00:00
dependabot[bot] da5dfef2ed ⬆️ Bump ruff from 0.3.4 to 0.3.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.4 to 0.3.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.4...v0.3.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-02 00:45:56 +00:00
Onuralp SEZER 03803a7d7e Merge pull request #1079 from roboflow/dependabot/pip/mkdocs-material-9.5.16
⬆️ Bump mkdocs-material from 9.5.15 to 9.5.16
2024-04-01 04:13:13 +03:00
dependabot[bot] cbc5aa49f5 ⬆️ Bump mkdocs-material from 9.5.15 to 9.5.16
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.15 to 9.5.16.
- [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.15...9.5.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 00:50:45 +00:00
Onuralp SEZER 8fc5413394 ci: 👷 supervision documentation 📚 release CI added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-04-01 02:37:57 +03:00
Raif Olson ad0e6c3b51 Add minimum consecutive frames to ByteTrack 2024-03-29 16:59:29 -04:00
Onuralp SEZER b5250105e0 Merge pull request #1071 from roboflow/dependabot/pip/build-1.2.1
⬆️ Bump build from 1.2.0 to 1.2.1
2024-03-29 16:34:00 +03:00
dependabot[bot] 90ad1c2c9a ⬆️ Bump build from 1.2.0 to 1.2.1
Bumps [build](https://github.com/pypa/build) from 1.2.0 to 1.2.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.2.0...1.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 00:38:59 +00:00
jeslinpjames 7dedc452b7 Added missing data field 2024-03-29 01:44:37 +05:30
jeslinpjames 767f9a7148 formatted code 2024-03-29 01:14:10 +05:30
jeslinpjames e6a1d58500 Update documentation for from_transformers function 2024-03-29 01:11:17 +05:30
jeslinpjames a8988d1d4d Enhance from_transformers to support class name extraction for Transformers 2024-03-29 01:05:49 +05:30
jeslinpjames 42a4ed4166 Merge branch 'develop' of github.com:jeslinpjames/supervision into feature/transformers-support 2024-03-29 00:47:29 +05:30
Piotr Skalski f4f9b3d53a Merge pull request #1068 from roboflow/docs/anaconda-install
docs: 📚 conda and mamba installation methods added
2024-03-28 18:55:47 +01:00
pre-commit-ci[bot] 719af725b1 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-28 17:53:57 +00:00
SkalskiP e88ab7c125 small update 2024-03-28 18:53:39 +01:00
Piotr Skalski 77e38ba93b Merge pull request #1069 from roboflow/from_transformers_improvements
`from_transformers` improvements
2024-03-28 17:17:18 +01:00
SkalskiP 68e1bae6de Merge remote-tracking branch 'origin/from_transformers_improvements' into from_transformers_improvements 2024-03-28 16:34:25 +01:00
SkalskiP afc31e42a5 few more improvements 2024-03-28 16:34:05 +01:00
pre-commit-ci[bot] 0c7c746f15 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-28 15:10:30 +00:00
SkalskiP 4ad78becb6 initial comment 2024-03-28 16:08:05 +01:00
Onuralp SEZER f9fd88fb01 docs: 📚 conda and mamba installation methods added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-28 18:00:43 +03:00
SkalskiP ccc7118bcb bump version from 0.20.0rc1 to 0.20.0rc2 2024-03-28 15:41:16 +01:00
Piotr Skalski 2cccfc5b62 Merge pull request #1067 from roboflow/ci/dev-ci
fix: 🐞 ci trigger error fix
2024-03-28 15:39:00 +01:00
Onuralp SEZER 93584cbb89 fix: 🐞 ci trigger error fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-03-28 17:29:10 +03:00
Piotr Skalski 5bfc977fac Merge pull request #1065 from roboflow/revert-1064-ci/dev-ci
Revert "fix: 🐞 ci trigger error fix"
2024-03-28 15:21:42 +01:00
Piotr Skalski 16c41d9d5d Revert "fix: 🐞 ci trigger error fix" 2024-03-28 15:19:24 +01:00
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
pre-commit-ci[bot] c56c446eb6 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-26 02:06:09 +00:00
jeslinpjames 8a06de47e9 made id2label param optional and handle class names 2024-03-26 07:35:45 +05:30
jeslinpjames a51267a4fa Merge branch 'develop' of github.com:jeslinpjames/supervision into feature/transformers-support 2024-03-26 07:33:30 +05:30
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] 8694f0023d fix(pre_commit): 🎨 auto format pre-commit hooks 2024-03-25 19:24:23 +00:00
jeslinpjames b2b417af5b Enhanced from_transformers to support class name extraction 2024-03-26 00:39:11 +05:30
Paweł Pęczek 0aacfa47d3 Add new utils to library interface 2024-03-25 19:47:45 +01:00
Paweł Pęczek de8222203c Add tests for tiles creation 2024-03-25 19:44:46 +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
Paweł Pęczek 4adb2339a4 Add tests and docstrings for utils 2024-03-25 16:02:00 +01:00
Paweł Pęczek 444853054f Add docstrings for image module 2024-03-25 15:22:52 +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
Paweł Pęczek 4bb711dcea Add draft of changes to support adding titles 2024-03-25 15:09:21 +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
Paweł Pęczek 7eba338243 Add first draft of implementation for function creating image tiles 2024-03-20 23:08:39 +01: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
pre-commit-ci[bot] 27b61ab4a3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-01-22 23:31:31 +00:00
Seongjun Choi bddb72fb28 Modify: from_mmdetection add mask. See #703 2024-01-23 15:26:02 +09:00
Seongjun Choi 823dc56057 Modify: from_mmdetection add mask. See #703 2024-01-23 15:14:56 +09:00
mario-dg 6f4704625b Refactored merging of Detection attributes to better work with np.ndarrays 2023-10-19 00:05:42 +02:00
mario-dg 9f222736e1 Added standard full image inference after sliced inference to increase large object detection accuracy 2023-10-19 00:05:05 +02:00
mario-dg 57b12e6e00 Added __setitem__ to Detections and refactored the object prediction merging 2023-10-19 00:03:36 +02:00
mario-dg c78ae33e43 feat: 🚀 Added Non-Maximum Merging to Detections 2023-10-13 18:24:22 +02:00
131 changed files with 10255 additions and 1691 deletions
@@ -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]
@@ -0,0 +1,55 @@
name: Supervision Release Documentation Workflow 📚
on:
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:
doc-build-deploy:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: 📦 Install mkdocs-material
run: pip install "mkdocs-material"
- 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: ⚙️ Configure git for github-actions 👷
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: 🚀 Deploy MkDoc-Material 📚
run: |
latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
MKDOCS_GIT_COMMITTERS_APIKEY=${{ secrets.GITHUB_TOKEN }} mike deploy --push --update-aliases $latest_tag latest
+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
+2 -3
View File
@@ -7,7 +7,7 @@ ci:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
@@ -27,7 +27,6 @@ repos:
- id: mixed-line-ending
- repo: https://github.com/PyCQA/bandit
rev: '1.7.8'
hooks:
@@ -46,7 +45,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.2
rev: v0.4.7
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+21 -18
View File
@@ -18,10 +18,10 @@
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb)
[![Gradio](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/Roboflow/Annotators)
[![Discord](https://img.shields.io/discord/1159501506232451173)](https://discord.gg/GbfgXGJ8Bk)
[![Built with Material for MkDocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?logo=MaterialForMkDocs&logoColor=white)](https://squidfunk.github.io/mkdocs-material/)
[![colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb)
[![gradio](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/Roboflow/Annotators)
[![discord](https://img.shields.io/discord/1159501506232451173)](https://discord.gg/GbfgXGJ8Bk)
[![built-with-material-for-mkdocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?logo=MaterialForMkDocs&logoColor=white)](https://squidfunk.github.io/mkdocs-material/)
</div>
## 👋 hello
@@ -39,7 +39,7 @@ Pip install the supervision package in a
pip install supervision
```
Read more about desktop, headless, and local installation in our [guide](https://roboflow.github.io/supervision/).
Read more about conda, mamba, and installing from source in our [guide](https://roboflow.github.io/supervision/).
## 🔥 quickstart
@@ -71,16 +71,15 @@ len(detections)
```python
import cv2
import supervision as sv
from inference.models.utils import get_roboflow_model
from inference import get_model
image = cv2.imread(...)
model = get_roboflow_model(model_id="yolov8s-640", api_key=<ROBOFLOW API KEY>)
model = get_model(model_id="yolov8s-640", api_key=<ROBOFLOW API KEY>)
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
len(detections)
# 5
# 5
```
</details>
@@ -217,19 +216,23 @@ len(dataset)
## 🎬 tutorials
<p align="left">
<a href="https://youtu.be/uWP6UjDeZvY" title="Speed Estimation & Vehicle Tracking | Computer Vision | Open Source"><img src="https://github.com/SkalskiP/SkalskiP/assets/26109316/61a444c8-b135-48ce-b979-2a5ab47c5a91" alt="Speed Estimation & Vehicle Tracking | Computer Vision | Open Source" width="300px" align="left" /></a>
<a href="https://youtu.be/uWP6UjDeZvY" title="Speed Estimation & Vehicle Tracking | Computer Vision | Open Source"><strong>Speed Estimation & Vehicle Tracking | Computer Vision | Open Source</strong></a>
<div><strong>Created: 11 Jan 2024</strong> | <strong>Updated: 11 Jan 2024</strong></div>
<br/> Learn how to track and estimate the speed of vehicles using YOLO, ByteTrack, and Roboflow Inference. This comprehensive tutorial covers object detection, multi-object tracking, filtering detections, perspective transformation, speed estimation, visualization improvements, and more.</p>
Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](https://github.com/roboflow/supervision/tree/develop/examples), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!
<br/>
<p align="left">
<a href="https://youtu.be/4Q3ut7vqD5o" title="Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking"><img src="https://github.com/roboflow/supervision/assets/26109316/54afdf1c-218c-4451-8f12-627fb85f1682" alt="Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking" width="300px" align="left" /></a>
<a href="https://youtu.be/4Q3ut7vqD5o" title="Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking"><strong>Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking</strong></a>
<div><strong>Created: 6 Sep 2023</strong> | <strong>Updated: 6 Sep 2023</strong></div>
<br/> In this video, we explore real-time traffic analysis using YOLOv8 and ByteTrack to detect and track vehicles on aerial images. Harnessing the power of Python and Supervision, we delve deep into assigning cars to specific entry zones and understanding their direction of movement. By visualizing their paths, we gain insights into traffic flow across bustling roundabouts... </p>
<a href="https://youtu.be/hAWpsIuem10" title="Dwell Time Analysis with Computer Vision | Real-Time Stream Processing"><img src="https://github.com/SkalskiP/SkalskiP/assets/26109316/a742823d-c158-407d-b30f-063a5d11b4e1" alt="Dwell Time Analysis with Computer Vision | Real-Time Stream Processing" width="300px" align="left" /></a>
<a href="https://youtu.be/hAWpsIuem10" title="Dwell Time Analysis with Computer Vision | Real-Time Stream Processing"><strong>Dwell Time Analysis with Computer Vision | Real-Time Stream Processing</strong></a>
<div><strong>Created: 5 Apr 2024</strong></div>
<br/>Learn how to use computer vision to analyze wait times and optimize processes. This tutorial covers object detection, tracking, and calculating time spent in designated zones. Use these techniques to improve customer experience in retail, traffic management, or other scenarios.</p>
<br/>
<p align="left">
<a href="https://youtu.be/uWP6UjDeZvY" title="Speed Estimation & Vehicle Tracking | Computer Vision | Open Source"><img src="https://github.com/SkalskiP/SkalskiP/assets/26109316/61a444c8-b135-48ce-b979-2a5ab47c5a91" alt="Speed Estimation & Vehicle Tracking | Computer Vision | Open Source" width="300px" align="left" /></a>
<a href="https://youtu.be/uWP6UjDeZvY" title="Speed Estimation & Vehicle Tracking | Computer Vision | Open Source"><strong>Speed Estimation & Vehicle Tracking | Computer Vision | Open Source</strong></a>
<div><strong>Created: 11 Jan 2024</strong></div>
<br/>Learn how to track and estimate the speed of vehicles using YOLO, ByteTrack, and Roboflow Inference. This comprehensive tutorial covers object detection, multi-object tracking, filtering detections, perspective transformation, speed estimation, visualization improvements, and more.</p>
## 💜 built with supervision
+2 -2
View File
@@ -19,13 +19,13 @@ as an extra within the Supervision package.
```
<div class="md-typeset">
<h2>download_assets</h2>
<h2><a href="#supervision.assets.downloader.download_assets.download_assets">download_assets</a></h2>
</div>
:::supervision.assets.downloader.download_assets
<div class="md-typeset">
<h2>VideoAssets</h2>
<h2><a href="#supervision.assets.downloader.download_assets.VideoAssets">VideoAssets</a></h2>
</div>
:::supervision.assets.list.VideoAssets
+145
View File
@@ -1,3 +1,148 @@
### 0.21.0 <small>Jun 5, 2024</small>
- Added [#500](https://github.com/roboflow/supervision/pull/500): [`sv.Detections.with_nmm`](https://supervision.roboflow.com/develop/detection/core/#supervision.detection.core.Detections.with_nmm) to perform non-maximum merging on the current set of object detections.
- Added [#1221](https://github.com/roboflow/supervision/pull/1221): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/develop/detection/core/#supervision.detection.core.Detections.from_lmm) allowing to parse Large Multimodal Model (LMM) text result into [`sv.Detections`](https://supervision.roboflow.com/develop/detection/core/) object. For now `from_lmm` supports only [PaliGemma](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-paligemma-on-detection-dataset.ipynb) result parsing.
```python
import supervision as sv
paligemma_result = "<loc0256><loc0256><loc0768><loc0768> cat"
detections = sv.Detections.from_lmm(
sv.LMM.PALIGEMMA,
paligemma_result,
resolution_wh=(1000, 1000),
classes=['cat', 'dog']
)
detections.xyxy
# array([[250., 250., 750., 750.]])
detections.class_id
# array([0])
```
- Added [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.VertexLabelAnnotator`](https://supervision.roboflow.com/develop/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator.annotate) allowing to annotate every vertex of a keypoint skeleton with custom text and color.
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
edge_annotator = sv.EdgeAnnotator(
color=sv.Color.GREEN,
thickness=5
)
annotated_frame = edge_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
- Added [#1147](https://github.com/roboflow/supervision/pull/1147): [`sv.KeyPoints.from_inference`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints.from_inference) allowing to create [`sv.KeyPoints`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints) from [Inference](https://github.com/roboflow/inference) result.
- Added [#1138](https://github.com/roboflow/supervision/pull/1138): [`sv.KeyPoints.from_yolo_nas`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints.from_yolo_nas) allowing to create [`sv.KeyPoints`](https://supervision.roboflow.com/develop/keypoint/core/#supervision.keypoint.core.KeyPoints) from [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) result.
- Added [#1163](https://github.com/roboflow/supervision/pull/1163): [`sv.mask_to_rle`](https://supervision.roboflow.com/develop/datasets/utils/#supervision.dataset.utils.rle_to_mask) and [`sv.rle_to_mask`](https://supervision.roboflow.com/develop/datasets/utils/#supervision.dataset.utils.rle_to_mask) allowing for easy conversion between mask and rle formats.
- Changed [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.InferenceSlicer`](https://supervision.roboflow.com/develop/detection/tools/inference_slicer/) allowing to select overlap filtering strategy (`NONE`, `NON_MAX_SUPPRESSION` and `NON_MAX_MERGE`).
- Changed [#1178](https://github.com/roboflow/supervision/pull/1178): [`sv.InferenceSlicer`](https://supervision.roboflow.com/develop/detection/tools/inference_slicer/) adding instance segmentation model support.
```python
import cv2
import numpy as np
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8x-seg-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
- Changed [#1228](https://github.com/roboflow/supervision/pull/1228): [`sv.LineZone`](https://supervision.roboflow.com/develop/detection/tools/line_zone/) making it 10-20 times faster, depending on the use case.
- Changed [#1163](https://github.com/roboflow/supervision/pull/1163): [`sv.DetectionDataset.from_coco`](https://supervision.roboflow.com/develop/datasets/core/#supervision.dataset.core.DetectionDataset.from_coco) and [`sv.DetectionDataset.as_coco`](https://supervision.roboflow.com/develop/datasets/core/#supervision.dataset.core.DetectionDataset.as_coco) adding support for run-length encoding (RLE) mask format.
### 0.20.0 <small>April 24, 2024</small>
- Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.KeyPoints`](/0.20.0/keypoint/core/#supervision.keypoint.core.KeyPoints) to provide initial support for pose estimation and broader keypoint detection models.
- Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.EdgeAnnotator`](/0.20.0/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator) and [`sv.VertexAnnotator`](/0.20.0/keypoint/annotators/#supervision.keypoint.annotators.VertexAnnotator) to enable rendering of results from keypoint detection models.
```python
import cv2
import supervision as sv
from ultralytics import YOLO
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8l-pose')
result = model(image, verbose=False)[0]
keypoints = sv.KeyPoints.from_ultralytics(result)
edge_annotators = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5)
annotated_image = edge_annotators.annotate(image.copy(), keypoints)
```
- Changed [#1037](https://github.com/roboflow/supervision/pull/1037): [`sv.LabelAnnotator`](/0.20.0/annotators/#supervision.annotators.core.LabelAnnotator) by adding an additional `corner_radius` argument that allows for rounding the corners of the bounding box.
- Changed [#1109](https://github.com/roboflow/supervision/pull/1109): [`sv.PolygonZone`](/0.20.0/detection/tools/polygon_zone/#supervision.detection.tools.polygon_zone.PolygonZone) such that the `frame_resolution_wh` argument is no longer required to initialize `sv.PolygonZone`.
!!! failure "Deprecated"
The `frame_resolution_wh` parameter in `sv.PolygonZone` is deprecated and will be removed in `supervision-0.24.0`.
- Changed [#1084](https://github.com/roboflow/supervision/pull/1084): [`sv.get_polygon_center`](/0.20.0/utils/geometry/#supervision.geometry.core.utils.get_polygon_center) to calculate a more accurate polygon centroid.
- Changed [#1069](https://github.com/roboflow/supervision/pull/1069): [`sv.Detections.from_transformers`](/0.20.0/detection/core/#supervision.detection.core.Detections.from_transformers) by adding support for Transformers segmentation models and extract class names values.
```python
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForSegmentation
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50-panoptic")
model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50-panoptic")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_segmentation(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(results, id2label=model.config.id2label)
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)
```
- Fixed [#787](https://github.com/roboflow/supervision/pull/787): [`sv.ByteTrack.update_with_detections`](/0.20.0/trackers/#supervision.tracker.byte_tracker.core.ByteTrack.update_with_detections) which was removing segmentation masks while tracking. Now, `ByteTrack` can be used alongside segmentation models.
### 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.
-1
View File
@@ -1,7 +1,6 @@
---
template: cookbooks.html
comments: true
status: new
hide:
- navigation
- toc
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# Datasets
+18
View File
@@ -0,0 +1,18 @@
---
comments: true
status: new
---
# Datasets Utils
<div class="md-typeset">
<h2><a href="#supervision.dataset.utils.rle_to_mask">rle_to_mask</a></h2>
</div>
:::supervision.dataset.utils.rle_to_mask
<div class="md-typeset">
<h2><a href="#supervision.dataset.utils.mask_to_rle">mask_to_rle</a></h2>
</div>
:::supervision.dataset.utils.mask_to_rle
+2 -1
View File
@@ -12,7 +12,8 @@ These features are phased out due to better alternatives or potential issues in
- 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.
- `BoxAnnotator` is deprecated and will be removed in `supervision-0.22.0`. Use [`BoundingBoxAnnotator`](detection/annotators.md/#supervision.annotators.core.BoundingBoxAnnotator) and [`LabelAnnotator`](detection/annotators.md/#supervision.annotators.core.LabelAnnotator) instead.
- The method [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) is deprecated and will be removed in `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead.
- The `track_buffer`, `track_thresh`, and `match_thresh` parameters in [`ByterTrack`](trackers.md/#supervision.tracker.byte_tracker.core.ByteTrack) are deprecated and will be removed in `supervision-0.23.0`. Use `lost_track_buffer,` `track_activation_threshold`, and `minimum_matching_threshold` instead.
- The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.23.0`. Use `triggering_anchors ` instead.
- The `frame_resolution_wh ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.24.0`.
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Annotators
@@ -260,15 +259,53 @@ 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
)
```
<div class="result" markdown>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
</div>
=== "RichLabel"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence
in zip(detections['class_name'], detections.confidence)
]
rich_label_annotator = sv.RichLabelAnnotator(
font_path=".../font.ttf",
text_position=sv.Position.CENTER
)
annotated_frame = label_annotator.annotate(
scene=image.copy(),
detections=detections,
labels=labels
)
```
@@ -281,7 +318,7 @@ status: new
=== "Crop"
```python
import supervision as sv
import supervision as sv
image = ...
detections = sv.Detections(...)
@@ -485,6 +522,12 @@ status: new
:::supervision.annotators.core.LabelAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.RichLabelAnnotator">RichLabelAnnotator</a></h2>
</div>
:::supervision.annotators.core.RichLabelAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.BlurAnnotator">BlurAnnotator</a></h2>
</div>
+30
View File
@@ -0,0 +1,30 @@
---
comments: true
status: new
---
# Double Detection Filter
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.OverlapFilter">OverlapFilter</a></h2>
</div>
:::supervision.detection.overlap_filter.OverlapFilter
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.box_non_max_suppression">box_non_max_suppression</a></h2>
</div>
:::supervision.detection.overlap_filter.box_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.mask_non_max_suppression">mask_non_max_suppression</a></h2>
</div>
:::supervision.detection.overlap_filter.mask_non_max_suppression
<div class="md-typeset">
<h2><a href="#supervision.detection.overlap_filter.box_non_max_merge">box_non_max_merge</a></h2>
</div>
:::supervision.detection.overlap_filter.box_non_max_merge
+17
View File
@@ -0,0 +1,17 @@
---
comments: true
---
# Metrics
<div class="md-typeset">
<h2><a href="#supervision.metrics.detection.ConfusionMatrix">ConfusionMatrix</a></h2>
</div>
:::supervision.metrics.detection.ConfusionMatrix
<div class="md-typeset">
<h2><a href="#supervision.metrics.detection.MeanAveragePrecision">MeanAveragePrecision</a></h2>
</div>
:::supervision.metrics.detection.MeanAveragePrecision
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# InferenceSlicer
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
<div class="md-typeset">
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Save Detections
+39 -22
View File
@@ -1,72 +1,89 @@
---
comments: true
status: new
---
# Detection Utils
<div class="md-typeset">
<h2>box_iou_batch</h2>
<h2><a href="#supervision.detection.utils.box_iou_batch">box_iou_batch</a></h2>
</div>
:::supervision.detection.utils.box_iou_batch
<div class="md-typeset">
<h2>mask_iou_batch</h2>
<h2><a href="#supervision.detection.utils.mask_iou_batch">mask_iou_batch</a></h2>
</div>
:::supervision.detection.utils.mask_iou_batch
<div class="md-typeset">
<h2>box_non_max_suppression</h2>
</div>
:::supervision.detection.utils.box_non_max_suppression
<div class="md-typeset">
<h2>mask_non_max_suppression</h2>
</div>
:::supervision.detection.utils.mask_non_max_suppression
<div class="md-typeset">
<h2>polygon_to_mask</h2>
<h2><a href="#supervision.detection.utils.polygon_to_mask">polygon_to_mask</a></h2>
</div>
:::supervision.detection.utils.polygon_to_mask
<div class="md-typeset">
<h2>mask_to_xyxy</h2>
<h2><a href="#supervision.detection.utils.mask_to_xyxy">mask_to_xyxy</a></h2>
</div>
:::supervision.detection.utils.mask_to_xyxy
<div class="md-typeset">
<h2>mask_to_polygons</h2>
<h2><a href="#supervision.detection.utils.mask_to_polygons">mask_to_polygons</a></h2>
</div>
:::supervision.detection.utils.mask_to_polygons
<div class="md-typeset">
<h2>polygon_to_xyxy</h2>
<h2><a href="#supervision.detection.utils.polygon_to_xyxy">polygon_to_xyxy</a></h2>
</div>
:::supervision.detection.utils.polygon_to_xyxy
<div class="md-typeset">
<h2>filter_polygons_by_area</h2>
<h2><a href="#supervision.detection.utils.filter_polygons_by_area">filter_polygons_by_area</a></h2>
</div>
:::supervision.detection.utils.filter_polygons_by_area
<div class="md-typeset">
<h2>move_boxes</h2>
<h2><a href="#supervision.detection.utils.move_boxes">move_boxes</a></h2>
</div>
:::supervision.detection.utils.move_boxes
<div class="md-typeset">
<h2>scale_boxes</h2>
<h2><a href="#supervision.detection.utils.move_masks">move_masks</a></h2>
</div>
:::supervision.detection.utils.move_masks
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.scale_boxes">scale_boxes</a></h2>
</div>
:::supervision.detection.utils.scale_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.clip_boxes">clip_boxes</a></h2>
</div>
:::supervision.detection.utils.clip_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.pad_boxes">pad_boxes</a></h2>
</div>
:::supervision.detection.utils.pad_boxes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.contains_holes">contains_holes</a></h2>
</div>
:::supervision.detection.utils.contains_holes
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.contains_multiple_segments">contains_multiple_segments</a></h2>
</div>
:::supervision.detection.utils.contains_multiple_segments
-13
View File
@@ -1,13 +0,0 @@
---
comments: true
---
# Color
:::supervision.draw.color.Color
<div class="md-typeset">
<h2>ColorPalette</h2>
</div>
:::supervision.draw.color.ColorPalette
-53
View File
@@ -1,53 +0,0 @@
---
comments: true
---
# Draw Utils
<div class="md-typeset">
<h2>draw_line</h2>
</div>
:::supervision.draw.utils.draw_line
<div class="md-typeset">
<h2>draw_rectangle</h2>
</div>
:::supervision.draw.utils.draw_rectangle
<div class="md-typeset">
<h2>draw_filled_rectangle</h2>
</div>
:::supervision.draw.utils.draw_filled_rectangle
<div class="md-typeset">
<h2>draw_polygon</h2>
</div>
:::supervision.draw.utils.draw_polygon
<div class="md-typeset">
<h2>draw_text</h2>
</div>
:::supervision.draw.utils.draw_text
<div class="md-typeset">
<h2>draw_image</h2>
</div>
:::supervision.draw.utils.draw_image
<div class="md-typeset">
<h2>calculate_dynamic_font_scale</h2>
</div>
:::supervision.draw.utils.calculate_dynamic_text_scale
<div class="md-typeset">
<h2>calculate_dynamic_line_thickness</h2>
</div>
:::supervision.draw.utils.calculate_dynamic_line_thickness
-7
View File
@@ -1,7 +0,0 @@
---
comments: true
---
# Position
:::supervision.geometry.core.Position
+340 -75
View File
@@ -4,17 +4,31 @@ comments: true
# 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.
## Run Inference
![basic-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_1.png)
First, you'll need to obtain predictions from your object detection or segmentation model.
## Run Detection
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(<SOURCE_IMAGE_PATH>)
results = model.infer(image)[0]
```
=== "Ultralytics"
@@ -23,105 +37,115 @@ First, you'll need to obtain predictions from your object detection or segmentat
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread(<PATH TO IMAGE>)
image = cv2.imread(<SOURCE_IMAGE_PATH>)
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(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
```
## 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>
image = cv2.imread(<PATH TO IMAGE>)
model = get_model(model_id="yolov8n-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
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
model = YOLO("yolov8n.pt")
image = cv2.imread(<PATH TO IMAGE>)
image = cv2.imread(<SOURCE_IMAGE_PATH>)
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-21" }
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
transformers_results=results,
id2label=model.config.id2label)
```
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>
image = cv2.imread(<PATH TO IMAGE>)
model = get_model(model_id="yolov8n-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
@@ -134,12 +158,253 @@ 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(<SOURCE_IMAGE_PATH>)
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="23-30" }
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
transformers_results=results,
id2label=model.config.id2label)
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(<SOURCE_IMAGE_PATH>)
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(<SOURCE_IMAGE_PATH>)
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="26-30 35" }
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
transformers_results=results,
id2label=model.config.id2label)
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)
```
![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(<SOURCE_IMAGE_PATH>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER_OF_MASS)
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(<SOURCE_IMAGE_PATH>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER_OF_MASS)
annotated_image = mask_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, DetrForSegmentation
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50-panoptic")
model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50-panoptic")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_segmentation(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
transformers_results=results,
id2label=model.config.id2label)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER_OF_MASS)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence
in zip(detections['class_name'], detections.confidence)
]
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
![segmentation-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_3.png)
+326
View File
@@ -0,0 +1,326 @@
---
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(<SOURCE_IMAGE_PATH>)
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(<SOURCE_IMAGE_PATH>)
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, DetrForSegmentation
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image_slice.size
target_size = torch.tensor([[width, height]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
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(<SOURCE_IMAGE_PATH>)
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(<SOURCE_IMAGE_PATH>)
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(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
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(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
result = model(image_slice)[0]
return sv.Detections.from_ultralytics(result)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
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(<SOURCE_IMAGE_PATH>)
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_slice.size
target_size = torch.tensor([[width, height]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
return sv.Detections.from_transformers(results)
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)
## Small Object Segmentation
[`InferenceSlicer`](/latest/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer) can perform segmentation tasks too.
=== "Inference"
```{ .py hl_lines="6 16 19-20" }
import cv2
import numpy as np
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8x-seg-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
=== "Ultralytics"
```{ .py hl_lines="6 16 19-20" }
import cv2
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x-seg.pt")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
def callback(image_slice: np.ndarray) -> sv.Detections:
result = model(image_slice)[0]
return sv.Detections.from_ultralytics(result)
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
```
![detection-with-inference-slicer](https://media.roboflow.com/supervision-docs/inference-slicer-segmentation-example.png)
+298
View File
@@ -0,0 +1,298 @@
---
comments: true
---
# Save Detections
Supervision enables an easy way to save detections in .CSV and .JSON files for offline
processing. This guide demonstrates how to perform video inference using the
[Inference](https://github.com/roboflow/inference),
[Ultralytics](https://github.com/ultralytics/ultralytics) or
[Transformers](https://github.com/huggingface/transformers) packages and save their results with
[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) and
[`sv.JSONSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink).
## Run Detection
First, you'll need to obtain predictions from your object detection or segmentation
model. You can learn more on this topic in our
[How to Detect and Annotate](/latest/how_to/detect_and_annotate.md) guide.
=== "Inference"
```python
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8n-640")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
for frame in frames_generator:
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
```
=== "Ultralytics"
```python
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
for frame in frames_generator:
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
```
=== "Transformers"
```python
import torch
import supervision as sv
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
for frame in frames_generator:
frame = sv.cv2_to_pillow(frame)
inputs = processor(images=frame, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = frame.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)
```
## Save Detections as CSV
To save detections to a `.CSV` file, open our
[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink)
and then pass the
[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections)
object resulting from the inference to it. Its fields are parsed and saved on disk.
=== "Inference"
```{ .py hl_lines="7 12" }
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8n-640")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.CSVSink(<TARGET_CSV_PATH>) as sink:
for frame in frames_generator:
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
sink.append(detections, {})
```
=== "Ultralytics"
```{ .py hl_lines="7 12" }
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.CSVSink(<TARGET_CSV_PATH>) as sink:
for frame in frames_generator:
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
sink.append(detections, {})
```
=== "Transformers"
```{ .py hl_lines="9 23" }
import torch
import supervision as sv
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.CSVSink(<TARGET_CSV_PATH>) as sink:
for frame in frames_generator:
frame = sv.cv2_to_pillow(frame)
inputs = processor(images=frame, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = frame.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)
sink.append(detections, {})
```
| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name |
|---------|----------|---------|----------|----------|------------|------------|------------|
| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car |
| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck |
| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car |
## Custom Fields
Besides regular fields in
[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections),
[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink)
also allows you to add custom information to each row, which can be passed via the
`custom_data` dictionary. Let's utilize this feature to save information about the
frame index from which the detections originate.
=== "Inference"
```{ .py hl_lines="8 12" }
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8n-640")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.CSVSink(<TARGET_CSV_PATH>) as sink:
for frame_index, frame in enumerate(frames_generator):
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
sink.append(detections, {"frame_index": frame_index})
```
=== "Ultralytics"
```{ .py hl_lines="8 12" }
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.CSVSink(<TARGET_CSV_PATH>) as sink:
for frame_index, frame in enumerate(frames_generator):
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
sink.append(detections, {"frame_index": frame_index})
```
=== "Transformers"
```{ .py hl_lines="10 23" }
import torch
import supervision as sv
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.CSVSink(<TARGET_CSV_PATH>) as sink:
for frame_index, frame in enumerate(frames_generator):
frame = sv.cv2_to_pillow(frame)
inputs = processor(images=frame, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = frame.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)
sink.append(detections, {"frame_index": frame_index})
```
| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name | frame_index |
|---------|----------|---------|----------|----------|------------|------------|------------|-------------|
| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car | 0 |
| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck | 0 |
| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car | 0 |
## Save Detections as JSON
If you prefer to save the result in a `.JSON` file instead of a `.CSV` file, all you
need to do is replace
[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink)
with
[`sv.JSONSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink).
=== "Inference"
```{ .py hl_lines="7" }
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8n-640")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.JSONSink(<TARGET_CSV_PATH>) as sink:
for frame_index, frame in enumerate(frames_generator):
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
sink.append(detections, {"frame_index": frame_index})
```
=== "Ultralytics"
```{ .py hl_lines="7" }
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.JSONSink(<TARGET_CSV_PATH>) as sink:
for frame_index, frame in enumerate(frames_generator):
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
sink.append(detections, {"frame_index": frame_index})
```
=== "Transformers"
```{ .py hl_lines="9" }
import torch
import supervision as sv
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with sv.JSONSink(<TARGET_CSV_PATH>) as sink:
for frame_index, frame in enumerate(frames_generator):
frame = sv.cv2_to_pillow(frame)
inputs = processor(images=frame, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = frame.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)
sink.append(detections, {"frame_index": frame_index})
```
+27 -2
View File
@@ -30,7 +30,7 @@ We write your reusable computer vision tools. Whether you need to load your data
## 💻 Install
You can install `supervision` with pip in a
You can install `supervision` in a
[**Python>=3.8**](https://www.python.org/) environment.
!!! example "pip install (recommended)"
@@ -49,6 +49,24 @@ You can install `supervision` with pip in a
pip install "supervision[desktop]"
```
!!! example "conda/mamba install"
=== "conda"
[![conda-recipe](https://img.shields.io/badge/recipe-supervision-green.svg)](https://anaconda.org/conda-forge/supervision) [![conda-downloads](https://img.shields.io/conda/dn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![conda-version](https://img.shields.io/conda/vn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![conda-platforms](https://img.shields.io/conda/pn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision)
```bash
conda install -c conda-forge supervision
```
=== "mamba"
[![mamba-recipe](https://img.shields.io/badge/recipe-supervision-green.svg)](https://anaconda.org/conda-forge/supervision) [![mamba-downloads](https://img.shields.io/conda/dn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![mamba-version](https://img.shields.io/conda/vn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision) [![mamba-platforms](https://img.shields.io/conda/pn/conda-forge/supervision.svg)](https://anaconda.org/conda-forge/supervision)
```bash
mamba install -c conda-forge supervision
```
!!! example "git clone (for development)"
=== "virtualenv"
@@ -88,7 +106,6 @@ You can install `supervision` with pip in a
poetry install --extras "desktop"
```
## 🚀 Quickstart
<div class="grid cards" markdown>
@@ -109,6 +126,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__
---
+97
View File
@@ -0,0 +1,97 @@
---
comments: true
status: new
---
# Annotators
=== "VertexAnnotator"
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
vertex_annotator = sv.VertexAnnotator(
color=sv.Color.GREEN,
radius=10
)
annotated_frame = vertex_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
<div class="result" markdown>
![vertex-annotator-example](https://media.roboflow.com/supervision-annotator-examples/vertex-annotator-example.png){ align=center width="800" }
</div>
=== "EdgeAnnotator"
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
edge_annotator = sv.EdgeAnnotator(
color=sv.Color.GREEN,
thickness=5
)
annotated_frame = edge_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
<div class="result" markdown>
![edge-annotator-example](https://media.roboflow.com/supervision-annotator-examples/edge-annotator-example.png){ align=center width="800" }
</div>
=== "VertexLabelAnnotator"
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
vertex_label_annotator = sv.VertexLabelAnnotator(
color=sv.Color.GREEN,
text_color=sv.Color.BLACK,
border_radius=5
)
annotated_frame = vertex_label_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
<div class="result" markdown>
![vertex-label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/vertex-label-annotator-example.png){ align=center width="800" }
</div>
<div class="md-typeset">
<h2><a href="#supervision.keypoint.annotators.VertexAnnotator">VertexAnnotator</a></h2>
</div>
:::supervision.keypoint.annotators.VertexAnnotator
<div class="md-typeset">
<h2><a href="#supervision.keypoint.annotators.EdgeAnnotator">EdgeAnnotator</a></h2>
</div>
:::supervision.keypoint.annotators.EdgeAnnotator
<div class="md-typeset">
<h2><a href="#supervision.keypoint.annotators.VertexLabelAnnotator">VertexLabelAnnotator</a></h2>
</div>
:::supervision.keypoint.annotators.VertexLabelAnnotator
+8
View File
@@ -0,0 +1,8 @@
---
comments: true
status: new
---
# Keypoint Detection
:::supervision.keypoint.core.KeyPoints
-22
View File
@@ -1,22 +0,0 @@
---
comments: true
---
#  Detection Metrics
!!! warning
Evaluation API is still fluid and may change. If you use Evaluation API in your project until further notice, freeze the
`supervision` version in your `requirements.txt` or `setup.py`.
<div class="md-typeset">
<h2><a href="#supervision.metrics.detection.ConfusionMatrix">ConfusionMatrix</a></h2>
</div>
:::supervision.metrics.detection.ConfusionMatrix
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.MeanAveragePrecision">MeanAveragePrecision</a></h2>
</div>
:::supervision.metrics.detection.MeanAveragePrecision
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# ByteTrack
+65
View File
@@ -0,0 +1,65 @@
---
comments: true
---
# Draw Utils
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_line">draw_line</a></h2>
</div>
:::supervision.draw.utils.draw_line
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_rectangle">draw_rectangle</a></h2>
</div>
:::supervision.draw.utils.draw_rectangle
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_filled_rectangle">draw_filled_rectangle</a></h2>
</div>
:::supervision.draw.utils.draw_filled_rectangle
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_polygon">draw_polygon</a></h2>
</div>
:::supervision.draw.utils.draw_polygon
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_text">draw_text</a></h2>
</div>
:::supervision.draw.utils.draw_text
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.draw_image">draw_image</a></h2>
</div>
:::supervision.draw.utils.draw_image
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.calculate_optimal_text_scale">calculate_optimal_text_scale</a></h2>
</div>
:::supervision.draw.utils.calculate_optimal_text_scale
<div class="md-typeset">
<h2><a href="#supervision.draw.utils.calculate_optimal_line_thickness">calculate_optimal_line_thickness</a></h2>
</div>
:::supervision.draw.utils.calculate_optimal_line_thickness
<div class="md-typeset">
<h2><a href="#supervision.draw.color.Color">Color</a></h2>
</div>
:::supervision.draw.color.Color
<div class="md-typeset">
<h2><a href="#supervision.draw.color.ColorPalette">ColorPalette</a></h2>
</div>
:::supervision.draw.color.ColorPalette
+1 -1
View File
@@ -5,7 +5,7 @@ comments: true
# File Utils
<div class="md-typeset">
<h2>list_files_with_extensions</h2>
<h2><a href="#supervision.utils.file.list_files_with_extensions">list_files_with_extensions</a></h2>
</div>
:::supervision.utils.file.list_files_with_extensions
+15
View File
@@ -0,0 +1,15 @@
---
comments: true
---
<div class="md-typeset">
<h2><a href="#supervision.geometry.core.utils.get_polygon_center">get_polygon_center</a></h2>
</div>
:::supervision.geometry.utils.get_polygon_center
<div class="md-typeset">
<h2><a href="#supervision.geometry.core.Position">Position</a></h2>
</div>
:::supervision.geometry.core.Position
+22 -11
View File
@@ -1,30 +1,41 @@
---
comments: true
status: new
---
# Image Utils
<div class="md-typeset">
<h2>ImageSink</h2>
</div>
:::supervision.utils.image.ImageSink
<div class="md-typeset">
<h2>crop_image</h2>
<h2><a href="#supervision.utils.image.crop_image">crop_image</a></h2>
</div>
:::supervision.utils.image.crop_image
<div class="md-typeset">
<h2>resize_image</h2>
<h2><a href="#supervision.utils.image.scale_image">scale_image</a></h2>
</div>
:::supervision.utils.image.scale_image
<div class="md-typeset">
<h2><a href="#supervision.utils.image.resize_image">resize_image</a></h2>
</div>
:::supervision.utils.image.resize_image
<div class="md-typeset">
<h2>place_image</h2>
<h2><a href="#supervision.utils.image.letterbox_image">letterbox_image</a></h2>
</div>
:::supervision.utils.image.place_image
:::supervision.utils.image.letterbox_image
<div class="md-typeset">
<h2><a href="#supervision.utils.image.overlay_image">overlay_image</a></h2>
</div>
:::supervision.utils.image.overlay_image
<div class="md-typeset">
<h2><a href="#supervision.utils.image.ImageSink">ImageSink</a></h2>
</div>
:::supervision.utils.image.ImageSink
+17
View File
@@ -0,0 +1,17 @@
---
comments: true
---
# Iterables Utils
<div class="md-typeset">
<h2><a href="#supervision.utils.iterables.create_batches">create_batches</a></h2>
</div>
:::supervision.utils.iterables.create_batches
<div class="md-typeset">
<h2><a href="#supervision.utils.iterables.fill">fill</a></h2>
</div>
:::supervision.utils.iterables.fill
+2 -3
View File
@@ -5,14 +5,13 @@ comments: true
# Notebooks Utils
<div class="md-typeset">
<h2>plot_image</h2>
<h2><a href="#supervision.utils.notebook.plot_image">plot_image</a></h2>
</div>
:::supervision.utils.notebook.plot_image
<div class="md-typeset">
<h2>## plot_images_grid
</h2>
<h2><a href="#supervision.utils.notebook.plot_images_grid">plot_images_grid</a></h2>
</div>
:::supervision.utils.notebook.plot_images_grid
+5 -5
View File
@@ -5,31 +5,31 @@ comments: true
# Video Utils
<div class="md-typeset">
<h2>VideoInfo</h2>
<h2><a href="#supervision.utils.video.VideoInfo">VideoInfo</a></h2>
</div>
:::supervision.utils.video.VideoInfo
<div class="md-typeset">
<h2>VideoSink</h2>
<h2><a href="#supervision.utils.video.VideoSink">VideoSink</a></h2>
</div>
:::supervision.utils.video.VideoSink
<div class="md-typeset">
<h2>FPSMonitor</h2>
<h2><a href="#supervision.utils.video.FPSMonitor">FPSMonitor</a></h2>
</div>
:::supervision.utils.video.FPSMonitor
<div class="md-typeset">
<h2>get_video_frames_generator</h2>
<h2><a href="#supervision.utils.video.get_video_frames_generator">get_video_frames_generator</a></h2>
</div>
:::supervision.utils.video.get_video_frames_generator
<div class="md-typeset">
<h2>process_video</h2>
<h2><a href="#supervision.utils.video.process_video">process_video</a></h2>
</div>
:::supervision.utils.video.process_video
+4 -23
View File
@@ -1,31 +1,12 @@
# Examples
This repository is packed with real-world use-cases, provided through Python scripts or
interactive notebooks. Browse through to understand how the Supervision library
interfaces with diverse applications.
Here, you'll find end-to-end examples that show how to solve common computer vision problems using Supervision.
For more information and examples, visit our [documentation](https://supervision.roboflow.com/develop/annotators/) and explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/) and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/). Join our [Discord](https://discord.com/invite/GbfgXGJ8Bk) and meet other Supervision power users!
- [tracking](./tracking) by [@SkalskiP](https://github.com/SkalskiP)
- [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
We welcome contributions from the community in the form of examples, applications, and
guides. To contribute, please follow these steps:
1. Create a pull request (PR) with the `[Example]` prefix in the title, adding your
project folder to the `examples/` directory in the repository.
2. Confirm your project aligns with the following standards:
- Incorporates the `supervision` package.
- Provides a `README.md` file, detailing the instructions to execute the project.
- Showcases visual results, demonstrating the app's functionality.
- Avoids adding large assets or dependencies unless absolutely necessary.
- The contributor is expected to provide support for issues related to their
examples.
- In case the presented model has licensing complications, kindly specify them to
circumvent potential misunderstandings.
For inquiries or concerns about these prerequisites, feel free to raise a PR. We are
committed to assist and guide you.
+3
View File
@@ -1,5 +1,8 @@
# count people in zone
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-detect-and-count-objects-in-polygon-zone.ipynb)
[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://www.youtube.com/watch?v=l_kf9CfZ_8M)
## 👋 hello
This demo is a video analysis tool that counts and highlights objects in specific zones
@@ -38,15 +38,15 @@ def initiate_annotators(
) -> Tuple[
List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator]
]:
line_thickness = sv.calculate_dynamic_line_thickness(resolution_wh=resolution_wh)
text_scale = sv.calculate_dynamic_text_scale(resolution_wh=resolution_wh)
line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh)
zones = []
zone_annotators = []
box_annotators = []
for index, polygon in enumerate(polygons):
zone = sv.PolygonZone(polygon=polygon, frame_resolution_wh=resolution_wh)
zone = sv.PolygonZone(polygon=polygon)
zone_annotator = sv.PolygonZoneAnnotator(
zone=zone,
color=COLORS.by_idx(index),
@@ -1,5 +1,5 @@
gdown
inference
supervision
inference==0.9.17
supervision>=0.20.0
tqdm
ultralytics
@@ -36,15 +36,15 @@ def initiate_annotators(
) -> Tuple[
List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator]
]:
line_thickness = sv.calculate_dynamic_line_thickness(resolution_wh=resolution_wh)
text_scale = sv.calculate_dynamic_text_scale(resolution_wh=resolution_wh)
line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh)
zones = []
zone_annotators = []
box_annotators = []
for index, polygon in enumerate(polygons):
zone = sv.PolygonZone(polygon=polygon, frame_resolution_wh=resolution_wh)
zone = sv.PolygonZone(polygon=polygon)
zone_annotator = sv.PolygonZoneAnnotator(
zone=zone,
color=COLORS.by_idx(index),
+1 -1
View File
@@ -1,2 +1,2 @@
supervision[assets]
supervision[assets]==0.19.0
ultralytics
@@ -98,10 +98,10 @@ if __name__ == "__main__":
frame_rate=video_info.fps, track_thresh=args.confidence_threshold
)
thickness = sv.calculate_dynamic_line_thickness(
thickness = sv.calculate_optimal_line_thickness(
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_dynamic_text_scale(resolution_wh=video_info.resolution_wh)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
@@ -116,9 +116,7 @@ if __name__ == "__main__":
frame_generator = sv.get_video_frames_generator(source_path=args.source_video_path)
polygon_zone = sv.PolygonZone(
polygon=SOURCE, frame_resolution_wh=video_info.resolution_wh
)
polygon_zone = sv.PolygonZone(polygon=SOURCE)
view_transformer = ViewTransformer(source=SOURCE, target=TARGET)
coordinates = defaultdict(lambda: deque(maxlen=video_info.fps))
+3 -3
View File
@@ -1,6 +1,6 @@
supervision==0.18.0rc1
tqdm==4.66.1
supervision>=0.20.0
tqdm==4.66.3
requests
ultralytics==8.0.237
super-gradients==3.5.0
inference==0.9.8
inference==0.9.17
@@ -76,10 +76,10 @@ if __name__ == "__main__":
frame_rate=video_info.fps, track_thresh=args.confidence_threshold
)
thickness = sv.calculate_dynamic_line_thickness(
thickness = sv.calculate_optimal_line_thickness(
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_dynamic_text_scale(resolution_wh=video_info.resolution_wh)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
@@ -94,9 +94,7 @@ if __name__ == "__main__":
frame_generator = sv.get_video_frames_generator(source_path=args.source_video_path)
polygon_zone = sv.PolygonZone(
polygon=SOURCE, frame_resolution_wh=video_info.resolution_wh
)
polygon_zone = sv.PolygonZone(polygon=SOURCE)
view_transformer = ViewTransformer(source=SOURCE, target=TARGET)
coordinates = defaultdict(lambda: deque(maxlen=video_info.fps))
@@ -77,10 +77,10 @@ if __name__ == "__main__":
frame_rate=video_info.fps, track_thresh=args.confidence_threshold
)
thickness = sv.calculate_dynamic_line_thickness(
thickness = sv.calculate_optimal_line_thickness(
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_dynamic_text_scale(resolution_wh=video_info.resolution_wh)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
@@ -95,9 +95,7 @@ if __name__ == "__main__":
frame_generator = sv.get_video_frames_generator(source_path=args.source_video_path)
polygon_zone = sv.PolygonZone(
polygon=SOURCE, frame_resolution_wh=video_info.resolution_wh
)
polygon_zone = sv.PolygonZone(polygon=SOURCE)
view_transformer = ViewTransformer(source=SOURCE, target=TARGET)
coordinates = defaultdict(lambda: deque(maxlen=video_info.fps))
+9
View File
@@ -0,0 +1,9 @@
data/
venv*/
*.pt
*.pth
*.mp4
*.mov
*.png
*.jpg
*.jpeg
+264
View File
@@ -0,0 +1,264 @@
# time in zone
[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://www.youtube.com/watch?v=hAWpsIuem10)
## 👋 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/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_stream_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python inference_stream_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--model_id "yolov8x-640" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
<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 ultralytics_file_example.py \
--zone_configuration_path "data/checkout/config.json" \
--source_video_path "data/checkout/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python ultralytics_file_example.py \
--zone_configuration_path "data/traffic/config.json" \
--source_video_path "data/traffic/video.mp4" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
### `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 ultralytics_stream_example.py \
--zone_configuration_path "data/checkout/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 0 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
```bash
python ultralytics_stream_example.py \
--zone_configuration_path "data/traffic/config.json" \
--rtsp_url "rtsp://localhost:8554/live0.stream" \
--weights "yolov8x.pt" \
--device "cpu" \
--classes 2 5 6 7 \
--confidence_threshold 0.3 \
--iou_threshold 0.7
```
</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,128 @@
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)
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
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,138 @@
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()
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
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,153 @@
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 = [
sv.PolygonZone(
polygon=polygon,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in self.polygons
]
def on_prediction(self, result: dict, frame: VideoFrame) -> None:
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>=0.20.0
ultralytics
inference==0.9.17
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,140 @@
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)
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
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,150 @@
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()
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
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,168 @@
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 = [
sv.PolygonZone(
polygon=polygon,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in self.polygons
]
def on_prediction(self, detections: sv.Detections, frame: VideoFrame) -> None:
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)
+2 -2
View File
@@ -1,4 +1,4 @@
inference
supervision
inference==0.9.17
supervision==0.19.0
tqdm
ultralytics
@@ -1,6 +1,6 @@
import argparse
import os
from typing import Dict, Iterable, List, Set, Tuple
from typing import Dict, Iterable, List, Set
import cv2
import numpy as np
@@ -60,13 +60,11 @@ class DetectionsManager:
def initiate_polygon_zones(
polygons: List[np.ndarray],
frame_resolution_wh: Tuple[int, int],
triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER],
) -> List[sv.PolygonZone]:
return [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=frame_resolution_wh,
triggering_anchors=triggering_anchors,
)
for polygon in polygons
@@ -92,12 +90,8 @@ class VideoProcessor:
self.tracker = sv.ByteTrack()
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]
)
self.zones_out = initiate_polygon_zones(
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER])
self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER])
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
+2 -2
View File
@@ -1,5 +1,5 @@
gdown
inference
supervision>=0.19.0rc5
inference==0.9.17
supervision>=0.20.0
tqdm
ultralytics
@@ -1,5 +1,5 @@
import argparse
from typing import Dict, Iterable, List, Set, Tuple
from typing import Dict, Iterable, List, Set
import cv2
import numpy as np
@@ -58,13 +58,11 @@ class DetectionsManager:
def initiate_polygon_zones(
polygons: List[np.ndarray],
frame_resolution_wh: Tuple[int, int],
triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER],
) -> List[sv.PolygonZone]:
return [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=frame_resolution_wh,
triggering_anchors=triggering_anchors,
)
for polygon in polygons
@@ -89,12 +87,8 @@ class VideoProcessor:
self.tracker = sv.ByteTrack()
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]
)
self.zones_out = initiate_polygon_zones(
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER])
self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER])
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
+23 -17
View File
@@ -35,18 +35,26 @@ extra_css:
nav:
- Home: index.md
- How to:
- Supervision: index.md
- Learn:
- Detect and Annotate: how_to/detect_and_annotate.md
- Track Objects: how_to/track_objects.md
- Save Detections: how_to/save_detections.md
- Filter Detections: how_to/filter_detections.md
- API:
- Annotators: annotators.md
- Classifications:
- Core: classification/core.md
- Detections:
- Detect Small Objects: how_to/detect_small_objects.md
- Track Objects on Video: how_to/track_objects.md
- Reference - Code API:
- Detection and Segmentation:
- Core: detection/core.md
- Annotators: detection/annotators.md
- Metrics: detection/metrics.md
- Double Detection Filter: detection/double_detection_filter.md
- Utils: detection/utils.md
- Keypoint Detection:
- Core: keypoint/core.md
- Annotators: keypoint/annotators.md
- Classification:
- Core: classification/core.md
- Tools:
- Line Zone: detection/tools/line_zone.md
- Polygon Zone: detection/tools/polygon_zone.md
@@ -54,26 +62,24 @@ nav:
- Detection Smoother: detection/tools/smoother.md
- Save Detections: detection/tools/save_detections.md
- Trackers: trackers.md
- Datasets: datasets.md
- Metrics:
- Object Detection: metrics/detection.md
- Draw:
- Color: draw/color.md
- Utils: draw/utils.md
- Geometry:
- Position: geometry/core.md
- Datasets:
- Core: datasets/core.md
- Utils: datasets/utils.md
- Utils:
- Video: utils/video.md
- Image: utils/image.md
- Iterables: utils/iterables.md
- Notebook: utils/notebook.md
- File: utils/file.md
- Draw: utils/draw.md
- Geometry: utils/geometry.md
- Assets: assets.md
- Cookbooks: cookbooks.md
- Contribute:
- Contributing: contributing.md
- Code of Conduct: code_of_conduct.md
- License: license.md
- Changelog:
- Release Notes:
- Changelog: changelog.md
- Deprecated: deprecated.md
Generated
+240 -234
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.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.1.1"
version = "1.2.1"
description = "A simple, correct Python build frontend"
optional = false
python-versions = ">= 3.7"
python-versions = ">=3.8"
files = [
{file = "build-1.1.1-py3-none-any.whl", hash = "sha256:8ed0851ee76e6e38adce47e4bee3b51c771d86c64cf578d0c2245567ee200e73"},
{file = "build-1.1.1.tar.gz", hash = "sha256:8eea65bb45b1aac2e734ba2cc8dad3a6d97d97901a395bd0ed3e7b46953d2a31"},
{file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"},
{file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"},
]
[package.dependencies]
colorama = {version = "*", markers = "os_name == \"nt\""}
importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""}
packaging = ">=19.0"
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]]
@@ -1121,13 +1122,13 @@ license = ["ukkonen"]
[[package]]
name = "idna"
version = "3.6"
version = "3.7"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
@@ -1252,21 +1253,21 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa
[[package]]
name = "ipywidgets"
version = "8.1.2"
version = "8.1.3"
description = "Jupyter interactive widgets"
optional = false
python-versions = ">=3.7"
files = [
{file = "ipywidgets-8.1.2-py3-none-any.whl", hash = "sha256:bbe43850d79fb5e906b14801d6c01402857996864d1e5b6fa62dd2ee35559f60"},
{file = "ipywidgets-8.1.2.tar.gz", hash = "sha256:d0b9b41e49bae926a866e613a39b0f0097745d2b9f1f3dd406641b4a57ec42c9"},
{file = "ipywidgets-8.1.3-py3-none-any.whl", hash = "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2"},
{file = "ipywidgets-8.1.3.tar.gz", hash = "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c"},
]
[package.dependencies]
comm = ">=0.1.3"
ipython = ">=6.1.0"
jupyterlab-widgets = ">=3.0.10,<3.1.0"
jupyterlab-widgets = ">=3.0.11,<3.1.0"
traitlets = ">=4.3.1"
widgetsnbextension = ">=4.0.10,<4.1.0"
widgetsnbextension = ">=4.0.11,<4.1.0"
[package.extras]
test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"]
@@ -1339,13 +1340,13 @@ trio = ["async_generator", "trio"]
[[package]]
name = "jinja2"
version = "3.1.3"
version = "3.1.4"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
]
[package.dependencies]
@@ -1565,13 +1566,13 @@ test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (>
[[package]]
name = "jupyterlab"
version = "4.1.2"
version = "4.2.0"
description = "JupyterLab computational environment"
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyterlab-4.1.2-py3-none-any.whl", hash = "sha256:aa88193f03cf4d3555f6712f04d74112b5eb85edd7d222c588c7603a26d33c5b"},
{file = "jupyterlab-4.1.2.tar.gz", hash = "sha256:5d6348b3ed4085181499f621b7dfb6eb0b1f57f3586857aadfc8e3bf4c4885f9"},
{file = "jupyterlab-4.2.0-py3-none-any.whl", hash = "sha256:0dfe9278e25a145362289c555d9beb505697d269c10e99909766af7c440ad3cc"},
{file = "jupyterlab-4.2.0.tar.gz", hash = "sha256:356e9205a6a2ab689c47c8fe4919dba6c076e376d03f26baadc05748c2435dd5"},
]
[package.dependencies]
@@ -1579,23 +1580,24 @@ async-lru = ">=1.0.0"
httpx = ">=0.25.0"
importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""}
importlib-resources = {version = ">=1.4", markers = "python_version < \"3.9\""}
ipykernel = "*"
ipykernel = ">=6.5.0"
jinja2 = ">=3.0.3"
jupyter-core = "*"
jupyter-lsp = ">=2.0.0"
jupyter-server = ">=2.4.0,<3"
jupyterlab-server = ">=2.19.0,<3"
jupyterlab-server = ">=2.27.1,<3"
notebook-shim = ">=0.2"
packaging = "*"
tomli = {version = "*", markers = "python_version < \"3.11\""}
tomli = {version = ">=1.2.2", markers = "python_version < \"3.11\""}
tornado = ">=6.2.0"
traitlets = "*"
[package.extras]
dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.2.0)"]
dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.3.5)"]
docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<7.3.0)", "sphinx-copybutton"]
docs-screenshots = ["altair (==5.2.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.1)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post6)", "matplotlib (==3.8.2)", "nbconvert (>=7.0.0)", "pandas (==2.2.0)", "scipy (==1.12.0)", "vega-datasets (==0.9.0)"]
docs-screenshots = ["altair (==5.3.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.2)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.1.post2)", "matplotlib (==3.8.3)", "nbconvert (>=7.0.0)", "pandas (==2.2.1)", "scipy (==1.12.0)", "vega-datasets (==0.9.0)"]
test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"]
upgrade-extension = ["copier (>=8,<10)", "jinja2-time (<0.3)", "pydantic (<2.0)", "pyyaml-include (<2.0)", "tomli-w (<2.0)"]
[[package]]
name = "jupyterlab-pygments"
@@ -1610,13 +1612,13 @@ files = [
[[package]]
name = "jupyterlab-server"
version = "2.25.3"
version = "2.27.1"
description = "A set of server components for JupyterLab and JupyterLab like applications."
optional = false
python-versions = ">=3.8"
files = [
{file = "jupyterlab_server-2.25.3-py3-none-any.whl", hash = "sha256:c48862519fded9b418c71645d85a49b2f0ec50d032ba8316738e9276046088c1"},
{file = "jupyterlab_server-2.25.3.tar.gz", hash = "sha256:846f125a8a19656611df5b03e5912c8393cea6900859baa64fa515eb64a8dc40"},
{file = "jupyterlab_server-2.27.1-py3-none-any.whl", hash = "sha256:f5e26156e5258b24d532c84e7c74cc212e203bff93eb856f81c24c16daeecc75"},
{file = "jupyterlab_server-2.27.1.tar.gz", hash = "sha256:097b5ac709b676c7284ac9c5e373f11930a561f52cd5a86e4fc7e5a9c8a8631d"},
]
[package.dependencies]
@@ -1632,28 +1634,28 @@ requests = ">=2.31"
[package.extras]
docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"]
openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"]
test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"]
test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0,<8)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"]
[[package]]
name = "jupyterlab-widgets"
version = "3.0.10"
version = "3.0.11"
description = "Jupyter interactive widgets for JupyterLab"
optional = false
python-versions = ">=3.7"
files = [
{file = "jupyterlab_widgets-3.0.10-py3-none-any.whl", hash = "sha256:dd61f3ae7a5a7f80299e14585ce6cf3d6925a96c9103c978eda293197730cb64"},
{file = "jupyterlab_widgets-3.0.10.tar.gz", hash = "sha256:04f2ac04976727e4f9d0fa91cdc2f1ab860f965e504c29dbd6a65c882c9d04c0"},
{file = "jupyterlab_widgets-3.0.11-py3-none-any.whl", hash = "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0"},
{file = "jupyterlab_widgets-3.0.11.tar.gz", hash = "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27"},
]
[[package]]
name = "jupytext"
version = "1.16.1"
version = "1.16.2"
description = "Jupyter notebooks as Markdown documents, Julia, Python or R scripts"
optional = false
python-versions = ">=3.8"
files = [
{file = "jupytext-1.16.1-py3-none-any.whl", hash = "sha256:796ec4f68ada663569e5d38d4ef03738a01284bfe21c943c485bc36433898bd0"},
{file = "jupytext-1.16.1.tar.gz", hash = "sha256:68c7b68685e870e80e60fda8286fbd6269e9c74dc1df4316df6fe46eabc94c99"},
{file = "jupytext-1.16.2-py3-none-any.whl", hash = "sha256:197a43fef31dca612b68b311e01b8abd54441c7e637810b16b6cb8f2ab66065e"},
{file = "jupytext-1.16.2.tar.gz", hash = "sha256:8627dd9becbbebd79cc4a4ed4727d89d78e606b4b464eab72357b3b029023a14"},
]
[package.dependencies]
@@ -1662,16 +1664,16 @@ mdit-py-plugins = "*"
nbformat = "*"
packaging = "*"
pyyaml = "*"
toml = "*"
tomli = {version = "*", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["jupytext[test-cov,test-external]"]
dev = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"]
docs = ["myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"]
test = ["pytest", "pytest-randomly", "pytest-xdist"]
test-cov = ["jupytext[test-integration]", "pytest-cov (>=2.6.1)"]
test-external = ["autopep8", "black", "flake8", "gitpython", "isort", "jupyter-fs (<0.4.0)", "jupytext[test-integration]", "pre-commit", "sphinx-gallery (<0.8)"]
test-functional = ["jupytext[test]"]
test-integration = ["ipykernel", "jupyter-server (!=2.11)", "jupytext[test-functional]", "nbconvert"]
test-cov = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist"]
test-external = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"]
test-functional = ["pytest", "pytest-randomly", "pytest-xdist"]
test-integration = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-randomly", "pytest-xdist"]
test-ui = ["calysto-bash"]
[[package]]
@@ -2047,13 +2049,13 @@ files = [
[[package]]
name = "mike"
version = "2.0.0"
version = "2.1.1"
description = "Manage multiple versions of your MkDocs-powered documentation"
optional = false
python-versions = "*"
files = [
{file = "mike-2.0.0-py3-none-any.whl", hash = "sha256:87f496a65900f93ba92d72940242b65c86f3f2f82871bc60ebdcffc91fad1d9e"},
{file = "mike-2.0.0.tar.gz", hash = "sha256:566f1cab1a58cc50b106fb79ea2f1f56e7bfc8b25a051e95e6eaee9fba0922de"},
{file = "mike-2.1.1-py3-none-any.whl", hash = "sha256:0b1d01a397a423284593eeb1b5f3194e37169488f929b860c9bfe95c0d5efb79"},
{file = "mike-2.1.1.tar.gz", hash = "sha256:f39ed39f3737da83ad0adc33e9f885092ed27f8c9e7ff0523add0480352a2c22"},
]
[package.dependencies]
@@ -2063,6 +2065,7 @@ jinja2 = ">=2.7"
mkdocs = ">=1.0"
pyparsing = ">=3.0"
pyyaml = ">=5.1"
pyyaml-env-tag = "*"
verspec = "*"
[package.extras]
@@ -2082,34 +2085,34 @@ files = [
[[package]]
name = "mkdocs"
version = "1.5.3"
version = "1.6.0"
description = "Project documentation with Markdown."
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"},
{file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"},
{file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"},
{file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"},
]
[package.dependencies]
click = ">=7.0"
colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
ghp-import = ">=1.0"
importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
jinja2 = ">=2.11.1"
markdown = ">=3.2.1"
markdown = ">=3.3.6"
markupsafe = ">=2.0.1"
mergedeep = ">=1.3.4"
mkdocs-get-deps = ">=0.2.0"
packaging = ">=20.5"
pathspec = ">=0.11.1"
platformdirs = ">=2.2.0"
pyyaml = ">=5.1"
pyyaml-env-tag = ">=0.1"
watchdog = ">=2.0"
[package.extras]
i18n = ["babel (>=2.9.0)"]
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"]
[[package]]
name = "mkdocs-autorefs"
@@ -2126,6 +2129,23 @@ files = [
Markdown = ">=3.3"
mkdocs = ">=1.1"
[[package]]
name = "mkdocs-get-deps"
version = "0.2.0"
description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file"
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"},
{file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"},
]
[package.dependencies]
importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
mergedeep = ">=1.3.4"
platformdirs = ">=2.2.0"
pyyaml = ">=5.1"
[[package]]
name = "mkdocs-git-committers-plugin-2"
version = "2.3.0"
@@ -2144,13 +2164,13 @@ requests = "*"
[[package]]
name = "mkdocs-git-revision-date-localized-plugin"
version = "1.2.4"
version = "1.2.6"
description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file."
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs-git-revision-date-localized-plugin-1.2.4.tar.gz", hash = "sha256:08fd0c6f33c8da9e00daf40f7865943113b3879a1c621b2bbf0fa794ffe997d3"},
{file = "mkdocs_git_revision_date_localized_plugin-1.2.4-py3-none-any.whl", hash = "sha256:1f94eb510862ef94e982a2910404fa17a1657ecf29f45a07b0f438c00767fc85"},
{file = "mkdocs_git_revision_date_localized_plugin-1.2.6-py3-none-any.whl", hash = "sha256:f015cb0f3894a39b33447b18e270ae391c4e25275cac5a626e80b243784e2692"},
{file = "mkdocs_git_revision_date_localized_plugin-1.2.6.tar.gz", hash = "sha256:e432942ce4ee8aa9b9f4493e993dee9d2cc08b3ea2b40a3d6b03ca0f2a4bcaa2"},
]
[package.dependencies]
@@ -2161,13 +2181,12 @@ pytz = "*"
[[package]]
name = "mkdocs-jupyter"
version = "0.24.3"
version = "0.24.7"
description = "Use Jupyter in mkdocs websites"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "mkdocs_jupyter-0.24.3-py3-none-any.whl", hash = "sha256:904262a8678a5e5920b7c3c03b5010b36301a69d0a38f2fcbf430493adf6879e"},
{file = "mkdocs_jupyter-0.24.3.tar.gz", hash = "sha256:3d81da9aea27480e93bab22438910c4f0b9630613e74f85b576590d78e0e8b14"},
{file = "mkdocs_jupyter-0.24.7-py3-none-any.whl", hash = "sha256:893d04bea1e007479a46e4e72852cd4d280c4d358ce4a0445250f3f80c639723"},
]
[package.dependencies]
@@ -2178,18 +2197,15 @@ mkdocs-material = ">9.0.0"
nbconvert = ">=7.2.9,<8"
pygments = ">2.12.0"
[package.extras]
test = ["coverage[toml]", "pymdown-extensions", "pytest", "pytest-cov"]
[[package]]
name = "mkdocs-material"
version = "9.5.13"
version = "9.5.25"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_material-9.5.13-py3-none-any.whl", hash = "sha256:5cbe17fee4e3b4980c8420a04cc762d8dc052ef1e10532abd4fce88e5ea9ce6a"},
{file = "mkdocs_material-9.5.13.tar.gz", hash = "sha256:d8e4caae576312a88fd2609b81cf43d233cdbe36860d67a68702b018b425bd87"},
{file = "mkdocs_material-9.5.25-py3-none-any.whl", hash = "sha256:68fdab047a0b9bfbefe79ce267e8a7daaf5128bcf7867065fcd201ee335fece1"},
{file = "mkdocs_material-9.5.25.tar.gz", hash = "sha256:d0662561efb725b712207e0ee01f035ca15633f29a64628e24f01ec99d7078f4"},
]
[package.dependencies]
@@ -2198,7 +2214,7 @@ cairosvg = {version = ">=2.6,<3.0", optional = true, markers = "extra == \"imagi
colorama = ">=0.4,<1.0"
jinja2 = ">=3.0,<4.0"
markdown = ">=3.2,<4.0"
mkdocs = ">=1.5.3,<1.6.0"
mkdocs = ">=1.6,<2.0"
mkdocs-material-extensions = ">=1.3,<2.0"
paginate = ">=0.5,<1.0"
pillow = {version = ">=10.2,<11.0", optional = true, markers = "extra == \"imaging\""}
@@ -2225,13 +2241,13 @@ files = [
[[package]]
name = "mkdocstrings"
version = "0.24.1"
version = "0.25.1"
description = "Automatic documentation from sources, for MkDocs."
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocstrings-0.24.1-py3-none-any.whl", hash = "sha256:b4206f9a2ca8a648e222d5a0ca1d36ba7dee53c88732818de183b536f9042b5d"},
{file = "mkdocstrings-0.24.1.tar.gz", hash = "sha256:cc83f9a1c8724fc1be3c2fa071dd73d91ce902ef6a79710249ec8d0ee1064401"},
{file = "mkdocstrings-0.25.1-py3-none-any.whl", hash = "sha256:da01fcc2670ad61888e8fe5b60afe9fee5781017d67431996832d63e887c2e51"},
{file = "mkdocstrings-0.25.1.tar.gz", hash = "sha256:c3a2515f31577f311a9ee58d089e4c51fc6046dbd9e9b4c3de4c3194667fe9bf"},
]
[package.dependencies]
@@ -2280,38 +2296,38 @@ files = [
[[package]]
name = "mypy"
version = "1.9.0"
version = "1.10.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{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"},
{file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"},
{file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"},
{file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"},
{file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"},
{file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"},
{file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"},
{file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"},
{file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"},
{file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"},
{file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"},
{file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"},
{file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"},
{file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"},
{file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"},
{file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"},
{file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"},
{file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"},
{file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"},
{file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"},
{file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"},
{file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"},
{file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"},
{file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"},
{file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"},
{file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"},
{file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"},
{file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"},
]
[package.dependencies]
@@ -2360,13 +2376,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=
[[package]]
name = "nbconvert"
version = "7.16.2"
version = "7.16.4"
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.2-py3-none-any.whl", hash = "sha256:0c01c23981a8de0220255706822c40b751438e32467d6a686e26be08ba784382"},
{file = "nbconvert-7.16.2.tar.gz", hash = "sha256:8310edd41e1c43947e4ecf16614c61469ebc024898eb808cce0999860fc9fb16"},
{file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"},
{file = "nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"},
]
[package.dependencies]
@@ -2388,12 +2404,12 @@ tinycss2 = "*"
traitlets = ">=5.1"
[package.extras]
all = ["nbconvert[docs,qtpdf,serve,test,webpdf]"]
all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"]
docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"]
qtpdf = ["nbconvert[qtpng]"]
qtpdf = ["pyqtwebengine (>=5.15)"]
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]]
@@ -2469,26 +2485,26 @@ setuptools = "*"
[[package]]
name = "notebook"
version = "7.1.2"
version = "7.2.0"
description = "Jupyter Notebook - A web-based notebook environment for interactive computing"
optional = false
python-versions = ">=3.8"
files = [
{file = "notebook-7.1.2-py3-none-any.whl", hash = "sha256:fc6c24b9aef18d0cd57157c9c47e95833b9b0bdc599652639acf0bdb61dc7d5f"},
{file = "notebook-7.1.2.tar.gz", hash = "sha256:efc2c80043909e0faa17fce9e9b37c059c03af0ec99a4d4db84cb21d9d2e936a"},
{file = "notebook-7.2.0-py3-none-any.whl", hash = "sha256:b4752d7407d6c8872fc505df0f00d3cae46e8efb033b822adacbaa3f1f3ce8f5"},
{file = "notebook-7.2.0.tar.gz", hash = "sha256:34a2ba4b08ad5d19ec930db7484fb79746a1784be9e1a5f8218f9af8656a141f"},
]
[package.dependencies]
jupyter-server = ">=2.4.0,<3"
jupyterlab = ">=4.1.1,<4.2"
jupyterlab-server = ">=2.22.1,<3"
jupyterlab = ">=4.2.0,<4.3"
jupyterlab-server = ">=2.27.1,<3"
notebook-shim = ">=0.2,<0.3"
tornado = ">=6.2.0"
[package.extras]
dev = ["hatch", "pre-commit"]
docs = ["myst-parser", "nbsphinx", "pydata-sphinx-theme", "sphinx (>=1.3.6)", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"]
test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.22.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"]
test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.27.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"]
[[package]]
name = "notebook-shim"
@@ -2692,79 +2708,80 @@ files = [
[[package]]
name = "pillow"
version = "10.2.0"
version = "10.3.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.8"
files = [
{file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"},
{file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"},
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"},
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"},
{file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"},
{file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"},
{file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"},
{file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"},
{file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"},
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"},
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"},
{file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"},
{file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"},
{file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"},
{file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"},
{file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"},
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"},
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"},
{file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"},
{file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"},
{file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"},
{file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"},
{file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"},
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"},
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"},
{file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"},
{file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"},
{file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"},
{file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"},
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"},
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"},
{file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"},
{file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"},
{file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"},
{file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"},
{file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"},
{file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"},
{file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"},
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"},
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"},
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"},
{file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"},
{file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"},
{file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"},
{file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"},
{file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"},
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"},
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"},
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"},
{file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"},
{file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"},
{file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"},
{file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"},
{file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"},
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"},
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"},
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"},
{file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"},
{file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"},
{file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"},
{file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"},
{file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"},
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"},
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"},
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"},
{file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"},
{file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"},
{file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"},
{file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"},
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"},
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"},
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"},
{file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"},
{file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"},
{file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"},
{file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"},
{file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"},
{file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"},
]
[package.extras]
@@ -2817,13 +2834,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-
[[package]]
name = "pluggy"
version = "1.4.0"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
{file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
@@ -3022,13 +3039,13 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[[package]]
name = "pytest"
version = "8.1.1"
version = "8.2.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
{file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
]
[package.dependencies]
@@ -3036,11 +3053,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.4,<2.0"
pluggy = ">=1.5,<2.0"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "python-dateutil"
@@ -3444,13 +3461,13 @@ files = [
[[package]]
name = "requests"
version = "2.31.0"
version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
@@ -3645,28 +3662,28 @@ files = [
[[package]]
name = "ruff"
version = "0.3.2"
version = "0.4.7"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01"},
{file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4"},
{file = "ruff-0.3.2-py3-none-win32.whl", hash = "sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a"},
{file = "ruff-0.3.2-py3-none-win_amd64.whl", hash = "sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037"},
{file = "ruff-0.3.2-py3-none-win_arm64.whl", hash = "sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b"},
{file = "ruff-0.3.2.tar.gz", hash = "sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142"},
{file = "ruff-0.4.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e089371c67892a73b6bb1525608e89a2aca1b77b5440acf7a71dda5dac958f9e"},
{file = "ruff-0.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:10f973d521d910e5f9c72ab27e409e839089f955be8a4c8826601a6323a89753"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c3d110970001dfa494bcd95478e62286c751126dfb15c3c46e7915fc49694f"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa9773c6c00f4958f73b317bc0fd125295110c3776089f6ef318f4b775f0abe4"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07fc80bbb61e42b3b23b10fda6a2a0f5a067f810180a3760c5ef1b456c21b9db"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fa4dafe3fe66d90e2e2b63fa1591dd6e3f090ca2128daa0be33db894e6c18648"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7c0083febdec17571455903b184a10026603a1de078428ba155e7ce9358c5f6"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad1b20e66a44057c326168437d680a2166c177c939346b19c0d6b08a62a37589"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf5d818553add7511c38b05532d94a407f499d1a76ebb0cad0374e32bc67202"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50e9651578b629baec3d1513b2534de0ac7ed7753e1382272b8d609997e27e83"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8874a9df7766cb956b218a0a239e0a5d23d9e843e4da1e113ae1d27ee420877a"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b9de9a6e49f7d529decd09381c0860c3f82fa0b0ea00ea78409b785d2308a567"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:13a1768b0691619822ae6d446132dbdfd568b700ecd3652b20d4e8bc1e498f78"},
{file = "ruff-0.4.7-py3-none-win32.whl", hash = "sha256:769e5a51df61e07e887b81e6f039e7ed3573316ab7dd9f635c5afaa310e4030e"},
{file = "ruff-0.4.7-py3-none-win_amd64.whl", hash = "sha256:9e3ab684ad403a9ed1226894c32c3ab9c2e0718440f6f50c7c5829932bc9e054"},
{file = "ruff-0.4.7-py3-none-win_arm64.whl", hash = "sha256:10f2204b9a613988e3484194c2c9e96a22079206b22b787605c255f130db5ed7"},
{file = "ruff-0.4.7.tar.gz", hash = "sha256:2331d2b051dc77a289a653fcc6a42cce357087c5975738157cd966590b18b5e1"},
]
[[package]]
@@ -3898,17 +3915,6 @@ webencodings = ">=0.4"
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "pytest"]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
[[package]]
name = "tomli"
version = "2.0.1"
@@ -3942,13 +3948,13 @@ files = [
[[package]]
name = "tox"
version = "4.14.1"
version = "4.15.0"
description = "tox is a generic virtualenv management and test command line tool"
optional = false
python-versions = ">=3.8"
files = [
{file = "tox-4.14.1-py3-none-any.whl", hash = "sha256:b03754b6ee6dadc70f2611da82b4ed8f625fcafd247e15d1d0cb056f90a06d3b"},
{file = "tox-4.14.1.tar.gz", hash = "sha256:f0ad758c3bbf7e237059c929d3595479363c3cdd5a06ac3e49d1dd020ffbee45"},
{file = "tox-4.15.0-py3-none-any.whl", hash = "sha256:300055f335d855b2ab1b12c5802de7f62a36d4fd53f30bd2835f6a201dda46ea"},
{file = "tox-4.15.0.tar.gz", hash = "sha256:7a0beeef166fbe566f54f795b4906c31b428eddafc0102ac00d20998dd1933f6"},
]
[package.dependencies]
@@ -3969,13 +3975,13 @@ testing = ["build[virtualenv] (>=1.0.3)", "covdefaults (>=2.3)", "detect-test-po
[[package]]
name = "tqdm"
version = "4.66.2"
version = "4.66.4"
description = "Fast, Extensible Progress Meter"
optional = true
python-versions = ">=3.7"
files = [
{file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"},
{file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"},
{file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"},
{file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"},
]
[package.dependencies]
@@ -4004,13 +4010,13 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,
[[package]]
name = "twine"
version = "5.0.0"
version = "5.1.0"
description = "Collection of utilities for publishing packages on PyPI"
optional = false
python-versions = ">=3.8"
files = [
{file = "twine-5.0.0-py3-none-any.whl", hash = "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0"},
{file = "twine-5.0.0.tar.gz", hash = "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4"},
{file = "twine-5.1.0-py3-none-any.whl", hash = "sha256:fe1d814395bfe50cfbe27783cb74efe93abeac3f66deaeb6c8390e4e92bacb43"},
{file = "twine-5.1.0.tar.gz", hash = "sha256:4d74770c88c4fcaf8134d2a6a9d863e40f08255ff7d8e2acb3cbbd57d25f6e9d"},
]
[package.dependencies]
@@ -4221,13 +4227,13 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"]
[[package]]
name = "widgetsnbextension"
version = "4.0.10"
version = "4.0.11"
description = "Jupyter interactive widgets for Jupyter Notebook"
optional = false
python-versions = ">=3.7"
files = [
{file = "widgetsnbextension-4.0.10-py3-none-any.whl", hash = "sha256:d37c3724ec32d8c48400a435ecfa7d3e259995201fbefa37163124a9fcb393cc"},
{file = "widgetsnbextension-4.0.10.tar.gz", hash = "sha256:64196c5ff3b9a9183a8e699a4227fb0b7002f252c814098e66c4d1cd0644688f"},
{file = "widgetsnbextension-4.0.11-py3-none-any.whl", hash = "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36"},
{file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"},
]
[[package]]
@@ -4252,4 +4258,4 @@ desktop = ["opencv-python"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "30b2e5864f20c0b0cf5ddff7658427a18e18c9d387da86725e8e9288f795086a"
content-hash = "e3d79f6c93041323b04c7b45e93bb3c4198b21889044004af8a0485a6145a207"
+5 -5
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "supervision"
version = "0.19.0"
version = "0.21.0"
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
authors = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
maintainers = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
@@ -42,8 +42,8 @@ pyyaml = ">=5.3"
defusedxml = "^0.7.1"
opencv-python = { version = ">=4.5.5.64", optional = true }
opencv-python-headless = ">=4.5.5.64"
requests = { version = ">=2.26.0,<=2.31.0", optional = true }
tqdm = { version = ">=4.62.3,<=4.66.2", optional = true }
requests = { version = ">=2.26.0,<=2.32.3", optional = true }
tqdm = { version = ">=4.62.3,<=4.66.4", optional = true }
pillow = ">=9.4"
[tool.poetry.extras]
@@ -54,7 +54,7 @@ assets = ["requests","tqdm"]
twine = ">=4.0.2,<6.0.0"
pytest = ">=7.2.2,<9.0.0"
wheel = ">=0.40,<0.44"
build = ">=0.10,<1.2"
build = ">=0.10,<1.3"
ruff = ">=0.1.0"
mypy = "^1.4.1"
pre-commit = "^3.3.3"
@@ -67,7 +67,7 @@ nbconvert = "^7.14.2"
[tool.poetry.group.docs.dependencies]
mkdocs-material = {extras = ["imaging"], version = "^9.5.5"}
mkdocstrings = {extras = ["python"], version = ">=0.20,<0.25"}
mkdocstrings = {extras = ["python"], version = ">=0.20,<0.26"}
mike = "^2.0.0"
# For Documentation Development use Python 3.10 or above
# Use Latest mkdocs-jupyter min 0.24.6 for Jupyter Notebook Theme support
+32 -5
View File
@@ -23,6 +23,7 @@ from supervision.annotators.core import (
PercentageBarAnnotator,
PixelateAnnotator,
PolygonAnnotator,
RichLabelAnnotator,
RoundBoxAnnotator,
TraceAnnotator,
TriangleAnnotator,
@@ -34,9 +35,17 @@ from supervision.dataset.core import (
ClassificationDataset,
DetectionDataset,
)
from supervision.dataset.utils import mask_to_rle, rle_to_mask
from supervision.detection.annotate import BoxAnnotator
from supervision.detection.core import Detections
from supervision.detection.line_zone import LineZone, LineZoneAnnotator
from supervision.detection.lmm import LMM
from supervision.detection.overlap_filter import (
OverlapFilter,
box_non_max_merge,
box_non_max_suppression,
mask_non_max_suppression,
)
from supervision.detection.tools.csv_sink import CSVSink
from supervision.detection.tools.inference_slicer import InferenceSlicer
from supervision.detection.tools.json_sink import JSONSink
@@ -44,22 +53,25 @@ from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnn
from supervision.detection.tools.smoother import DetectionsSmoother
from supervision.detection.utils import (
box_iou_batch,
box_non_max_suppression,
calculate_masks_centroids,
clip_boxes,
contains_holes,
contains_multiple_segments,
filter_polygons_by_area,
mask_iou_batch,
mask_non_max_suppression,
mask_to_polygons,
mask_to_xyxy,
move_boxes,
move_masks,
pad_boxes,
polygon_to_mask,
polygon_to_xyxy,
scale_boxes,
)
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,
@@ -69,10 +81,25 @@ from supervision.draw.utils import (
)
from supervision.geometry.core import Point, Position, Rect
from supervision.geometry.utils import get_polygon_center
from supervision.keypoint.annotators import (
EdgeAnnotator,
VertexAnnotator,
VertexLabelAnnotator,
)
from supervision.keypoint.core import KeyPoints
from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision
from supervision.tracker.byte_tracker.core import ByteTrack
from supervision.utils.conversion import cv2_to_pillow, pillow_to_cv2
from supervision.utils.file import list_files_with_extensions
from supervision.utils.image import ImageSink, crop_image, place_image, resize_image
from supervision.utils.image import (
ImageSink,
create_tiles,
crop_image,
letterbox_image,
overlay_image,
resize_image,
scale_image,
)
from supervision.utils.notebook import plot_image, plot_images_grid
from supervision.utils.video import (
FPSMonitor,
+317 -103
View File
@@ -3,13 +3,14 @@ from typing import List, Optional, Tuple, Union
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from supervision.annotators.base import BaseAnnotator, ImageType
from supervision.annotators.utils import (
ColorLookup,
Trace,
resolve_color,
scene_to_annotator_img_type,
resolve_text_background_xyxy,
)
from supervision.config import CLASS_NAME_DATA_FIELD, ORIENTED_BOX_COORDINATES
from supervision.detection.core import Detections
@@ -17,7 +18,8 @@ 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
from supervision.utils.conversion import convert_for_annotation_method
from supervision.utils.image import crop_image, overlay_image, scale_image
class BoundingBoxAnnotator(BaseAnnotator):
@@ -43,7 +45,7 @@ class BoundingBoxAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -124,7 +126,7 @@ class OrientedBoxAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -212,7 +214,7 @@ class MaskAnnotator(BaseAnnotator):
self.opacity = opacity
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -299,7 +301,7 @@ class PolygonAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -385,7 +387,7 @@ class ColorAnnotator(BaseAnnotator):
self.color_lookup: ColorLookup = color_lookup
self.opacity = opacity
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -479,7 +481,7 @@ class HaloAnnotator(BaseAnnotator):
self.color_lookup: ColorLookup = color_lookup
self.kernel_size: int = kernel_size
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -577,7 +579,7 @@ class EllipseAnnotator(BaseAnnotator):
self.end_angle: int = end_angle
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -668,7 +670,7 @@ class BoxCornerAnnotator(BaseAnnotator):
self.corner_length: int = corner_length
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -756,7 +758,7 @@ class CircleAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -846,7 +848,7 @@ class DotAnnotator(BaseAnnotator):
self.position: Position = position
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -914,6 +916,7 @@ class LabelAnnotator:
text_padding: int = 10,
text_position: Position = Position.TOP_LEFT,
color_lookup: ColorLookup = ColorLookup.CLASS,
border_radius: int = 0,
):
"""
Args:
@@ -927,7 +930,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
@@ -936,60 +942,7 @@ class LabelAnnotator:
self.text_anchor: Position = text_position
self.color_lookup: ColorLookup = color_lookup
@staticmethod
def resolve_text_background_xyxy(
center_coordinates: Tuple[int, int],
text_wh: Tuple[int, int],
position: Position,
) -> Tuple[int, int, int, int]:
center_x, center_y = center_coordinates
text_w, text_h = text_wh
if position == Position.TOP_LEFT:
return center_x, center_y - text_h, center_x + text_w, center_y
elif position == Position.TOP_RIGHT:
return center_x - text_w, center_y - text_h, center_x, center_y
elif position == Position.TOP_CENTER:
return (
center_x - text_w // 2,
center_y - text_h,
center_x + text_w // 2,
center_y,
)
elif position == Position.CENTER or position == Position.CENTER_OF_MASS:
return (
center_x - text_w // 2,
center_y - text_h // 2,
center_x + text_w // 2,
center_y + text_h // 2,
)
elif position == Position.BOTTOM_LEFT:
return center_x, center_y, center_x + text_w, center_y + text_h
elif position == Position.BOTTOM_RIGHT:
return center_x - text_w, center_y, center_x, center_y + text_h
elif position == Position.BOTTOM_CENTER:
return (
center_x - text_w // 2,
center_y,
center_x + text_w // 2,
center_y + text_h,
)
elif position == Position.CENTER_LEFT:
return (
center_x - text_w,
center_y - text_h // 2,
center_x,
center_y + text_h // 2,
)
elif position == Position.CENTER_RIGHT:
return (
center_x,
center_y - text_h // 2,
center_x + text_w,
center_y + text_h // 2,
)
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -1015,15 +968,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
)
```
@@ -1049,9 +1009,11 @@ class LabelAnnotator:
color=self.color,
detections=detections,
detection_idx=detection_idx,
color_lookup=self.color_lookup
if custom_color_lookup is None
else custom_color_lookup,
color_lookup=(
self.color_lookup
if custom_color_lookup is None
else custom_color_lookup
),
)
if labels is not None:
@@ -1071,7 +1033,7 @@ class LabelAnnotator:
)[0]
text_w_padded = text_w + 2 * self.text_padding
text_h_padded = text_h + 2 * self.text_padding
text_background_xyxy = self.resolve_text_background_xyxy(
text_background_xyxy = resolve_text_background_xyxy(
center_coordinates=tuple(center_coordinates),
text_wh=(text_w_padded, text_h_padded),
position=self.text_anchor,
@@ -1080,12 +1042,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,
@@ -1099,6 +1060,207 @@ 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 RichLabelAnnotator:
"""
A class for annotating labels on an image using provided detections,
with support for Unicode characters by using a custom font.
"""
def __init__(
self,
color: Union[Color, ColorPalette] = ColorPalette.DEFAULT,
text_color: Color = Color.WHITE,
font_path: str = None,
font_size: int = 10,
text_padding: int = 10,
text_position: Position = Position.TOP_LEFT,
color_lookup: ColorLookup = ColorLookup.CLASS,
border_radius: int = 0,
):
"""
Args:
color (Union[Color, ColorPalette]): The color or color palette to use for
annotating the text background.
text_color (Color): The color to use for the text.
font_path (str): Path to the font file (e.g., ".ttf" or ".otf") to use for
rendering text. If `None`, the default PIL font will be used.
font_size (int): Font size for the text.
text_padding (int): Padding around the text within its background box.
text_position (Position): Position of the text relative to the detection.
Possible values are defined in the `Position` enum.
color_lookup (ColorLookup): 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.color = color
self.text_color = text_color
self.text_padding = text_padding
self.text_anchor = text_position
self.color_lookup = color_lookup
self.border_radius = border_radius
if font_path is not None:
try:
self.font = ImageFont.truetype(font_path, font_size)
except OSError:
print(f"Font path '{font_path}' not found. Using PIL's default font.")
self.font = ImageFont.load_default(size=font_size)
else:
self.font = ImageFont.load_default(size=font_size)
def annotate(
self,
scene: ImageType,
detections: Detections,
labels: List[str] = None,
custom_color_lookup: Optional[np.ndarray] = None,
) -> ImageType:
"""
Annotates the given scene with labels based on the provided
detections, with support for Unicode characters.
Args:
scene (ImageType): The image where labels will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): Object detections to annotate.
labels (List[str]): Optional. Custom labels for each detection.
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
Allows to override the default color mapping strategy.
Returns:
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence
in zip(detections['class_name'], detections.confidence)
]
rich_label_annotator = sv.RichLabelAnnotator(font_path="path/to/font.ttf")
annotated_frame = label_annotator.annotate(
scene=image.copy(),
detections=detections,
labels=labels
)
```
"""
if isinstance(scene, np.ndarray):
scene = Image.fromarray(cv2.cvtColor(scene, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(scene)
anchors_coordinates = detections.get_anchors_coordinates(
anchor=self.text_anchor
).astype(int)
if labels is not None and len(labels) != len(detections):
raise ValueError(
f"The number of labels provided ({len(labels)}) does not match the "
f"number of detections ({len(detections)}). Each detection should have "
f"a corresponding label. This discrepancy can occur if the labels and "
f"detections are not aligned or if an incorrect number of labels has "
f"been provided. Please ensure that the labels array has the same "
f"length as the Detections object."
)
for detection_idx, center_coordinates in enumerate(anchors_coordinates):
color = resolve_color(
color=self.color,
detections=detections,
detection_idx=detection_idx,
color_lookup=(
self.color_lookup
if custom_color_lookup is None
else custom_color_lookup
),
)
if labels is not None:
text = labels[detection_idx]
elif detections[CLASS_NAME_DATA_FIELD] is not None:
text = detections[CLASS_NAME_DATA_FIELD][detection_idx]
elif detections.class_id is not None:
text = str(detections.class_id[detection_idx])
else:
text = str(detection_idx)
left, top, right, bottom = draw.textbbox((0, 0), text, font=self.font)
text_width = right - left
text_height = bottom - top
text_w_padded = text_width + 2 * self.text_padding
text_h_padded = text_height + 2 * self.text_padding
text_background_xyxy = resolve_text_background_xyxy(
center_coordinates=tuple(center_coordinates),
text_wh=(text_w_padded, text_h_padded),
position=self.text_anchor,
)
text_x = text_background_xyxy[0] + self.text_padding - left
text_y = text_background_xyxy[1] + self.text_padding - top
draw.rounded_rectangle(
text_background_xyxy,
radius=self.border_radius,
fill=color.as_rgb(),
outline=None,
)
draw.text(
xy=(text_x, text_y),
text=text,
font=self.font,
fill=self.text_color.as_rgb(),
)
return scene
class BlurAnnotator(BaseAnnotator):
"""
@@ -1112,7 +1274,7 @@ class BlurAnnotator(BaseAnnotator):
"""
self.kernel_size: int = kernel_size
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -1197,7 +1359,7 @@ class TraceAnnotator:
self.thickness = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -1304,7 +1466,7 @@ class HeatMapAnnotator:
self.top_hue = top_hue
self.low_hue = low_hue
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(self, scene: ImageType, detections: Detections) -> ImageType:
"""
Annotates the scene with a heatmap based on the provided detections.
@@ -1380,7 +1542,7 @@ class PixelateAnnotator(BaseAnnotator):
"""
self.pixel_size: int = pixel_size
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -1468,7 +1630,7 @@ class TriangleAnnotator(BaseAnnotator):
self.position: Position = position
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -1564,7 +1726,7 @@ class RoundBoxAnnotator(BaseAnnotator):
raise ValueError("roundness attribute must be float between (0, 1.0]")
self.roundness: float = roundness
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -1701,7 +1863,7 @@ class PercentageBarAnnotator(BaseAnnotator):
if border_thickness is None:
self.border_thickness = int(0.15 * self.height)
@scene_to_annotator_img_type
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
@@ -1847,7 +2009,14 @@ 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):
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
@@ -1855,16 +2024,25 @@ class CropAnnotator(BaseAnnotator):
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
@convert_for_annotation_method
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
) -> np.ndarray:
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
@@ -1873,8 +2051,12 @@ class CropAnnotator(BaseAnnotator):
Args:
scene (np.ndarray): The image where cropped detection will be placed.
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.
@@ -1897,41 +2079,73 @@ class CropAnnotator(BaseAnnotator):
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
scale_image(image=crop, scale_factor=self.scale_factor) for crop in crops
]
anchors = detections.get_anchors_coordinates(anchor=self.position).astype(int)
for resized_crop, anchor in zip(resized_crops, anchors):
for idx, (resized_crop, anchor) in enumerate(zip(resized_crops, anchors)):
crop_wh = resized_crop.shape[1], resized_crop.shape[0]
crop_anchor = self.calculate_crop_coordinates(
(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=crop_anchor)
scene = overlay_image(
scene=scene, inserted_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[int, int]:
) -> 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
return (anchor_x - width, anchor_y - height), (anchor_x, anchor_y)
elif position == Position.TOP_CENTER:
return anchor_x - width // 2, anchor_y - height
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
return (anchor_x, anchor_y - height), (anchor_x + width, anchor_y)
elif position == Position.CENTER_LEFT:
return anchor_x - width, anchor_y - height // 2
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
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
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
return (anchor_x - width, anchor_y), (anchor_x, anchor_y + height)
elif position == Position.BOTTOM_CENTER:
return anchor_x - width // 2, anchor_y
return (
(anchor_x - width // 2, anchor_y),
(anchor_x + width // 2, anchor_y + height),
)
elif position == Position.BOTTOM_RIGHT:
return anchor_x, anchor_y
return (anchor_x, anchor_y), (anchor_x + width, anchor_y + height)
+58 -39
View File
@@ -1,12 +1,8 @@
from enum import Enum
from functools import wraps
from typing import Optional, Union
from typing import Optional, Tuple, Union
import cv2
import numpy as np
from PIL import Image
from supervision.annotators.base import ImageType
from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.geometry.core import Position
@@ -38,14 +34,14 @@ def resolve_color_idx(
) -> int:
if detection_idx >= len(detections):
raise ValueError(
f"Detection index {detection_idx}"
f"Detection index {detection_idx} "
f"is out of bounds for detections of length {len(detections)}"
)
if isinstance(color_lookup, np.ndarray):
if len(color_lookup) != len(detections):
raise ValueError(
f"Length of color lookup {len(color_lookup)}"
f"Length of color lookup {len(color_lookup)} "
f"does not match length of detections {len(detections)}"
)
return color_lookup[detection_idx]
@@ -54,19 +50,72 @@ def resolve_color_idx(
elif color_lookup == ColorLookup.CLASS:
if detections.class_id is None:
raise ValueError(
"Could not resolve color by class because"
"Could not resolve color by class because "
"Detections do not have class_id"
)
return detections.class_id[detection_idx]
elif color_lookup == ColorLookup.TRACK:
if detections.tracker_id is None:
raise ValueError(
"Could not resolve color by track because"
"Could not resolve color by track because "
"Detections do not have tracker_id"
)
return detections.tracker_id[detection_idx]
def resolve_text_background_xyxy(
center_coordinates: Tuple[int, int],
text_wh: Tuple[int, int],
position: Position,
) -> Tuple[int, int, int, int]:
center_x, center_y = center_coordinates
text_w, text_h = text_wh
if position == Position.TOP_LEFT:
return center_x, center_y - text_h, center_x + text_w, center_y
elif position == Position.TOP_RIGHT:
return center_x - text_w, center_y - text_h, center_x, center_y
elif position == Position.TOP_CENTER:
return (
center_x - text_w // 2,
center_y - text_h,
center_x + text_w // 2,
center_y,
)
elif position == Position.CENTER or position == Position.CENTER_OF_MASS:
return (
center_x - text_w // 2,
center_y - text_h // 2,
center_x + text_w // 2,
center_y + text_h // 2,
)
elif position == Position.BOTTOM_LEFT:
return center_x, center_y, center_x + text_w, center_y + text_h
elif position == Position.BOTTOM_RIGHT:
return center_x - text_w, center_y, center_x, center_y + text_h
elif position == Position.BOTTOM_CENTER:
return (
center_x - text_w // 2,
center_y,
center_x + text_w // 2,
center_y + text_h,
)
elif position == Position.CENTER_LEFT:
return (
center_x - text_w,
center_y - text_h // 2,
center_x,
center_y + text_h // 2,
)
elif position == Position.CENTER_RIGHT:
return (
center_x,
center_y - text_h // 2,
center_x + text_w,
center_y + text_h // 2,
)
def get_color_by_index(color: Union[Color, ColorPalette], idx: int) -> Color:
if isinstance(color, ColorPalette):
return color.by_idx(idx)
@@ -123,33 +172,3 @@ class Trace:
def get(self, tracker_id: int) -> np.ndarray:
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
an image type used internally by the annotators, converts back when annotation
is complete.
"""
@wraps(annotate_func)
def wrapper(self, scene: ImageType, *args, **kwargs):
if isinstance(scene, np.ndarray):
return annotate_func(self, scene, *args, **kwargs)
if isinstance(scene, Image.Image):
scene = pillow_to_cv2(scene)
annotated = annotate_func(self, scene, *args, **kwargs)
annotated = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
annotated = Image.fromarray(annotated)
return annotated
raise ValueError(f"Unsupported image type: {type(scene)}")
return wrapper
+12
View File
@@ -18,6 +18,8 @@ class VideoAssets(Enum):
| `SUBWAY` | `subway.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/subway.mp4) |
| `MARKET_SQUARE` | `market-square.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/market-square.mp4) |
| `PEOPLE_WALKING` | `people-walking.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/people-walking.mp4) |
| `BEACH` | `beach-1.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/beach-1.mp4) |
| `BASKETBALL` | `basketball-1.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/basketball-1.mp4) |
""" # noqa: E501 // docs
VEHICLES = "vehicles.mp4"
@@ -27,6 +29,8 @@ class VideoAssets(Enum):
SUBWAY = "subway.mp4"
MARKET_SQUARE = "market-square.mp4"
PEOPLE_WALKING = "people-walking.mp4"
BEACH = "beach-1.mp4"
BASKETBALL = "basketball-1.mp4"
@classmethod
def list(cls):
@@ -62,4 +66,12 @@ VIDEO_ASSETS: Dict[str, Tuple[str, str]] = {
f"{BASE_VIDEO_URL}{VideoAssets.PEOPLE_WALKING.value}",
"0574c053c8686c3f1dc0aa3743e45cb9",
),
VideoAssets.BEACH.value: (
f"{BASE_VIDEO_URL}{VideoAssets.BEACH.value}",
"4175d42fec4d450ed081523fd39e0cf8",
),
VideoAssets.BASKETBALL.value: (
f"{BASE_VIDEO_URL}{VideoAssets.BASKETBALL.value}",
"60d94a3c7c47d16f09d342b088012ecc",
),
}
+23 -11
View File
@@ -116,13 +116,12 @@ class DetectionDataset(BaseDataset):
Tuple[DetectionDataset, DetectionDataset]: A tuple containing
the training and testing datasets.
Example:
Examples:
```python
import supervision as sv
ds = sv.DetectionDataset(...)
train_ds, test_ds = ds.split(split_ratio=0.7,
random_state=42, shuffle=True)
train_ds, test_ds = ds.split(split_ratio=0.7, random_state=42, shuffle=True)
len(train_ds), len(test_ds)
# (700, 300)
```
@@ -229,7 +228,7 @@ class DetectionDataset(BaseDataset):
DetectionDataset: A DetectionDataset instance containing
the loaded images and annotations.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
@@ -286,7 +285,7 @@ class DetectionDataset(BaseDataset):
DetectionDataset: A DetectionDataset instance
containing the loaded images and annotations.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
@@ -391,7 +390,7 @@ class DetectionDataset(BaseDataset):
DetectionDataset: A DetectionDataset instance containing
the loaded images and annotations.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
@@ -431,6 +430,20 @@ class DetectionDataset(BaseDataset):
Exports the dataset to COCO format. This method saves the
images and their corresponding annotations in COCO format.
!!! tip
The format of the mask is determined automatically based on its structure:
- If a mask contains multiple disconnected components or holes, it will be
saved using the Run-Length Encoding (RLE) format for efficient storage and
processing.
- If a mask consists of a single, contiguous region without any holes, it
will be encoded as a polygon, preserving the outline of the object.
This automatic selection ensures that the masks are stored in the most
appropriate and space-efficient format, complying with COCO dataset
standards.
Args:
images_directory_path (Optional[str]): The path to the directory
where the images should be saved.
@@ -482,7 +495,7 @@ class DetectionDataset(BaseDataset):
(DetectionDataset): A single `DetectionDataset` object containing
the merged data from the input list.
Example:
Examples:
```python
import supervision as sv
@@ -567,13 +580,12 @@ class ClassificationDataset(BaseDataset):
Tuple[ClassificationDataset, ClassificationDataset]: A tuple containing
the training and testing datasets.
Example:
Examples:
```python
import supervision as sv
cd = sv.ClassificationDataset(...)
train_cd,test_cd = cd.split(split_ratio=0.7,
random_state=42,shuffle=True)
train_cd,test_cd = cd.split(split_ratio=0.7, random_state=42,shuffle=True)
len(train_cd), len(test_cd)
# (700, 300)
```
@@ -635,7 +647,7 @@ class ClassificationDataset(BaseDataset):
Returns:
ClassificationDataset: The dataset.
Example:
Examples:
```python
import roboflow
from roboflow import Roboflow
+49 -24
View File
@@ -5,13 +5,20 @@ from typing import Dict, List, Tuple
import cv2
import numpy as np
import numpy.typing as npt
from supervision.dataset.utils import (
approximate_mask_with_polygons,
map_detections_class_id,
mask_to_rle,
rle_to_mask,
)
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask
from supervision.detection.utils import (
contains_holes,
contains_multiple_segments,
polygon_to_mask,
)
from supervision.utils.file import read_json_file, save_json_file
@@ -57,13 +64,24 @@ def group_coco_annotations_by_image_id(
return annotations
def _polygons_to_masks(
polygons: List[np.ndarray], resolution_wh: Tuple[int, int]
) -> np.ndarray:
def coco_annotations_to_masks(
image_annotations: List[dict], resolution_wh: Tuple[int, int]
) -> npt.NDArray[np.bool_]:
return np.array(
[
polygon_to_mask(polygon=polygon, resolution_wh=resolution_wh)
for polygon in polygons
rle_to_mask(
rle=np.array(image_annotation["segmentation"]["counts"]),
resolution_wh=resolution_wh,
)
if image_annotation["iscrowd"]
else polygon_to_mask(
polygon=np.reshape(
np.asarray(image_annotation["segmentation"], dtype=np.int32),
(-1, 2),
),
resolution_wh=resolution_wh,
)
for image_annotation in image_annotations
],
dtype=bool,
)
@@ -83,13 +101,9 @@ def coco_annotations_to_detections(
xyxy[:, 2:4] += xyxy[:, 0:2]
if with_masks:
polygons = [
np.reshape(
np.asarray(image_annotation["segmentation"], dtype=np.int32), (-1, 2)
)
for image_annotation in image_annotations
]
mask = _polygons_to_masks(polygons=polygons, resolution_wh=resolution_wh)
mask = coco_annotations_to_masks(
image_annotations=image_annotations, resolution_wh=resolution_wh
)
return Detections(
class_id=np.asarray(class_ids, dtype=int), xyxy=xyxy, mask=mask
)
@@ -108,24 +122,35 @@ def detections_to_coco_annotations(
coco_annotations = []
for xyxy, mask, _, class_id, _, _ in detections:
box_width, box_height = xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]
polygon = []
segmentation = []
iscrowd = 0
if mask is not None:
polygon = list(
approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)[0].flatten()
)
iscrowd = contains_holes(mask=mask) or contains_multiple_segments(mask=mask)
if iscrowd:
segmentation = {
"counts": mask_to_rle(mask=mask),
"size": list(mask.shape[:2]),
}
else:
segmentation = [
list(
approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)[0].flatten()
)
]
coco_annotation = {
"id": annotation_id,
"image_id": image_id,
"category_id": int(class_id),
"bbox": [xyxy[0], xyxy[1], box_width, box_height],
"area": box_width * box_height,
"segmentation": [polygon] if polygon else [],
"iscrowd": 0,
"segmentation": segmentation,
"iscrowd": iscrowd,
}
coco_annotations.append(coco_annotation)
annotation_id += 1
+122 -1
View File
@@ -2,10 +2,11 @@ import copy
import os
import random
from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypeVar
from typing import Dict, List, Optional, Tuple, TypeVar, Union
import cv2
import numpy as np
import numpy.typing as npt
from supervision.detection.core import Detections
from supervision.detection.utils import (
@@ -129,3 +130,123 @@ def train_test_split(
split_index = int(len(data) * train_ratio)
return data[:split_index], data[split_index:]
def rle_to_mask(
rle: Union[npt.NDArray[np.int_], List[int]], resolution_wh: Tuple[int, int]
) -> npt.NDArray[np.bool_]:
"""
Converts run-length encoding (RLE) to a binary mask.
Args:
rle (Union[npt.NDArray[np.int_], List[int]]): The 1D RLE array, the format
used in the COCO dataset (column-wise encoding, values of an array with
even indices represent the number of pixels assigned as background,
values of an array with odd indices represent the number of pixels
assigned as foreground object).
resolution_wh (Tuple[int, int]): The width (w) and height (h)
of the desired binary mask.
Returns:
The generated 2D Boolean mask of shape `(h, w)`, where the foreground object is
marked with `True`'s and the rest is filled with `False`'s.
Raises:
AssertionError: If the sum of pixels encoded in RLE differs from the
number of pixels in the expected mask (computed based on resolution_wh).
Examples:
```python
import supervision as sv
sv.rle_to_mask([5, 2, 2, 2, 5], (4, 4))
# array([
# [False, False, False, False],
# [False, True, True, False],
# [False, True, True, False],
# [False, False, False, False],
# ])
```
"""
if isinstance(rle, list):
rle = np.array(rle, dtype=int)
width, height = resolution_wh
assert width * height == np.sum(rle), (
"the sum of the number of pixels in the RLE must be the same "
"as the number of pixels in the expected mask"
)
zero_one_values = np.zeros(shape=(rle.size, 1), dtype=np.uint8)
zero_one_values[1::2] = 1
decoded_rle = np.repeat(zero_one_values, rle, axis=0)
decoded_rle = np.append(
decoded_rle, np.zeros(width * height - len(decoded_rle), dtype=np.uint8)
)
return decoded_rle.reshape((height, width), order="F")
def mask_to_rle(mask: npt.NDArray[np.bool_]) -> List[int]:
"""
Converts a binary mask into a run-length encoding (RLE).
Args:
mask (npt.NDArray[np.bool_]): 2D binary mask where `True` indicates foreground
object and `False` indicates background.
Returns:
The run-length encoded mask. Values of a list with even indices
represent the number of pixels assigned as background (`False`), values
of a list with odd indices represent the number of pixels assigned
as foreground object (`True`).
Raises:
AssertionError: If input mask is not 2D or is empty.
Examples:
```python
import numpy as np
import supervision as sv
mask = np.array([
[True, True, True, True],
[True, True, True, True],
[True, True, True, True],
[True, True, True, True],
])
sv.mask_to_rle(mask)
# [0, 16]
mask = np.array([
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
])
sv.mask_to_rle(mask)
# [5, 2, 2, 2, 5]
```
![mask_to_rle](https://media.roboflow.com/supervision-docs/mask-to-rle.png){ align=center width="800" }
""" # noqa E501 // docs
assert mask.ndim == 2, "Input mask must be 2D"
assert mask.size != 0, "Input mask cannot be empty"
on_value_change_indices = np.where(
mask.ravel(order="F") != np.roll(mask.ravel(order="F"), 1)
)[0]
on_value_change_indices = np.append(on_value_change_indices, mask.size)
# need to add 0 at the beginning when the same value is in the first and
# last element of the flattened mask
if on_value_change_indices[0] != 0:
on_value_change_indices = np.insert(on_value_change_indices, 0, 0)
rle = np.diff(on_value_change_indices)
if mask[0][0] == 1:
rle = np.insert(rle, 0, 0)
return list(rle)
+6 -6
View File
@@ -3,16 +3,12 @@ from typing import List, Optional, Union
import cv2
from supervision.annotators.base import ImageType
from supervision.annotators.utils import scene_to_annotator_img_type
from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.utils.conversion import convert_for_annotation_method
from supervision.utils.internal import deprecated
@deprecated(
"`BoxAnnotator` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `BoundingBoxAnnotator` and `LabelAnnotator` instead"
)
class BoxAnnotator:
"""
A class for drawing bounding boxes on an image using detections provided.
@@ -46,7 +42,11 @@ class BoxAnnotator:
self.text_thickness: int = text_thickness
self.text_padding: int = text_padding
@scene_to_annotator_img_type
@deprecated(
"`BoxAnnotator` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `BoundingBoxAnnotator` and `LabelAnnotator` instead"
)
@convert_for_annotation_method
def annotate(
self,
scene: ImageType,
+419 -64
View File
@@ -7,53 +7,96 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
import numpy as np
from supervision.config import CLASS_NAME_DATA_FIELD, ORIENTED_BOX_COORDINATES
from supervision.detection.utils import (
from supervision.detection.lmm import LMM, from_paligemma, validate_lmm_and_kwargs
from supervision.detection.overlap_filter import (
box_non_max_merge,
box_non_max_suppression,
mask_non_max_suppression,
)
from supervision.detection.utils import (
box_iou_batch,
calculate_masks_centroids,
extract_ultralytics_masks,
get_data_item,
is_data_equal,
mask_non_max_suppression,
mask_to_xyxy,
merge_data,
process_roboflow_result,
validate_detections_fields,
xywh_to_xyxy,
)
from supervision.geometry.core import Position
from supervision.utils.internal import deprecated
from supervision.utils.internal import deprecated, get_instance_variables
from supervision.validators import validate_detections_fields
@dataclass
class Detections:
"""
The `sv.Detections` allows you to convert results from a variety of object detection
and segmentation models into a single, unified format. The `sv.Detections` class
enables easy data manipulation and filtering, and provides a consistent API for
Supervision's tools like trackers, annotators, and zones.
The `sv.Detections` class in the Supervision library standardizes results from
various object detection and segmentation models into a consistent format. This
class simplifies data manipulation and filtering, providing a uniform API for
integration with Supervision [trackers](/trackers/), [annotators](/detection/annotators/), and [tools](/detection/tools/line_zone/).
```python
import cv2
import supervision as sv
from ultralytics import YOLO
=== "Inference"
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s.pt')
annotator = sv.BoundingBoxAnnotator()
Use [`sv.Detections.from_inference`](/detection/core/#supervision.detection.core.Detections.from_inference)
method, which accepts model results from both detection and segmentation models.
result = model(image)[0]
detections = sv.Detections.from_ultralytics(result)
```python
import cv2
import supervision as sv
from inference import get_model
annotated_image = annotator.annotate(image, detections)
```
model = get_model(model_id="yolov8n-640")
image = cv2.imread(<SOURCE_IMAGE_PATH>)
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
```
!!! tip
=== "Ultralytics"
In `sv.Detections`, detection data is categorized into two main field types:
fixed and custom. The fixed fields include `xyxy`, `mask`, `confidence`,
`class_id`, and `tracker_id`. For any additional data requirements, custom
fields come into play, stored in the data field. These custom fields are easily
accessible using the `detections[<FIELD_NAME>]` syntax, providing flexibility
for diverse data handling needs.
Use [`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(<SOURCE_IMAGE_PATH>)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```
=== "Transformers"
Use [`sv.Detections.from_transformers`](/detection/core/#supervision.detection.core.Detections.from_transformers)
method, which accepts model results from both detection and segmentation models.
```python
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
transformers_results=results,
id2label=model.config.id2label)
```
Attributes:
xyxy (np.ndarray): An array of shape `(n, 4)` containing
@@ -69,15 +112,7 @@ class Detections:
data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional
data where each key is a string representing the data type, and the value
is either a NumPy array or a list of corresponding data.
!!! warning
The `data` field in the `sv.Detections` class is currently in an experimental
phase. Please be aware that its API and functionality are subject to change in
future updates as we continue to refine and improve its capabilities.
We encourage users to experiment with this feature and provide feedback, but
also to be prepared for potential modifications in upcoming releases.
"""
""" # noqa: E501 // docs
xyxy: np.ndarray
mask: Optional[np.ndarray] = None
@@ -176,8 +211,8 @@ class Detections:
@classmethod
def from_ultralytics(cls, ultralytics_results) -> Detections:
"""
Creates a Detections instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
Creates a `sv.Detections` instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
!!! Note
@@ -188,7 +223,7 @@ class Detections:
Args:
ultralytics_results (ultralytics.yolo.engine.results.Results):
The output Results instance from YOLOv8
The output Results instance from Ultralytics
Returns:
Detections: A new Detections object.
@@ -201,13 +236,16 @@ class Detections:
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s.pt')
result = model(image)[0]
detections = sv.Detections.from_ultralytics(result)
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```
!!! tip
Class names values can be accessed using `detections["class_name"]`.
""" # noqa: E501 // docs
if ultralytics_results.obb is not None:
if hasattr(ultralytics_results, "obb") and ultralytics_results.obb is not None:
class_id = ultralytics_results.obb.cls.cpu().numpy().astype(int)
class_names = np.array([ultralytics_results.names[i] for i in class_id])
oriented_box_coordinates = ultralytics_results.obb.xyxyxyxy.cpu().numpy()
@@ -385,23 +423,86 @@ class Detections:
xyxy=mmdet_results.pred_instances.bboxes.cpu().numpy(),
confidence=mmdet_results.pred_instances.scores.cpu().numpy(),
class_id=mmdet_results.pred_instances.labels.cpu().numpy().astype(int),
mask=mmdet_results.pred_instances.masks.cpu().numpy()
if "masks" in mmdet_results.pred_instances
else None,
)
@classmethod
def from_transformers(cls, transformers_results: dict) -> Detections:
def from_transformers(
cls, transformers_results: dict, id2label: Optional[Dict[int, str]] = None
) -> Detections:
"""
Creates a Detections instance from object detection
[transformer](https://github.com/huggingface/transformers) inference result.
Creates a Detections instance from object detection or segmentation
[Transformer](https://github.com/huggingface/transformers) inference result.
Args:
transformers_results (dict): The output of Transformers model inference. A
dictionary containing the `scores`, `labels`, `boxes` and `masks` keys.
id2label (Optional[Dict[int, str]]): A dictionary mapping class IDs to
class names. If provided, the resulting Detections object will contain
`class_name` data field with the class names.
Returns:
Detections: A new Detections object.
"""
return cls(
xyxy=transformers_results["boxes"].cpu().numpy(),
confidence=transformers_results["scores"].cpu().numpy(),
class_id=transformers_results["labels"].cpu().numpy().astype(int),
)
Example:
```python
import torch
import supervision as sv
from PIL import Image
from transformers import DetrImageProcessor, DetrForObjectDetection
processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50")
image = Image.open(<SOURCE_IMAGE_PATH>)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
width, height = image.size
target_size = torch.tensor([[height, width]])
results = processor.post_process_object_detection(
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(
transformers_results=results,
id2label=model.config.id2label
)
```
!!! tip
Class names values can be accessed using `detections["class_name"]`.
""" # noqa: E501 // docs
class_ids = transformers_results["labels"].cpu().detach().numpy().astype(int)
data = {}
if id2label is not None:
class_names = np.array([id2label[class_id] for class_id in class_ids])
data[CLASS_NAME_DATA_FIELD] = class_names
if "boxes" in transformers_results:
return cls(
xyxy=transformers_results["boxes"].cpu().detach().numpy(),
confidence=transformers_results["scores"].cpu().detach().numpy(),
class_id=class_ids,
data=data,
)
elif "masks" in transformers_results:
masks = transformers_results["masks"].cpu().detach().numpy().astype(bool)
return cls(
xyxy=mask_to_xyxy(masks),
mask=masks,
confidence=transformers_results["scores"].cpu().detach().numpy(),
class_id=class_ids,
data=data,
)
else:
raise NotImplementedError(
"Only object detection and semantic segmentation results are supported."
)
@classmethod
def from_detectron2(cls, detectron2_results) -> Detections:
@@ -448,17 +549,12 @@ class Detections:
@classmethod
def from_inference(cls, roboflow_result: Union[dict, Any]) -> Detections:
"""
Create a Detections object from the [Roboflow](https://roboflow.com/)
Create a `sv.Detections` object from the [Roboflow](https://roboflow.com/)
API inference result or the [Inference](https://inference.roboflow.com/)
package results. This method extracts bounding boxes, class IDs,
confidences, and class names from the Roboflow API result and encapsulates
them into a Detections object.
!!! note
Class names can be accessed using the key 'class_name' in the returned
object's data attribute.
Args:
roboflow_result (dict, any): The result from the
Roboflow API or Inference package containing predictions.
@@ -471,14 +567,18 @@ class Detections:
```python
import cv2
import supervision as sv
from inference.models.utils import get_roboflow_model
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_roboflow_model(model_id="yolov8s-640")
model = get_model(model_id="yolov8s-640")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
```
!!! tip
Class names values can be accessed using `detections["class_name"]`.
"""
with suppress(AttributeError):
roboflow_result = roboflow_result.dict(exclude_none=True, by_alias=True)
@@ -528,10 +628,10 @@ class Detections:
```python
import cv2
import supervision as sv
from inference.models.utils import get_roboflow_model
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_roboflow_model(model_id="yolov8s-640")
model = get_model(model_id="yolov8s-640")
result = model.infer(image)[0]
detections = sv.Detections.from_roboflow(result)
@@ -710,6 +810,52 @@ class Detections:
class_id=paddledet_result["bbox"][:, 0].astype(int),
)
@classmethod
def from_lmm(cls, lmm: Union[LMM, str], result: str, **kwargs) -> Detections:
"""
Creates a Detections object from the given result string based on the specified
Large Multimodal Model (LMM).
Args:
lmm (Union[LMM, str]): The type of LMM (Large Multimodal Model) to use.
result (str): The result string containing the detection data.
**kwargs: Additional keyword arguments required by the specified LMM.
Returns:
Detections: A new Detections object.
Raises:
ValueError: If the LMM is invalid, required arguments are missing, or
disallowed arguments are provided.
ValueError: If the specified LMM is not supported.
Examples:
```python
import supervision as sv
paligemma_result = "<loc0256><loc0256><loc0768><loc0768> cat"
detections = sv.Detections.from_lmm(
sv.LMM.PALIGEMMA,
paligemma_result,
resolution_wh=(1000, 1000),
classes=['cat', 'dog']
)
detections.xyxy
# array([[250., 250., 750., 750.]])
detections.class_id
# array([0])
```
"""
lmm = validate_lmm_and_kwargs(lmm, kwargs)
if lmm == LMM.PALIGEMMA:
xyxy, class_id, class_name = from_paligemma(result, **kwargs)
data = {CLASS_NAME_DATA_FIELD: class_name}
return cls(xyxy=xyxy, class_id=class_id, data=data)
raise ValueError(f"Unsupported LMM: {lmm}")
@classmethod
def empty(cls) -> Detections:
"""
@@ -732,6 +878,14 @@ class Detections:
class_id=np.array([], dtype=int),
)
def is_empty(self) -> bool:
"""
Returns `True` if the `Detections` object is considered empty.
"""
empty_detections = Detections.empty()
empty_detections.data = self.data
return self == empty_detections
@classmethod
def merge(cls, detections_list: List[Detections]) -> Detections:
"""
@@ -739,9 +893,14 @@ class Detections:
This method takes a list of Detections objects and combines their
respective fields (`xyxy`, `mask`, `confidence`, `class_id`, and `tracker_id`)
into a single Detections object. If all elements in a field are not
`None`, the corresponding field will be stacked.
Otherwise, the field will be set to `None`.
into a single Detections object.
For example, if merging Detections with 3 and 4 detected objects, this method
will return a Detections with 7 objects (7 entries in `xyxy`, `mask`, etc).
!!! Note
When merging, empty `Detections` objects are ignored.
Args:
detections_list (List[Detections]): A list of Detections objects to merge.
@@ -781,6 +940,10 @@ class Detections:
array([0.1, 0.2, 0.3])
```
"""
detections_list = [
detections for detections in detections_list if not detections.is_empty()
]
if len(detections_list) == 0:
return Detections.empty()
@@ -1055,3 +1218,195 @@ class Detections:
)
return self[indices]
def with_nmm(
self, threshold: float = 0.5, class_agnostic: bool = False
) -> Detections:
"""
Perform non-maximum merging on the current set of object detections.
Args:
threshold (float, optional): The intersection-over-union threshold
to use for non-maximum merging. Defaults to 0.5.
class_agnostic (bool, optional): Whether to perform class-agnostic
non-maximum merging. If True, the class_id of each detection
will be ignored. Defaults to False.
Returns:
Detections: A new Detections object containing the subset of detections
after non-maximum merging.
Raises:
AssertionError: If `confidence` is None or `class_id` is None and
class_agnostic is False.
![non-max-merging](https://media.roboflow.com/supervision-docs/non-max-merging.png){ align=center width="800" }
""" # noqa: E501 // docs
if len(self) == 0:
return self
assert (
self.confidence is not None
), "Detections confidence must be given for NMM to be executed."
if class_agnostic:
predictions = np.hstack((self.xyxy, self.confidence.reshape(-1, 1)))
else:
assert self.class_id is not None, (
"Detections class_id must be given for NMM to be executed. If you"
" intended to perform class agnostic NMM set class_agnostic=True."
)
predictions = np.hstack(
(
self.xyxy,
self.confidence.reshape(-1, 1),
self.class_id.reshape(-1, 1),
)
)
merge_groups = box_non_max_merge(
predictions=predictions, iou_threshold=threshold
)
result = []
for merge_group in merge_groups:
unmerged_detections = [self[i] for i in merge_group]
merged_detections = merge_inner_detections_objects(
unmerged_detections, threshold
)
result.append(merged_detections)
return Detections.merge(result)
def merge_inner_detection_object_pair(
detections_1: Detections, detections_2: Detections
) -> Detections:
"""
Merges two Detections object into a single Detections object.
Assumes each Detections contains exactly one object.
A `winning` detection is determined based on the confidence score of the two
input detections. This winning detection is then used to specify which
`class_id`, `tracker_id`, and `data` to include in the merged Detections object.
The resulting `confidence` of the merged object is calculated by the weighted
contribution of ea detection to the merged object.
The bounding boxes and masks of the two input detections are merged into a
single bounding box and mask, respectively.
Args:
detections_1 (Detections):
The first Detections object
detections_2 (Detections):
The second Detections object
Returns:
Detections: A new Detections object, with merged attributes.
Raises:
ValueError: If the input Detections objects do not have exactly 1 detected
object.
Example:
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8s-640")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
merged_detections = merge_object_detection_pair(
detections[0], detections[1])
```
"""
if len(detections_1) != 1 or len(detections_2) != 1:
raise ValueError("Both Detections should have exactly 1 detected object.")
validate_fields_both_defined_or_none(detections_1, detections_2)
xyxy_1 = detections_1.xyxy[0]
xyxy_2 = detections_2.xyxy[0]
if detections_1.confidence is None and detections_2.confidence is None:
merged_confidence = None
else:
detection_1_area = (xyxy_1[2] - xyxy_1[0]) * (xyxy_1[3] - xyxy_1[1])
detections_2_area = (xyxy_2[2] - xyxy_2[0]) * (xyxy_2[3] - xyxy_2[1])
merged_confidence = (
detection_1_area * detections_1.confidence[0]
+ detections_2_area * detections_2.confidence[0]
) / (detection_1_area + detections_2_area)
merged_confidence = np.array([merged_confidence])
merged_x1, merged_y1 = np.minimum(xyxy_1[:2], xyxy_2[:2])
merged_x2, merged_y2 = np.maximum(xyxy_1[2:], xyxy_2[2:])
merged_xyxy = np.array([[merged_x1, merged_y1, merged_x2, merged_y2]])
if detections_1.mask is None and detections_2.mask is None:
merged_mask = None
else:
merged_mask = np.logical_or(detections_1.mask, detections_2.mask)
if detections_1.confidence is None and detections_2.confidence is None:
winning_detection = detections_1
elif detections_1.confidence[0] >= detections_2.confidence[0]:
winning_detection = detections_1
else:
winning_detection = detections_2
return Detections(
xyxy=merged_xyxy,
mask=merged_mask,
confidence=merged_confidence,
class_id=winning_detection.class_id,
tracker_id=winning_detection.tracker_id,
data=winning_detection.data,
)
def merge_inner_detections_objects(
detections: List[Detections], threshold=0.5
) -> Detections:
"""
Given N detections each of length 1 (exactly one object inside), combine them into a
single detection object of length 1. The contained inner object will be the merged
result of all the input detections.
For example, this lets you merge N boxes into one big box, N masks into one mask,
etc.
"""
detections_1 = detections[0]
for detections_2 in detections[1:]:
box_iou = box_iou_batch(detections_1.xyxy, detections_2.xyxy)[0]
if box_iou < threshold:
break
detections_1 = merge_inner_detection_object_pair(detections_1, detections_2)
return detections_1
def validate_fields_both_defined_or_none(
detections_1: Detections, detections_2: Detections
) -> None:
"""
Verify that for each optional field in the Detections, both instances either have
the field set to None or both have it set to non-None values.
`data` field is ignored.
Raises:
ValueError: If one field is None and the other is not, for any of the fields.
"""
attributes = get_instance_variables(detections_1)
for attribute in attributes:
value_1 = getattr(detections_1, attribute)
value_2 = getattr(detections_2, attribute)
if (value_1 is None) != (value_2 is None):
raise ValueError(
f"Field '{attribute}' should be consistently None or not None in both "
"Detections."
)
+26 -20
View File
@@ -1,12 +1,15 @@
import warnings
from typing import Dict, Iterable, Optional, Tuple
import cv2
import numpy as np
from supervision.detection.core import Detections
from supervision.detection.utils import cross_product
from supervision.draw.color import Color
from supervision.draw.utils import draw_text
from supervision.geometry.core import Point, Position, Vector
from supervision.utils.internal import SupervisionWarnings
class LineZone:
@@ -81,6 +84,8 @@ class LineZone:
self.in_count: int = 0
self.out_count: int = 0
self.triggering_anchors = triggering_anchors
if not list(self.triggering_anchors):
raise ValueError("Triggering anchors cannot be empty.")
@staticmethod
def calculate_region_of_interest_limits(vector: Vector) -> Tuple[Vector, Vector]:
@@ -140,6 +145,15 @@ class LineZone:
if len(detections) == 0:
return crossed_in, crossed_out
if detections.tracker_id is None:
warnings.warn(
"Line zone counting skipped. LineZone requires tracker_id. Refer to "
"https://supervision.roboflow.com/latest/trackers for more "
"information.",
category=SupervisionWarnings,
)
return crossed_in, crossed_out
all_anchors = np.array(
[
detections.get_anchors_coordinates(anchor)
@@ -147,31 +161,23 @@ class LineZone:
]
)
cross_products_1 = cross_product(all_anchors, self.limits[0])
cross_products_2 = cross_product(all_anchors, self.limits[1])
in_limits = (cross_products_1 > 0) == (cross_products_2 > 0)
in_limits = np.all(in_limits, axis=0)
triggers = cross_product(all_anchors, self.vector) < 0
has_any_left_trigger = np.any(triggers, axis=0)
has_any_right_trigger = np.any(~triggers, axis=0)
is_uniformly_triggered = ~(has_any_left_trigger & has_any_right_trigger)
for i, tracker_id in enumerate(detections.tracker_id):
if tracker_id is None:
if not in_limits[i]:
continue
box_anchors = [Point(x=x, y=y) for x, y in all_anchors[:, i, :]]
in_limits = all(
[
self.is_point_in_limits(point=anchor, limits=self.limits)
for anchor in box_anchors
]
)
if not in_limits:
if not is_uniformly_triggered[i]:
continue
triggers = [
self.vector.cross_product(point=anchor) < 0 for anchor in box_anchors
]
if len(set(triggers)) == 2:
continue
tracker_state = triggers[0]
tracker_state = has_any_left_trigger[i]
if tracker_id not in self.tracker_state:
self.tracker_state[tracker_id] = tracker_state
continue
+59
View File
@@ -0,0 +1,59 @@
import re
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union
import numpy as np
class LMM(Enum):
PALIGEMMA = "paligemma"
REQUIRED_ARGUMENTS: Dict[LMM, List[str]] = {LMM.PALIGEMMA: ["resolution_wh"]}
ALLOWED_ARGUMENTS: Dict[LMM, List[str]] = {LMM.PALIGEMMA: ["resolution_wh", "classes"]}
def validate_lmm_and_kwargs(lmm: Union[LMM, str], kwargs: Dict[str, Any]) -> LMM:
if isinstance(lmm, str):
try:
lmm = LMM(lmm.lower())
except ValueError:
raise ValueError(
f"Invalid lmm value: {lmm}. Must be one of {[e.value for e in LMM]}"
)
required_args = REQUIRED_ARGUMENTS.get(lmm, [])
for arg in required_args:
if arg not in kwargs:
raise ValueError(f"Missing required argument: {arg}")
allowed_args = ALLOWED_ARGUMENTS.get(lmm, [])
for arg in kwargs:
if arg not in allowed_args:
raise ValueError(f"Argument {arg} is not allowed for {lmm.name}")
return lmm
def from_paligemma(
result: str, resolution_wh: Tuple[int, int], classes: Optional[List[str]] = None
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
w, h = resolution_wh
pattern = re.compile(
r"(?<!<loc\d{4}>)<loc(\d{4})><loc(\d{4})><loc(\d{4})><loc(\d{4})> ([\w\s\-]+)"
)
matches = pattern.findall(result)
matches = np.array(matches) if matches else np.empty((0, 5))
xyxy, class_name = matches[:, [1, 0, 3, 2]], matches[:, 4]
xyxy = xyxy.astype(int) / 1024 * np.array([w, h, w, h])
class_name = np.char.strip(class_name.astype(str))
class_id = None
if classes is not None:
mask = np.array([name in classes for name in class_name]).astype(bool)
xyxy, class_name = xyxy[mask], class_name[mask]
class_id = np.array([classes.index(name) for name in class_name])
return xyxy, class_id, class_name
+263
View File
@@ -0,0 +1,263 @@
from enum import Enum
from typing import List, Union
import numpy as np
import numpy.typing as npt
from supervision.detection.utils import box_iou_batch, mask_iou_batch
def resize_masks(masks: np.ndarray, max_dimension: int = 640) -> np.ndarray:
"""
Resize all masks in the array to have a maximum dimension of max_dimension,
maintaining aspect ratio.
Args:
masks (np.ndarray): 3D array of binary masks with shape (N, H, W).
max_dimension (int): The maximum dimension for the resized masks.
Returns:
np.ndarray: Array of resized masks.
"""
max_height = np.max(masks.shape[1])
max_width = np.max(masks.shape[2])
scale = min(max_dimension / max_height, max_dimension / max_width)
new_height = int(scale * max_height)
new_width = int(scale * max_width)
x = np.linspace(0, max_width - 1, new_width).astype(int)
y = np.linspace(0, max_height - 1, new_height).astype(int)
xv, yv = np.meshgrid(x, y)
resized_masks = masks[:, yv, xv]
resized_masks = resized_masks.reshape(masks.shape[0], new_height, new_width)
return resized_masks
def mask_non_max_suppression(
predictions: np.ndarray,
masks: np.ndarray,
iou_threshold: float = 0.5,
mask_dimension: int = 640,
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on segmentation predictions.
Args:
predictions (np.ndarray): A 2D array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`. Shape: `(N, 5)` or
`(N, 6)`, where N is the number of predictions.
masks (np.ndarray): A 3D array of binary masks corresponding to the predictions.
Shape: `(N, H, W)`, where N is the number of predictions, and H, W are the
dimensions of each mask.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression.
mask_dimension (int, optional): The dimension to which the masks should be
resized before computing IOU values. Defaults to 640.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after
non-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the closed
range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
sort_index = predictions[:, 4].argsort()[::-1]
predictions = predictions[sort_index]
masks = masks[sort_index]
masks_resized = resize_masks(masks, mask_dimension)
ious = mask_iou_batch(masks_resized, masks_resized)
categories = predictions[:, 5]
keep = np.ones(rows, dtype=bool)
for i in range(rows):
if keep[i]:
condition = (ious[i] > iou_threshold) & (categories[i] == categories)
keep[i + 1 :] = np.where(condition[i + 1 :], False, keep[i + 1 :])
return keep[sort_index.argsort()]
def box_non_max_suppression(
predictions: np.ndarray, iou_threshold: float = 0.5
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on object detection predictions.
Args:
predictions (np.ndarray): An array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after n
on-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the
closed range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
# add column #5 - category filled with zeros for agnostic nms
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
# sort predictions column #4 - score
sort_index = np.flip(predictions[:, 4].argsort())
predictions = predictions[sort_index]
boxes = predictions[:, :4]
categories = predictions[:, 5]
ious = box_iou_batch(boxes, boxes)
ious = ious - np.eye(rows)
keep = np.ones(rows, dtype=bool)
for index, (iou, category) in enumerate(zip(ious, categories)):
if not keep[index]:
continue
# drop detections with iou > iou_threshold and
# same category as current detections
condition = (iou > iou_threshold) & (categories == category)
keep = keep & ~condition
return keep[sort_index.argsort()]
def group_overlapping_boxes(
predictions: npt.NDArray[np.float64], iou_threshold: float = 0.5
) -> List[List[int]]:
"""
Apply greedy version of non-maximum merging to avoid detecting too many
overlapping bounding boxes for a given object.
Args:
predictions (npt.NDArray[np.float64]): An array of shape `(n, 5)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
and the confidence scores.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression. Defaults to 0.5.
Returns:
List[List[int]]: Groups of prediction indices be merged.
Each group may have 1 or more elements.
"""
merge_groups: List[List[int]] = []
scores = predictions[:, 4]
order = scores.argsort()
while len(order) > 0:
idx = int(order[-1])
order = order[:-1]
if len(order) == 0:
merge_groups.append([idx])
break
merge_candidate = np.expand_dims(predictions[idx], axis=0)
ious = box_iou_batch(predictions[order][:, :4], merge_candidate[:, :4])
ious = ious.flatten()
above_threshold = ious >= iou_threshold
merge_group = [idx] + np.flip(order[above_threshold]).tolist()
merge_groups.append(merge_group)
order = order[~above_threshold]
return merge_groups
def box_non_max_merge(
predictions: npt.NDArray[np.float64],
iou_threshold: float = 0.5,
) -> List[List[int]]:
"""
Apply greedy version of non-maximum merging per category to avoid detecting
too many overlapping bounding boxes for a given object.
Args:
predictions (npt.NDArray[np.float64]): An array of shape `(n, 5)` or `(n, 6)`
containing the bounding boxes coordinates in format `[x1, y1, x2, y2]`,
the confidence scores and class_ids. Omit class_id column to allow
detections of different classes to be merged.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression. Defaults to 0.5.
Returns:
List[List[int]]: Groups of prediction indices be merged.
Each group may have 1 or more elements.
"""
if predictions.shape[1] == 5:
return group_overlapping_boxes(predictions, iou_threshold)
category_ids = predictions[:, 5]
merge_groups = []
for category_id in np.unique(category_ids):
curr_indices = np.where(category_ids == category_id)[0]
merge_class_groups = group_overlapping_boxes(
predictions[curr_indices], iou_threshold
)
for merge_class_group in merge_class_groups:
merge_groups.append(curr_indices[merge_class_group].tolist())
for merge_group in merge_groups:
if len(merge_group) == 0:
raise ValueError(
f"Empty group detected when non-max-merging "
f"detections: {merge_groups}"
)
return merge_groups
class OverlapFilter(Enum):
"""
Enum specifying the strategy for filtering overlapping detections.
Attributes:
NONE: Do not filter detections based on overlap.
NON_MAX_SUPPRESSION: Filter detections using non-max suppression. This means,
detections that overlap by more than a set threshold will be discarded,
except for the one with the highest confidence.
NON_MAX_MERGE: Merge detections with non-max merging. This means,
detections that overlap by more than a set threshold will be merged
into a single detection.
"""
NONE = "none"
NON_MAX_SUPPRESSION = "non_max_suppression"
NON_MAX_MERGE = "non_max_merge"
def validate_overlap_filter(
strategy: Union[OverlapFilter, str],
) -> OverlapFilter:
if isinstance(strategy, str):
try:
strategy = OverlapFilter(strategy.lower())
except ValueError:
raise ValueError(
f"Invalid strategy value: {strategy}. Must be one of "
f"{[e.value for e in OverlapFilter]}"
)
return strategy
+55 -12
View File
@@ -1,23 +1,42 @@
import warnings
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Callable, Optional, Tuple
from typing import Callable, Optional, Tuple, Union
import numpy as np
from supervision.detection.core import Detections
from supervision.detection.utils import move_boxes
from supervision.detection.overlap_filter import OverlapFilter, validate_overlap_filter
from supervision.detection.utils import move_boxes, move_masks
from supervision.utils.image import crop_image
from supervision.utils.internal import SupervisionWarnings
def move_detections(detections: Detections, offset: np.array) -> Detections:
def move_detections(
detections: Detections,
offset: np.ndarray,
resolution_wh: Optional[Tuple[int, int]] = None,
) -> Detections:
"""
Args:
detections (sv.Detections): Detections object to be moved.
offset (np.array): An array of shape `(2,)` containing offset values in format
offset (np.ndarray): An array of shape `(2,)` containing offset values in format
is `[dx, dy]`.
resolution_wh (Tuple[int, int]): The width and height of the desired mask
resolution. Required for segmentation detections.
Returns:
(sv.Detections) repositioned Detections object.
"""
detections.xyxy = move_boxes(xyxy=detections.xyxy, offset=offset)
if detections.mask is not None:
if resolution_wh is None:
raise ValueError(
"Resolution width and height are required for moving segmentation "
"detections. This should be the same as (width, height) of image shape."
)
detections.mask = move_masks(
masks=detections.mask, offset=offset, resolution_wh=resolution_wh
)
return detections
@@ -34,8 +53,10 @@ class InferenceSlicer:
`(width, height)`.
overlap_ratio_wh (Tuple[float, float]): Overlap ratio between consecutive
slices in the format `(width_ratio, height_ratio)`.
iou_threshold (Optional[float]): Intersection over Union (IoU) threshold
used for non-max suppression.
overlap_filter_strategy (Union[OverlapFilter, str]): Strategy for
filtering or merging overlapping detections in slices.
iou_threshold (float): Intersection over Union (IoU) threshold
used when filtering by overlap.
callback (Callable): A function that performs inference on a given image
slice and returns detections.
thread_workers (int): Number of threads for parallel execution.
@@ -52,12 +73,18 @@ class InferenceSlicer:
callback: Callable[[np.ndarray], Detections],
slice_wh: Tuple[int, int] = (320, 320),
overlap_ratio_wh: Tuple[float, float] = (0.2, 0.2),
iou_threshold: Optional[float] = 0.5,
overlap_filter_strategy: Union[
OverlapFilter, str
] = OverlapFilter.NON_MAX_SUPPRESSION,
iou_threshold: float = 0.5,
thread_workers: int = 1,
):
overlap_filter_strategy = validate_overlap_filter(overlap_filter_strategy)
self.slice_wh = slice_wh
self.overlap_ratio_wh = overlap_ratio_wh
self.iou_threshold = iou_threshold
self.overlap_filter_strategy = overlap_filter_strategy
self.callback = callback
self.thread_workers = thread_workers
@@ -88,7 +115,10 @@ class InferenceSlicer:
result = model(image_slice)[0]
return sv.Detections.from_ultralytics(result)
slicer = sv.InferenceSlicer(callback = callback)
slicer = sv.InferenceSlicer(
callback=callback,
overlap_filter_strategy=sv.OverlapFilter.NON_MAX_SUPPRESSION,
)
detections = slicer(image)
```
@@ -108,9 +138,19 @@ class InferenceSlicer:
for future in as_completed(futures):
detections_list.append(future.result())
return Detections.merge(detections_list=detections_list).with_nms(
threshold=self.iou_threshold
)
merged = Detections.merge(detections_list=detections_list)
if self.overlap_filter_strategy == OverlapFilter.NONE:
return merged
elif self.overlap_filter_strategy == OverlapFilter.NON_MAX_SUPPRESSION:
return merged.with_nms(threshold=self.iou_threshold)
elif self.overlap_filter_strategy == OverlapFilter.NON_MAX_MERGE:
return merged.with_nmm(threshold=self.iou_threshold)
else:
warnings.warn(
f"Invalid overlap filter strategy: {self.overlap_filter_strategy}",
category=SupervisionWarnings,
)
return merged
def _run_callback(self, image, offset) -> Detections:
"""
@@ -126,7 +166,10 @@ class InferenceSlicer:
"""
image_slice = crop_image(image=image, xyxy=offset)
detections = self.callback(image_slice)
detections = move_detections(detections=detections, offset=offset[:2])
resolution_wh = (image.shape[1], image.shape[0])
detections = move_detections(
detections=detections, offset=offset[:2], resolution_wh=resolution_wh
)
return detections
+21 -10
View File
@@ -1,8 +1,10 @@
import warnings
from dataclasses import replace
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
@@ -10,7 +12,7 @@ from supervision.draw.color import Color
from supervision.draw.utils import draw_polygon, draw_text
from supervision.geometry.core import Position
from supervision.geometry.utils import get_polygon_center
from supervision.utils.internal import deprecated_parameter
from supervision.utils.internal import SupervisionWarnings, deprecated_parameter
class PolygonZone:
@@ -20,7 +22,6 @@ class PolygonZone:
Attributes:
polygon (np.ndarray): A polygon represented by a numpy array of shape
`(N, 2)`, containing the `x`, `y` coordinates of the points.
frame_resolution_wh (Tuple[int, int]): The frame resolution (width, height)
triggering_anchors (Iterable[sv.Position]): A list of positions specifying
which anchors of the detections bounding box to consider when deciding on
whether the detection fits within the PolygonZone
@@ -39,22 +40,32 @@ class PolygonZone:
)
def __init__(
self,
polygon: np.ndarray,
frame_resolution_wh: Tuple[int, int],
polygon: npt.NDArray[np.int64],
frame_resolution_wh: Optional[Tuple[int, int]] = None,
triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
):
if frame_resolution_wh is not None:
warnings.warn(
"The `frame_resolution_wh` parameter is no longer required and will be "
"dropped in version supervision-0.24.0. The mask resolution is now "
"calculated automatically based on the polygon coordinates.",
category=SupervisionWarnings,
)
self.polygon = polygon.astype(int)
self.frame_resolution_wh = frame_resolution_wh
self.triggering_anchors = triggering_anchors
if not list(self.triggering_anchors):
raise ValueError("Triggering anchors cannot be empty.")
self.current_count = 0
width, height = frame_resolution_wh
x_max, y_max = np.max(polygon, axis=0)
self.frame_resolution_wh = (x_max + 1, y_max + 1)
self.mask = polygon_to_mask(
polygon=polygon, resolution_wh=(width + 1, height + 1)
polygon=polygon, resolution_wh=(x_max + 2, y_max + 2)
)
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 +89,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)
+6 -4
View File
@@ -1,3 +1,4 @@
import warnings
from collections import defaultdict, deque
from copy import deepcopy
from typing import Optional
@@ -5,6 +6,7 @@ from typing import Optional
import numpy as np
from supervision.detection.core import Detections
from supervision.utils.internal import SupervisionWarnings
class DetectionsSmoother:
@@ -70,16 +72,16 @@ class DetectionsSmoother:
"""
if detections.tracker_id is None:
print(
warnings.warn(
"Smoothing skipped. DetectionsSmoother requires tracker_id. Refer to "
"https://supervision.roboflow.com/latest/trackers for more information."
"https://supervision.roboflow.com/latest/trackers for more "
"information.",
category=SupervisionWarnings,
)
return detections
for detection_idx in range(len(detections)):
tracker_id = detections.tracker_id[detection_idx]
if tracker_id is None:
continue
self.tracks[tracker_id].append(detections[detection_idx])
+289 -267
View File
@@ -1,10 +1,12 @@
from itertools import chain
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union
import cv2
import numpy as np
import numpy.typing as npt
from supervision.config import CLASS_NAME_DATA_FIELD
from supervision.geometry.core import Vector
MIN_POLYGON_POINT_COUNT = 3
@@ -56,7 +58,9 @@ def box_iou_batch(boxes_true: np.ndarray, boxes_detection: np.ndarray) -> np.nda
bottom_right = np.minimum(boxes_true[:, None, 2:], boxes_detection[:, 2:])
area_inter = np.prod(np.clip(bottom_right - top_left, a_min=0, a_max=None), 2)
return area_inter / (area_true[:, None] + area_detection - area_inter)
ious = area_inter / (area_true[:, None] + area_detection - area_inter)
ious = np.nan_to_num(ious)
return ious
def _mask_iou_batch_split(
@@ -136,144 +140,6 @@ def mask_iou_batch(
return np.vstack(ious)
def resize_masks(masks: np.ndarray, max_dimension: int = 640) -> np.ndarray:
"""
Resize all masks in the array to have a maximum dimension of max_dimension,
maintaining aspect ratio.
Args:
masks (np.ndarray): 3D array of binary masks with shape (N, H, W).
max_dimension (int): The maximum dimension for the resized masks.
Returns:
np.ndarray: Array of resized masks.
"""
max_height = np.max(masks.shape[1])
max_width = np.max(masks.shape[2])
scale = min(max_dimension / max_height, max_dimension / max_width)
new_height = int(scale * max_height)
new_width = int(scale * max_width)
x = np.linspace(0, max_width - 1, new_width).astype(int)
y = np.linspace(0, max_height - 1, new_height).astype(int)
xv, yv = np.meshgrid(x, y)
resized_masks = masks[:, yv, xv]
resized_masks = resized_masks.reshape(masks.shape[0], new_height, new_width)
return resized_masks
def mask_non_max_suppression(
predictions: np.ndarray,
masks: np.ndarray,
iou_threshold: float = 0.5,
mask_dimension: int = 640,
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on segmentation predictions.
Args:
predictions (np.ndarray): A 2D array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`. Shape: `(N, 5)` or
`(N, 6)`, where N is the number of predictions.
masks (np.ndarray): A 3D array of binary masks corresponding to the predictions.
Shape: `(N, H, W)`, where N is the number of predictions, and H, W are the
dimensions of each mask.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression.
mask_dimension (int, optional): The dimension to which the masks should be
resized before computing IOU values. Defaults to 640.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after
non-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the closed
range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
sort_index = predictions[:, 4].argsort()[::-1]
predictions = predictions[sort_index]
masks = masks[sort_index]
masks_resized = resize_masks(masks, mask_dimension)
ious = mask_iou_batch(masks_resized, masks_resized)
categories = predictions[:, 5]
keep = np.ones(rows, dtype=bool)
for i in range(rows):
if keep[i]:
condition = (ious[i] > iou_threshold) & (categories[i] == categories)
keep[i + 1 :] = np.where(condition[i + 1 :], False, keep[i + 1 :])
return keep[sort_index.argsort()]
def box_non_max_suppression(
predictions: np.ndarray, iou_threshold: float = 0.5
) -> np.ndarray:
"""
Perform Non-Maximum Suppression (NMS) on object detection predictions.
Args:
predictions (np.ndarray): An array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after n
on-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the
closed range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
# add column #5 - category filled with zeros for agnostic nms
if columns == 5:
predictions = np.c_[predictions, np.zeros(rows)]
# sort predictions column #4 - score
sort_index = np.flip(predictions[:, 4].argsort())
predictions = predictions[sort_index]
boxes = predictions[:, :4]
categories = predictions[:, 5]
ious = box_iou_batch(boxes, boxes)
ious = ious - np.eye(rows)
keep = np.ones(rows, dtype=bool)
for index, (iou, category) in enumerate(zip(ious, categories)):
if not keep[index]:
continue
# drop detections with iou > iou_threshold and
# same category as current detections
condition = (iou > iou_threshold) & (categories == category)
keep = keep & ~condition
return keep[sort_index.argsort()]
def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
"""
Clips bounding boxes coordinates to fit within the frame resolution.
@@ -289,6 +155,25 @@ def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
np.ndarray: A numpy array of shape `(N, 4)` where each row
corresponds to a bounding box with coordinates clipped to fit
within the frame resolution.
Examples:
```python
import numpy as np
import supervision as sv
xyxy = np.array([
[10, 20, 300, 200],
[15, 25, 350, 450],
[-10, -20, 30, 40]
])
sv.clip_boxes(xyxy=xyxy, resolution_wh=(320, 240))
# array([
# [ 10, 20, 300, 200],
# [ 15, 25, 320, 240],
# [ 0, 0, 30, 40]
# ])
```
"""
result = np.copy(xyxy)
width, height = resolution_wh
@@ -297,6 +182,52 @@ def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
return result
def pad_boxes(xyxy: np.ndarray, px: int, py: Optional[int] = None) -> np.ndarray:
"""
Pads bounding boxes coordinates with a constant padding.
Args:
xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each
row corresponds to a bounding box in the format
`(x_min, y_min, x_max, y_max)`.
px (int): The padding value to be added to both the left and right sides of
each bounding box.
py (Optional[int]): The padding value to be added to both the top and bottom
sides of each bounding box. If not provided, `px` will be used for both
dimensions.
Returns:
np.ndarray: A numpy array of shape `(N, 4)` where each row corresponds to a
bounding box with coordinates padded according to the provided padding
values.
Examples:
```python
import numpy as np
import supervision as sv
xyxy = np.array([
[10, 20, 30, 40],
[15, 25, 35, 45]
])
sv.pad_boxes(xyxy=xyxy, px=5, py=10)
# array([
# [ 5, 10, 35, 50],
# [10, 15, 40, 55]
# ])
```
"""
if py is None:
py = px
result = xyxy.copy()
result[:, [0, 1]] -= [px, py]
result[:, [2, 3]] += [px, py]
return result
def xywh_to_xyxy(boxes_xywh: np.ndarray) -> np.ndarray:
xyxy = boxes_xywh.copy()
xyxy[:, 2] = boxes_xywh[:, 0] + boxes_xywh[:, 2]
@@ -317,7 +248,7 @@ def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
`(x_min, y_min, x_max, y_max)` for each mask
"""
n = masks.shape[0]
bboxes = np.zeros((n, 4), dtype=int)
xyxy = np.zeros((n, 4), dtype=int)
for i, mask in enumerate(masks):
rows, cols = np.where(mask)
@@ -325,9 +256,9 @@ def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
if len(rows) > 0 and len(cols) > 0:
x_min, x_max = np.min(cols), np.max(cols)
y_min, y_max = np.min(rows), np.max(rows)
bboxes[i, :] = [x_min, y_min, x_max, y_max]
xyxy[i, :] = [x_min, y_min, x_max, y_max]
return bboxes
return xyxy
def mask_to_polygons(mask: np.ndarray) -> List[np.ndarray]:
@@ -500,7 +431,7 @@ def process_roboflow_result(
np.ndarray,
Optional[np.ndarray],
Optional[np.ndarray],
Dict[str, List[np.ndarray]],
Dict[str, Union[List[np.ndarray], np.ndarray]],
]:
if not roboflow_result["predictions"]:
return (
@@ -563,59 +494,103 @@ def process_roboflow_result(
return xyxy, confidence, class_id, masks, tracker_id, data
def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray:
def move_boxes(
xyxy: npt.NDArray[np.float64], offset: npt.NDArray[np.int32]
) -> npt.NDArray[np.float64]:
"""
Parameters:
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes
coordinates in format `[x1, y1, x2, y2]`
xyxy (npt.NDArray[np.float64]): An array of shape `(n, 4)` containing the
bounding boxes coordinates in format `[x1, y1, x2, y2]`
offset (np.array): An array of shape `(2,)` containing offset values in format
is `[dx, dy]`.
Returns:
np.ndarray: Repositioned bounding boxes.
npt.NDArray[np.float64]: Repositioned bounding boxes.
Example:
Examples:
```python
import numpy as np
import supervision as sv
boxes = np.array([[10, 10, 20, 20], [30, 30, 40, 40]])
xyxy = np.array([
[10, 10, 20, 20],
[30, 30, 40, 40]
])
offset = np.array([5, 5])
moved_box = sv.move_boxes(boxes, offset)
print(moved_box)
# np.array([
sv.move_boxes(xyxy=xyxy, offset=offset)
# array([
# [15, 15, 25, 25],
# [35, 35, 45, 45]
# [35, 35, 45, 45]
# ])
```
"""
return xyxy + np.hstack([offset, offset])
def scale_boxes(xyxy: np.ndarray, factor: float) -> np.ndarray:
def move_masks(
masks: npt.NDArray[np.bool_],
offset: npt.NDArray[np.int32],
resolution_wh: Tuple[int, int],
) -> npt.NDArray[np.bool_]:
"""
Offset the masks in an array by the specified (x, y) amount.
Args:
masks (npt.NDArray[np.bool_]): A 3D array of binary masks corresponding to the
predictions. Shape: `(N, H, W)`, where N is the number of predictions, and
H, W are the dimensions of each mask.
offset (npt.NDArray[np.int32]): An array of shape `(2,)` containing non-negative
int values `[dx, dy]`.
resolution_wh (Tuple[int, int]): The width and height of the desired mask
resolution.
Returns:
(npt.NDArray[np.bool_]) repositioned masks, optionally padded to the specified
shape.
"""
if offset[0] < 0 or offset[1] < 0:
raise ValueError(f"Offset values must be non-negative integers. Got: {offset}")
mask_array = np.full((masks.shape[0], resolution_wh[1], resolution_wh[0]), False)
mask_array[
:,
offset[1] : masks.shape[1] + offset[1],
offset[0] : masks.shape[2] + offset[0],
] = masks
return mask_array
def scale_boxes(
xyxy: npt.NDArray[np.float64], factor: float
) -> npt.NDArray[np.float64]:
"""
Scale the dimensions of bounding boxes.
Parameters:
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes
coordinates in format `[x1, y1, x2, y2]`
xyxy (npt.NDArray[np.float64]): An array of shape `(n, 4)` containing the
bounding boxes coordinates in format `[x1, y1, x2, y2]`
factor (float): A float value representing the factor by which the box
dimensions are scaled. A factor greater than 1 enlarges the boxes, while a
factor less than 1 shrinks them.
Returns:
np.ndarray: Scaled bounding boxes.
npt.NDArray[np.float64]: Scaled bounding boxes.
Example:
Examples:
```python
import numpy as np
import supervision as sv
boxes = np.array([[10, 10, 20, 20], [30, 30, 40, 40]])
factor = 1.5
scaled_bb = sv.scale_boxes(boxes, factor)
print(scaled_bb)
# np.array([
xyxy = np.array([
[10, 10, 20, 20],
[30, 30, 40, 40]
])
sv.scale_boxes(xyxy=xyxy, factor=1.5)
# array([
# [ 7.5, 7.5, 22.5, 22.5],
# [27.5, 27.5, 42.5, 42.5]
# ])
@@ -656,102 +631,6 @@ def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray:
return np.column_stack((centroid_x, centroid_y)).astype(int)
def validate_xyxy(xyxy: Any) -> None:
expected_shape = "(_, 4)"
actual_shape = str(getattr(xyxy, "shape", None))
is_valid = isinstance(xyxy, np.ndarray) and xyxy.ndim == 2 and xyxy.shape[1] == 4
if not is_valid:
raise ValueError(
f"xyxy must be a 2D np.ndarray with shape {expected_shape}, but got shape "
f"{actual_shape}"
)
def validate_mask(mask: Any, n: int) -> None:
expected_shape = f"({n}, H, W)"
actual_shape = str(getattr(mask, "shape", None))
is_valid = mask is None or (
isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n
)
if not is_valid:
raise ValueError(
f"mask must be a 3D np.ndarray with shape {expected_shape}, but got shape "
f"{actual_shape}"
)
def validate_class_id(class_id: Any, n: int) -> None:
expected_shape = f"({n},)"
actual_shape = str(getattr(class_id, "shape", None))
is_valid = class_id is None or (
isinstance(class_id, np.ndarray) and class_id.shape == (n,)
)
if not is_valid:
raise ValueError(
f"class_id must be a 1D np.ndarray with shape {expected_shape}, but got "
f"shape {actual_shape}"
)
def validate_confidence(confidence: Any, n: int) -> None:
expected_shape = f"({n},)"
actual_shape = str(getattr(confidence, "shape", None))
is_valid = confidence is None or (
isinstance(confidence, np.ndarray) and confidence.shape == (n,)
)
if not is_valid:
raise ValueError(
f"confidence must be a 1D np.ndarray with shape {expected_shape}, but got "
f"shape {actual_shape}"
)
def validate_tracker_id(tracker_id: Any, n: int) -> None:
expected_shape = f"({n},)"
actual_shape = str(getattr(tracker_id, "shape", None))
is_valid = tracker_id is None or (
isinstance(tracker_id, np.ndarray) and tracker_id.shape == (n,)
)
if not is_valid:
raise ValueError(
f"tracker_id must be a 1D np.ndarray with shape {expected_shape}, but got "
f"shape {actual_shape}"
)
def validate_data(data: Dict[str, Any], n: int) -> None:
for key, value in data.items():
if isinstance(value, list):
if len(value) != n:
raise ValueError(f"Length of list for key '{key}' must be {n}")
elif isinstance(value, np.ndarray):
if value.ndim == 1 and value.shape[0] != n:
raise ValueError(f"Shape of np.ndarray for key '{key}' must be ({n},)")
elif value.ndim > 1 and value.shape[0] != n:
raise ValueError(
f"First dimension of np.ndarray for key '{key}' must have size {n}"
)
else:
raise ValueError(f"Value for key '{key}' must be a list or np.ndarray")
def validate_detections_fields(
xyxy: Any,
mask: Any,
class_id: Any,
confidence: Any,
tracker_id: Any,
data: Dict[str, Any],
) -> None:
validate_xyxy(xyxy)
n = len(xyxy)
validate_mask(mask, n)
validate_class_id(class_id, n)
validate_confidence(confidence, n)
validate_tracker_id(tracker_id, n)
validate_data(data, n)
def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray]) -> bool:
"""
Compares the data payloads of two Detections instances.
@@ -768,17 +647,19 @@ def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray])
def merge_data(
data_list: List[Dict[str, Union[np.ndarray, List]]],
) -> Dict[str, Union[np.ndarray, List]]:
data_list: List[Dict[str, Union[npt.NDArray[np.generic], List]]],
) -> Dict[str, Union[npt.NDArray[np.generic], List]]:
"""
Merges the data payloads of a list of Detections instances.
Args:
data_list: The data payloads of the instances.
data_list: The data payloads of the Detections instances. Each data payload
is a dictionary with the same keys, and the values are either lists or
npt.NDArray[np.generic].
Returns:
A single data payload containing the merged data, preserving the original data
types (list or np.ndarray).
types (list or npt.NDArray[np.generic]).
Raises:
ValueError: If data values within a single object have different lengths or if
@@ -799,9 +680,8 @@ def merge_data(
)
merged_data = {key: [] for key in all_keys_sets[0]}
for data in data_list:
for key in merged_data:
for key in data:
merged_data[key].append(data[key])
for key in merged_data:
@@ -845,8 +725,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:
@@ -855,3 +742,138 @@ def get_data_item(
raise TypeError(f"Unsupported data type for key '{key}': {type(value)}")
return subset_data
def contains_holes(mask: npt.NDArray[np.bool_]) -> bool:
"""
Checks if the binary mask contains holes (background pixels fully enclosed by
foreground pixels).
Args:
mask (npt.NDArray[np.bool_]): 2D binary mask where `True` indicates foreground
object and `False` indicates background.
Returns:
True if holes are detected, False otherwise.
Examples:
```python
import numpy as np
import supervision as sv
mask = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
]).astype(bool)
sv.contains_holes(mask=mask)
# True
mask = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
]).astype(bool)
sv.contains_holes(mask=mask)
# False
```
![contains_holes](https://media.roboflow.com/supervision-docs/contains-holes.png){ align=center width="800" }
""" # noqa E501 // docs
mask_uint8 = mask.astype(np.uint8)
_, hierarchy = cv2.findContours(mask_uint8, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
if hierarchy is not None:
parent_contour_index = 3
for h in hierarchy[0]:
if h[parent_contour_index] != -1:
return True
return False
def contains_multiple_segments(
mask: npt.NDArray[np.bool_], connectivity: int = 4
) -> bool:
"""
Checks if the binary mask contains multiple unconnected foreground segments.
Args:
mask (npt.NDArray[np.bool_]): 2D binary mask where `True` indicates foreground
object and `False` indicates background.
connectivity (int) : Default: 4 is 4-way connectivity, which means that
foreground pixels are the part of the same segment/component
if their edges touch.
Alternatively: 8 for 8-way connectivity, when foreground pixels are
connected by their edges or corners touch.
Returns:
True when the mask contains multiple not connected components, False otherwise.
Raises:
ValueError: If connectivity(int) parameter value is not 4 or 8.
Examples:
```python
import numpy as np
import supervision as sv
mask = np.array([
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 1, 1],
[0, 1, 1, 0, 1, 1],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0]
]).astype(bool)
sv.contains_multiple_segments(mask=mask, connectivity=4)
# True
mask = np.array([
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0]
]).astype(bool)
sv.contains_multiple_segments(mask=mask, connectivity=4)
# False
```
![contains_multiple_segments](https://media.roboflow.com/supervision-docs/contains-multiple-segments.png){ align=center width="800" }
""" # noqa E501 // docs
if connectivity != 4 and connectivity != 8:
raise ValueError(
"Incorrect connectivity value. Possible connectivity values: 4 or 8."
)
mask_uint8 = mask.astype(np.uint8)
labels = np.zeros_like(mask_uint8, dtype=np.int32)
number_of_labels, _ = cv2.connectedComponents(
mask_uint8, labels, connectivity=connectivity
)
return number_of_labels > 2
def cross_product(anchors: np.ndarray, vector: Vector) -> np.ndarray:
"""
Get array of cross products of each anchor with a vector.
Args:
anchors: Array of anchors of shape (number of anchors, detections, 2)
vector: Vector to calculate cross product with
Returns:
Array of cross products of shape (number of anchors, detections)
"""
vector_at_zero = np.array(
[vector.end.x - vector.start.x, vector.end.y - vector.start.y]
)
vector_start = np.array([vector.start.x, vector.start.y])
return np.cross(vector_at_zero, anchors - vector_start)
+78 -10
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Tuple
from typing import List, Tuple, Union
import matplotlib.pyplot as plt
@@ -104,10 +104,13 @@ class Color:
Create a Color instance from a hex string.
Args:
color_hex (str): Hex string of the color.
color_hex (str): The hex string representing the color. This string can
start with '#' followed by either 3 or 6 hexadecimal characters. In
case of 3 characters, each character is repeated to form the full
6-character hex code.
Returns:
Color: Instance representing the color.
Color: An instance representing the color.
Example:
```python
@@ -115,6 +118,9 @@ class Color:
sv.Color.from_hex('#ff00ff')
# Color(r=255, g=0, b=255)
sv.Color.from_hex('#f0f')
# Color(r=255, g=0, b=255)
```
"""
_validate_color_hex(color_hex)
@@ -124,6 +130,52 @@ class Color:
r, g, b = (int(color_hex[i : i + 2], 16) for i in range(0, 6, 2))
return cls(r, g, b)
@classmethod
def from_rgb_tuple(cls, color_tuple: Tuple[int, int, int]) -> Color:
"""
Create a Color instance from an RGB tuple.
Args:
color_tuple (Tuple[int, int, int]): A tuple representing the color in RGB
format, where each element is an integer in the range 0-255.
Returns:
Color: An instance representing the color.
Example:
```python
import supervision as sv
sv.Color.from_rgb_tuple((255, 255, 0))
# Color(r=255, g=255, b=0)
```
"""
r, g, b = color_tuple
return cls(r=r, g=g, b=b)
@classmethod
def from_bgr_tuple(cls, color_tuple: Tuple[int, int, int]) -> Color:
"""
Create a Color instance from a BGR tuple.
Args:
color_tuple (Tuple[int, int, int]): A tuple representing the color in BGR
format, where each element is an integer in the range 0-255.
Returns:
Color: An instance representing the color.
Example:
```python
import supervision as sv
sv.Color.from_bgr_tuple((0, 255, 255))
# Color(r=255, g=255, b=0)
```
"""
b, g, r = color_tuple
return cls(r=r, g=g, b=b)
def as_hex(self) -> str:
"""
Converts the Color instance to a hex string.
@@ -176,31 +228,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
@@ -396,3 +448,19 @@ class ColorPalette:
raise ValueError("idx argument should not be negative")
idx = idx % len(self.colors)
return self.colors[idx]
def unify_to_bgr(color: Union[Tuple[int, int, int], Color]) -> Tuple[int, int, int]:
"""
Converts a color input in multiple formats to a standardized BGR format.
Args:
color (Union[Tuple[int, int, int], Color]): The color input to be converted,
which can be either a tuple of RGB values or an instance of a Color class.
Returns:
Tuple[int, int, int]: The color in BGR format as a tuple of three integers.
"""
if issubclass(type(color), Color):
return color.as_bgr()
return color
+60 -16
View File
@@ -81,6 +81,58 @@ def draw_filled_rectangle(scene: np.ndarray, rect: Rect, color: Color) -> np.nda
return scene
def draw_rounded_rectangle(
scene: np.ndarray,
rect: Rect,
color: Color,
border_radius: int,
) -> np.ndarray:
"""
Draws a rounded rectangle on an image.
Parameters:
scene (np.ndarray): The image on which the rounded rectangle will be drawn.
rect (Rect): The rectangle to be drawn.
color (Color): The color of the rounded rectangle.
border_radius (int): The radius of the corner rounding.
Returns:
np.ndarray: The image with the rounded rectangle drawn on it.
"""
x1, y1, x2, y2 = rect.as_xyxy_int_tuple()
width, height = x2 - x1, y2 - y1
border_radius = min(border_radius, min(width, height) // 2)
rectangle_coordinates = [
((x1 + border_radius, y1), (x2 - border_radius, y2)),
((x1, y1 + border_radius), (x2, y2 - border_radius)),
]
circle_centers = [
(x1 + border_radius, y1 + border_radius),
(x2 - border_radius, y1 + border_radius),
(x1 + border_radius, y2 - border_radius),
(x2 - border_radius, y2 - border_radius),
]
for coordinates in rectangle_coordinates:
cv2.rectangle(
img=scene,
pt1=coordinates[0],
pt2=coordinates[1],
color=color.as_bgr(),
thickness=-1,
)
for center in circle_centers:
cv2.circle(
img=scene,
center=center,
radius=border_radius,
color=color.as_bgr(),
thickness=-1,
)
return scene
def draw_polygon(
scene: np.ndarray, polygon: np.ndarray, color: Color, thickness: int = 2
) -> np.ndarray:
@@ -238,13 +290,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 +304,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
+13
View File
@@ -98,6 +98,11 @@ class Rect:
width: float
height: float
@classmethod
def from_xyxy(cls, xyxy: Tuple[float, float, float, float]) -> Rect:
x1, y1, x2, y2 = xyxy
return cls(x=x1, y=y1, width=x2 - x1, height=y2 - y1)
@property
def top_left(self) -> Point:
return Point(x=self.x, y=self.y)
@@ -113,3 +118,11 @@ class Rect:
width=self.width + 2 * padding,
height=self.height + 2 * padding,
)
def as_xyxy_int_tuple(self) -> Tuple[int, int, int, int]:
return (
int(self.x),
int(self.y),
int(self.x + self.width),
int(self.y + self.height),
)
+18 -11
View File
@@ -5,12 +5,8 @@ from supervision.geometry.core import Point
def get_polygon_center(polygon: np.ndarray) -> Point:
"""
Calculate the center of a polygon.
This function takes in a polygon as a 2-dimensional numpy ndarray and
returns the center of the polygon as a Point object.
The center is calculated as the mean of the polygon's vertices along each axis,
and is rounded down to the nearest integer.
Calculate the center of a polygon. The center is calculated as the center
of the solid figure formed by the points of the polygon
Parameters:
polygon (np.ndarray): A 2-dimensional numpy ndarray representing the
@@ -22,13 +18,24 @@ def get_polygon_center(polygon: np.ndarray) -> Point:
Examples:
```python
from supervision.geometry.utils import get_polygon_center
import numpy as np
import supervision as sv
vertices = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])
get_center(vertices)
Point(x=0.5, y=0.5)
polygon = np.array([[0, 0], [0, 2], [2, 2], [2, 0]])
sv.get_polygon_center(polygon=polygon)
# Point(x=1, y=1)
```
"""
center = np.mean(polygon, axis=0).astype(int)
# This is one of the 3 candidate algorithms considered for centroid calculation.
# For a more detailed discussion, see PR #1084 and commit eb33176
shift_polygon = np.roll(polygon, -1, axis=0)
signed_areas = np.cross(polygon, shift_polygon) / 2
if signed_areas.sum() == 0:
center = np.mean(polygon, axis=0).round()
return Point(x=center[0], y=center[1])
centroids = (polygon + shift_polygon) / 3.0
center = np.average(centroids, axis=0, weights=signed_areas).round()
return Point(x=center[0], y=center[1])
View File
+418
View File
@@ -0,0 +1,418 @@
from abc import ABC, abstractmethod
from logging import warn
from typing import List, Optional, Tuple, Union
import cv2
import numpy as np
from supervision import Rect, pad_boxes
from supervision.annotators.base import ImageType
from supervision.draw.color import Color
from supervision.draw.utils import draw_rounded_rectangle
from supervision.keypoint.core import KeyPoints
from supervision.keypoint.skeletons import SKELETONS_BY_VERTEX_COUNT
from supervision.utils.conversion import convert_for_annotation_method
class BaseKeyPointAnnotator(ABC):
@abstractmethod
def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
pass
class VertexAnnotator(BaseKeyPointAnnotator):
"""
A class that specializes in drawing skeleton vertices on images. It uses
specified key points to determine the locations where the vertices should be
drawn.
"""
def __init__(
self,
color: Color = Color.ROBOFLOW,
radius: int = 4,
) -> None:
"""
Args:
color (Color, optional): The color to use for annotating key points.
radius (int, optional): The radius of the circles used to represent the key
points.
"""
self.color = color
self.radius = radius
@convert_for_annotation_method
def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
"""
Annotates the given scene with skeleton vertices based on the provided key
points. It draws circles at each key point location.
Args:
scene (ImageType): The image where skeleton vertices will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray` or
`PIL.Image.Image`.
key_points (KeyPoints): A collection of key points where each key point
consists of x and y coordinates.
Returns:
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
vertex_annotator = sv.VertexAnnotator(
color=sv.Color.GREEN,
radius=10
)
annotated_frame = vertex_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
![vertex-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/vertex-annotator-example.png)
"""
if len(key_points) == 0:
return scene
for xy in key_points.xy:
for x, y in xy:
cv2.circle(
img=scene,
center=(int(x), int(y)),
radius=self.radius,
color=self.color.as_bgr(),
thickness=-1,
)
return scene
class EdgeAnnotator(BaseKeyPointAnnotator):
"""
A class that specializes in drawing skeleton edges on images using specified key
points. It connects key points with lines to form the skeleton structure.
"""
def __init__(
self,
color: Color = Color.ROBOFLOW,
thickness: int = 2,
edges: Optional[List[Tuple[int, int]]] = None,
) -> None:
"""
Args:
color (Color, optional): The color to use for the edges.
thickness (int, optional): The thickness of the edges.
edges (Optional[List[Tuple[int, int]]]): The edges to draw.
If set to `None`, will attempt to select automatically.
"""
self.color = color
self.thickness = thickness
self.edges = edges
@convert_for_annotation_method
def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
"""
Annotates the given scene by drawing lines between specified key points to form
edges.
Args:
scene (ImageType): The image where skeleton edges will be drawn. `ImageType`
is a flexible type, accepting either `numpy.ndarray` or
`PIL.Image.Image`.
key_points (KeyPoints): A collection of key points where each key point
consists of x and y coordinates.
Returns:
Returns:
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
edge_annotator = sv.EdgeAnnotator(
color=sv.Color.GREEN,
thickness=5
)
annotated_frame = edge_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
![edge-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/edge-annotator-example.png)
"""
if len(key_points) == 0:
return scene
for xy in key_points.xy:
edges = self.edges
if not edges:
edges = SKELETONS_BY_VERTEX_COUNT.get(len(xy))
if not edges:
warn(f"No skeleton found with {len(xy)} vertices")
return scene
for class_a, class_b in edges:
xy_a = xy[class_a - 1]
xy_b = xy[class_b - 1]
missing_a = np.allclose(xy_a, 0)
missing_b = np.allclose(xy_b, 0)
if missing_a or missing_b:
continue
cv2.line(
img=scene,
pt1=(int(xy_a[0]), int(xy_a[1])),
pt2=(int(xy_b[0]), int(xy_b[1])),
color=self.color.as_bgr(),
thickness=self.thickness,
)
return scene
class VertexLabelAnnotator:
"""
A class that draws labels of skeleton vertices on images. It uses specified key
points to determine the locations where the vertices should be drawn.
"""
def __init__(
self,
color: Union[Color, List[Color]] = Color.ROBOFLOW,
text_color: Color = Color.WHITE,
text_scale: float = 0.5,
text_thickness: int = 1,
text_padding: int = 10,
border_radius: int = 0,
):
"""
Args:
color (Union[Color, List[Color]], optional): The color to use for each
keypoint label. If a list is provided, the colors will be used in order
for each keypoint.
text_color (Color, optional): The color to use for the labels.
text_scale (float, optional): The scale of the text.
text_thickness (int, optional): The thickness of the text.
text_padding (int, optional): The padding around the text.
border_radius (int, optional): The radius of the rounded corners of the
boxes. Set to a high value to produce circles.
"""
self.border_radius: int = border_radius
self.color: Union[Color, List[Color]] = color
self.text_color: Color = text_color
self.text_scale: float = text_scale
self.text_thickness: int = text_thickness
self.text_padding: int = text_padding
def annotate(
self, scene: ImageType, key_points: KeyPoints, labels: List[str] = None
) -> ImageType:
"""
A class that draws labels of skeleton vertices on images. It uses specified key
points to determine the locations where the vertices should be drawn.
Args:
scene (ImageType): The image where vertex labels will be drawn. `ImageType`
is a flexible type, accepting either `numpy.ndarray` or
`PIL.Image.Image`.
key_points (KeyPoints): A collection of key points where each key point
consists of x and y coordinates.
labels (List[str], optional): A list of labels to be displayed on the
annotated image. If not provided, keypoint indices will be used.
Returns:
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
vertex_label_annotator = sv.VertexLabelAnnotator(
color=sv.Color.GREEN,
text_color=sv.Color.BLACK,
border_radius=5
)
annotated_frame = vertex_label_annotator.annotate(
scene=image.copy(),
key_points=key_points
)
```
![vertex-label-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/vertex-label-annotator-example.png)
!!! tip
`VertexLabelAnnotator` allows to customize the color of each keypoint label
values.
Example:
```python
import supervision as sv
image = ...
key_points = sv.KeyPoints(...)
LABELS = [
"nose", "left eye", "right eye", "left ear",
"right ear", "left shoulder", "right shoulder", "left elbow",
"right elbow", "left wrist", "right wrist", "left hip",
"right hip", "left knee", "right knee", "left ankle",
"right ankle"
]
COLORS = [
"#FF6347", "#FF6347", "#FF6347", "#FF6347",
"#FF6347", "#FF1493", "#00FF00", "#FF1493",
"#00FF00", "#FF1493", "#00FF00", "#FFD700",
"#00BFFF", "#FFD700", "#00BFFF", "#FFD700",
"#00BFFF"
]
COLORS = [sv.Color.from_hex(color_hex=c) for c in COLORS]
vertex_label_annotator = sv.VertexLabelAnnotator(
color=COLORS,
text_color=sv.Color.BLACK,
border_radius=5
)
annotated_frame = vertex_label_annotator.annotate(
scene=image.copy(),
key_points=key_points,
labels=labels
)
```
![vertex-label-annotator-custom-example](https://media.roboflow.com/
supervision-annotator-examples/vertex-label-annotator-custom-example.png)
"""
font = cv2.FONT_HERSHEY_SIMPLEX
skeletons_count, points_count, _ = key_points.xy.shape
if skeletons_count == 0:
return scene
anchors = key_points.xy.reshape(points_count * skeletons_count, 2).astype(int)
mask = np.all(anchors != 0, axis=1)
if not np.any(mask):
return scene
colors = self.preprocess_and_validate_colors(
colors=self.color,
points_count=points_count,
skeletons_count=skeletons_count,
)
labels = self.preprocess_and_validate_labels(
labels=labels, points_count=points_count, skeletons_count=skeletons_count
)
anchors = anchors[mask]
colors = colors[mask]
labels = labels[mask]
xyxy = np.array(
[
self.get_text_bounding_box(
text=label,
font=font,
text_scale=self.text_scale,
text_thickness=self.text_thickness,
center_coordinates=tuple(anchor),
)
for anchor, label in zip(anchors, labels)
]
)
xyxy_padded = pad_boxes(xyxy=xyxy, px=self.text_padding)
for text, color, box, box_padded in zip(labels, colors, xyxy, xyxy_padded):
draw_rounded_rectangle(
scene=scene,
rect=Rect.from_xyxy(box_padded),
color=color,
border_radius=self.border_radius,
)
cv2.putText(
img=scene,
text=text,
org=(box[0], box[1] + self.text_padding),
fontFace=font,
fontScale=self.text_scale,
color=self.text_color.as_rgb(),
thickness=self.text_thickness,
lineType=cv2.LINE_AA,
)
return scene
@staticmethod
def get_text_bounding_box(
text: str,
font: int,
text_scale: float,
text_thickness: int,
center_coordinates: Tuple[int, int],
) -> Tuple[int, int, int, int]:
text_w, text_h = cv2.getTextSize(
text=text,
fontFace=font,
fontScale=text_scale,
thickness=text_thickness,
)[0]
center_x, center_y = center_coordinates
return (
center_x - text_w // 2,
center_y - text_h // 2,
center_x + text_w // 2,
center_y + text_h // 2,
)
@staticmethod
def preprocess_and_validate_labels(
labels: Optional[List[str]], points_count: int, skeletons_count: int
) -> np.array:
if labels and len(labels) != points_count:
raise ValueError(
f"Number of labels ({len(labels)}) must match number of key points "
f"({points_count})."
)
if labels is None:
labels = [str(i) for i in range(points_count)]
return np.array(labels * skeletons_count)
@staticmethod
def preprocess_and_validate_colors(
colors: Optional[Union[Color, List[Color]]],
points_count: int,
skeletons_count: int,
) -> np.array:
if isinstance(colors, list) and len(colors) != points_count:
raise ValueError(
f"Number of colors ({len(colors)}) must match number of key points "
f"({points_count})."
)
return (
np.array(colors * skeletons_count)
if isinstance(colors, list)
else np.array([colors] * points_count * skeletons_count)
)
+375
View File
@@ -0,0 +1,375 @@
from __future__ import annotations
from contextlib import suppress
from dataclasses import dataclass, field
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
import numpy as np
import numpy.typing as npt
from supervision.config import CLASS_NAME_DATA_FIELD
from supervision.detection.utils import get_data_item, is_data_equal
from supervision.validators import validate_keypoints_fields
@dataclass
class KeyPoints:
"""
The `sv.KeyPoints` class in the Supervision library standardizes results from
various keypoint detection and pose estimation models into a consistent format. This
class simplifies data manipulation and filtering, providing a uniform API for
integration with Supervision annotators.
=== "Ultralytics"
Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics)
method, which accepts model results.
```python
import cv2
import supervision as sv
from ultralytics import YOLO
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s-pose.pt')
result = model(image)[0]
key_points = sv.KeyPoints.from_ultralytics(result)
```
Attributes:
xy (np.ndarray): An array of shape `(n, 2)` containing
the bounding boxes coordinates in format `[x1, y1]`
confidence (Optional[np.ndarray]): An array of shape
`(n,)` containing the confidence scores of the keypoint keypoints.
class_id (Optional[np.ndarray]): An array of shape
`(n,)` containing the class ids of the keypoint keypoints.
data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional
data where each key is a string representing the data type, and the value
is either a NumPy array or a list of corresponding data.
""" # noqa: E501 // docs
xy: npt.NDArray[np.float32]
class_id: Optional[npt.NDArray[np.int_]] = None
confidence: Optional[npt.NDArray[np.float32]] = None
data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict)
def __post_init__(self):
validate_keypoints_fields(
xy=self.xy,
confidence=self.confidence,
class_id=self.class_id,
data=self.data,
)
def __len__(self) -> int:
"""
Returns the number of keypoints in the keypoints object.
"""
return len(self.xy)
def __iter__(
self,
) -> Iterator[
Tuple[
np.ndarray,
Optional[np.ndarray],
Optional[float],
Optional[int],
Optional[int],
Dict[str, Union[np.ndarray, List]],
]
]:
"""
Iterates over the Keypoint object and yield a tuple of
`(xy, confidence, class_id, data)` for each keypoint detection.
"""
for i in range(len(self.xy)):
yield (
self.xy[i],
self.confidence[i] if self.confidence is not None else None,
self.class_id[i] if self.class_id is not None else None,
get_data_item(self.data, i),
)
def __eq__(self, other: KeyPoints) -> bool:
return all(
[
np.array_equal(self.xy, other.xy),
np.array_equal(self.class_id, other.class_id),
np.array_equal(self.confidence, other.confidence),
is_data_equal(self.data, other.data),
]
)
@classmethod
def from_inference(cls, inference_result: Union[dict, Any]) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the [Roboflow](https://roboflow.com/)
API inference result or the [Inference](https://inference.roboflow.com/)
package results. When a keypoint detection model is used, this method
extracts the keypoint coordinates, class IDs, confidences, and class names.
Args:
inference_result (dict, any): The result from the
Roboflow API or Inference package containing predictions with keypoints.
Returns:
(KeyPoints): A KeyPoints object containing the keypoint coordinates,
class IDs, and confidences of each keypoint.
Example:
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id=<POSE_MODEL_ID>, api_key=<ROBOFLOW_API_KEY>)
result = model.infer(image)[0]
key_points = sv.KeyPoints.from_inference(result)
```
```python
import cv2
import supervision as sv
from inference_sdk import InferenceHTTPClient
image = cv2.imread(<SOURCE_IMAGE_PATH>)
client = InferenceHTTPClient(
api_url="https://detect.roboflow.com",
api_key=<ROBOFLOW_API_KEY>
)
result = client.infer(image, model_id=<POSE_MODEL_ID>)
key_points = sv.KeyPoints.from_inference(result)
```
"""
if isinstance(inference_result, list):
raise ValueError(
"from_inference() operates on a single result at a time."
"You can retrieve it like so: inference_result = model.infer(image)[0]"
)
# Unpack the result if received from inference.get_model,
# rather than inference_sdk.InferenceHTTPClient
with suppress(AttributeError):
inference_result = inference_result.dict(exclude_none=True, by_alias=True)
if not inference_result.get("predictions"):
return cls.empty()
xy = []
confidence = []
class_id = []
class_names = []
for prediction in inference_result["predictions"]:
prediction_xy = []
prediction_confidence = []
for keypoint in prediction["keypoints"]:
prediction_xy.append([keypoint["x"], keypoint["y"]])
prediction_confidence.append(keypoint["confidence"])
xy.append(prediction_xy)
confidence.append(prediction_confidence)
class_id.append(prediction["class_id"])
class_names.append(prediction["class"])
data = {CLASS_NAME_DATA_FIELD: np.array(class_names)}
return cls(
xy=np.array(xy, dtype=np.float32),
confidence=np.array(confidence, dtype=np.float32),
class_id=np.array(class_id, dtype=int),
data=data,
)
@classmethod
def from_ultralytics(cls, ultralytics_results) -> KeyPoints:
"""
Creates a KeyPoints instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
Args:
ultralytics_results (ultralytics.engine.results.Keypoints):
The output Results instance from YOLOv8
Returns:
KeyPoints: A new KeyPoints object.
Example:
```python
import cv2
import supervision as sv
from ultralytics import YOLO
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s-pose.pt')
result = model(image)[0]
keypoints = sv.KeyPoints.from_ultralytics(result)
```
"""
if ultralytics_results.keypoints.xy.numel() == 0:
return cls.empty()
xy = ultralytics_results.keypoints.xy.cpu().numpy()
class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int)
class_names = np.array([ultralytics_results.names[i] for i in class_id])
confidence = ultralytics_results.keypoints.conf.cpu().numpy()
data = {CLASS_NAME_DATA_FIELD: class_names}
return cls(xy, class_id, confidence, data)
@classmethod
def from_yolo_nas(cls, yolo_nas_results) -> KeyPoints:
"""
Create a KeyPoints instance from a YOLO NAS results.
Args:
yolo_nas_results (ImagePoseEstimationPrediction):
The output object from YOLO NAS.
Returns:
KeyPoints: A new KeyPoints object.
Example:
```python
import cv2
import torch
import supervision as sv
import super_gradients
image = cv2.imread(<SOURCE_IMAGE_PATH>)
device = "cuda" if torch.cuda.is_available() else "cpu"
yolo_nas = super_gradients.training.models.get(
"yolo_nas_pose_s", pretrained_weights="coco_pose").to(device)
results = yolo_nas.predict(image, conf=0.1)
keypoints = sv.KeyPoints.from_yolo_nas(results)
```
"""
if len(yolo_nas_results.prediction.poses) == 0:
return cls.empty()
xy = yolo_nas_results.prediction.poses[:, :, :2]
confidence = yolo_nas_results.prediction.poses[:, :, 2]
# yolo_nas_results treats params differently.
# prediction.labels may not exist, whereas class_names might be None
if hasattr(yolo_nas_results.prediction, "labels"):
class_id = yolo_nas_results.prediction.labels # np.array[int]
else:
class_id = None
data = {}
if class_id is not None and yolo_nas_results.class_names is not None:
class_names = []
for c_id in class_id:
name = yolo_nas_results.class_names[c_id] # tuple[str]
class_names.append(name)
data[CLASS_NAME_DATA_FIELD] = class_names
return cls(
xy=xy,
confidence=confidence,
class_id=class_id,
data=data,
)
def __getitem__(
self, index: Union[int, slice, List[int], np.ndarray, str]
) -> Union[KeyPoints, List, np.ndarray, None]:
"""
Get a subset of the KeyPoints object or access an item from its data field.
When provided with an integer, slice, list of integers, or a numpy array, this
method returns a new KeyPoints object that represents a subset of the original
keypoints. When provided with a string, it accesses the corresponding item in
the data dictionary.
Args:
index (Union[int, slice, List[int], np.ndarray, str]): The index, indices,
or key to access a subset of the KeyPoints or an item from the data.
Returns:
Union[KeyPoints, Any]: A subset of the KeyPoints object or an item from
the data field.
Example:
```python
import supervision as sv
keypoints = sv.KeyPoints()
first_detection = keypoints[0]
first_10_keypoints = keypoints[0:10]
some_keypoints = keypoints[[0, 2, 4]]
class_0_keypoints = keypoints[keypoints.class_id == 0]
high_confidence_keypoints = keypoints[keypoints.confidence > 0.5]
feature_vector = keypoints['feature_vector']
```
"""
if isinstance(index, str):
return self.data.get(index)
if isinstance(index, int):
index = [index]
return KeyPoints(
xy=self.xy[index],
confidence=self.confidence[index] if self.confidence is not None else None,
class_id=self.class_id[index] if self.class_id is not None else None,
data=get_data_item(self.data, index),
)
def __setitem__(self, key: str, value: Union[np.ndarray, List]):
"""
Set a value in the data dictionary of the KeyPoints object.
Args:
key (str): The key in the data dictionary to set.
value (Union[np.ndarray, List]): The value to set for the key.
Example:
```python
import cv2
import supervision as sv
from ultralytics import YOLO
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s.pt')
result = model(image)[0]
keypoints = sv.KeyPoints.from_ultralytics(result)
keypoints['names'] = [
model.model.names[class_id]
for class_id
in keypoints.class_id
]
```
"""
if not isinstance(value, (np.ndarray, list)):
raise TypeError("Value must be a np.ndarray or a list")
if isinstance(value, list):
value = np.array(value)
self.data[key] = value
@classmethod
def empty(cls) -> KeyPoints:
"""
Create an empty Keypoints object with no keypoints.
Returns:
(KeyPoints): An empty Keypoints object.
Example:
```python
from supervision import Keypoints
empty_keypoints = Keypoints.empty()
```
"""
return cls(xy=np.empty((0, 0, 2), dtype=np.float32))
+36
View File
@@ -0,0 +1,36 @@
from enum import Enum
from typing import Dict, List, Tuple
Edges = List[Tuple[int, int]]
class Skeleton(Enum):
COCO = [
(1, 2),
(1, 3),
(2, 3),
(2, 4),
(3, 5),
(6, 12),
(6, 7),
(6, 8),
(7, 13),
(7, 9),
(8, 10),
(9, 11),
(12, 13),
(14, 12),
(15, 13),
(16, 14),
(17, 15),
]
SKELETONS_BY_EDGE_COUNT: Dict[int, Edges] = {}
SKELETONS_BY_VERTEX_COUNT: Dict[int, Edges] = {}
for skeleton in Skeleton:
SKELETONS_BY_EDGE_COUNT[len(skeleton.value)] = skeleton.value
unique_vertices = set(vertex for edge in skeleton.value for vertex in edge)
SKELETONS_BY_VERTEX_COUNT[len(unique_vertices)] = skeleton.value
+72 -37
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
@@ -11,8 +12,9 @@ from supervision.utils.internal import deprecated_parameter
class STrack(BaseTrack):
shared_kalman = KalmanFilter()
_external_count = 0
def __init__(self, tlwh, score, class_ids):
def __init__(self, tlwh, score, class_ids, minimum_consecutive_frames):
# wait activate
self._tlwh = np.asarray(tlwh, dtype=np.float32)
self.kalman_filter = None
@@ -23,6 +25,10 @@ class STrack(BaseTrack):
self.class_ids = class_ids
self.tracklet_len = 0
self.external_track_id = -1
self.minimum_consecutive_frames = minimum_consecutive_frames
def predict(self):
mean_state = self.mean.copy()
if self.state != TrackState.Tracked:
@@ -52,7 +58,7 @@ class STrack(BaseTrack):
def activate(self, kalman_filter, frame_id):
"""Start a new tracklet"""
self.kalman_filter = kalman_filter
self.track_id = self.next_id()
self.internal_track_id = self.next_id()
self.mean, self.covariance = self.kalman_filter.initiate(
self.tlwh_to_xyah(self._tlwh)
)
@@ -61,6 +67,10 @@ class STrack(BaseTrack):
self.state = TrackState.Tracked
if frame_id == 1:
self.is_activated = True
if self.minimum_consecutive_frames == 1:
self.external_track_id = self.next_external_id()
self.frame_id = frame_id
self.start_frame = frame_id
@@ -70,10 +80,10 @@ class STrack(BaseTrack):
)
self.tracklet_len = 0
self.state = TrackState.Tracked
self.is_activated = True
self.frame_id = frame_id
if new_id:
self.track_id = self.next_id()
self.internal_track_id = self.next_id()
self.score = new_track.score
def update(self, new_track, frame_id):
@@ -92,7 +102,10 @@ class STrack(BaseTrack):
self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh)
)
self.state = TrackState.Tracked
self.is_activated = True
if self.tracklet_len == self.minimum_consecutive_frames:
self.is_activated = True
if self.external_track_id == -1:
self.external_track_id = self.next_external_id()
self.score = new_track.score
@@ -130,6 +143,15 @@ class STrack(BaseTrack):
def to_xyah(self):
return self.tlwh_to_xyah(self.tlwh)
@staticmethod
def next_external_id():
STrack._external_count += 1
return STrack._external_count
@staticmethod
def reset_external_counter():
STrack._external_count = 0
@staticmethod
def tlbr_to_tlwh(tlbr):
ret = np.asarray(tlbr).copy()
@@ -143,7 +165,9 @@ class STrack(BaseTrack):
return ret
def __repr__(self):
return "OT_{}_({}-{})".format(self.track_id, self.start_frame, self.end_frame)
return "OT_{}_({}-{})".format(
self.internal_track_id, self.start_frame, self.end_frame
)
def detections2boxes(detections: Detections) -> np.ndarray:
@@ -185,6 +209,10 @@ class ByteTrack:
Increasing minimum_matching_threshold improves accuracy but risks fragmentation.
Decreasing it improves completeness but risks false positives and drift.
frame_rate (int, optional): The frame rate of the video.
minimum_consecutive_frames (int, optional): Number of consecutive frames that an object must
be tracked before it is considered a 'valid' track.
Increasing minimum_consecutive_frames prevents the creation of accidental tracks from
false detection or double detection, but risks missing shorter tracks.
""" # noqa: E501 // docs
@deprecated_parameter(
@@ -217,6 +245,7 @@ class ByteTrack:
lost_track_buffer: int = 30,
minimum_matching_threshold: float = 0.8,
frame_rate: int = 30,
minimum_consecutive_frames: int = 1,
):
self.track_activation_threshold = track_activation_threshold
self.minimum_matching_threshold = minimum_matching_threshold
@@ -224,6 +253,7 @@ class ByteTrack:
self.frame_id = 0
self.det_thresh = self.track_activation_threshold + 0.1
self.max_time_lost = int(frame_rate / 30.0 * lost_track_buffer)
self.minimum_consecutive_frames = minimum_consecutive_frames
self.kalman_filter = KalmanFilter()
self.tracked_tracks: List[STrack] = []
@@ -270,27 +300,31 @@ 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].external_track_id
)
return detections[detections.tracker_id != -1]
else:
detections = Detections.empty()
detections.tracker_id = np.array([], dtype=int)
return detections
return detections
def reset(self):
"""
@@ -306,6 +340,7 @@ class ByteTrack:
self.lost_tracks: List[STrack] = []
self.removed_tracks: List[STrack] = []
BaseTrack.reset_counter()
STrack.reset_external_counter()
def update_with_tensors(self, tensors: np.ndarray) -> List[STrack]:
"""
@@ -343,7 +378,7 @@ class ByteTrack:
if len(dets) > 0:
"""Detections"""
detections = [
STrack(STrack.tlbr_to_tlwh(tlbr), s, c)
STrack(STrack.tlbr_to_tlwh(tlbr), s, c, self.minimum_consecutive_frames)
for (tlbr, s, c) in zip(dets, scores_keep, class_ids_keep)
]
else:
@@ -385,7 +420,7 @@ class ByteTrack:
if len(dets_second) > 0:
"""Detections"""
detections_second = [
STrack(STrack.tlbr_to_tlwh(tlbr), s, c)
STrack(STrack.tlbr_to_tlwh(tlbr), s, c, self.minimum_consecutive_frames)
for (tlbr, s, c) in zip(dets_second, scores_second, class_ids_second)
]
else:
@@ -452,7 +487,7 @@ class ByteTrack:
self.lost_tracks = sub_tracks(self.lost_tracks, self.tracked_tracks)
self.lost_tracks.extend(lost_stracks)
self.lost_tracks = sub_tracks(self.lost_tracks, self.removed_tracks)
self.removed_tracks.extend(removed_stracks)
self.removed_tracks = removed_stracks
self.tracked_tracks, self.lost_tracks = remove_duplicate_tracks(
self.tracked_tracks, self.lost_tracks
)
@@ -466,22 +501,22 @@ def joint_tracks(
) -> List[STrack]:
"""
Joins two lists of tracks, ensuring that the resulting list does not
contain tracks with duplicate track_id values.
contain tracks with duplicate internal_track_id values.
Parameters:
track_list_a: First list of tracks (with track_id attribute).
track_list_b: Second list of tracks (with track_id attribute).
track_list_a: First list of tracks (with internal_track_id attribute).
track_list_b: Second list of tracks (with internal_track_id attribute).
Returns:
Combined list of tracks from track_list_a and track_list_b
without duplicate track_id values.
without duplicate internal_track_id values.
"""
seen_track_ids = set()
result = []
for track in track_list_a + track_list_b:
if track.track_id not in seen_track_ids:
seen_track_ids.add(track.track_id)
if track.internal_track_id not in seen_track_ids:
seen_track_ids.add(track.internal_track_id)
result.append(track)
return result
@@ -490,17 +525,17 @@ def joint_tracks(
def sub_tracks(track_list_a: List, track_list_b: List) -> List[int]:
"""
Returns a list of tracks from track_list_a after removing any tracks
that share the same track_id with tracks in track_list_b.
that share the same internal_track_id with tracks in track_list_b.
Parameters:
track_list_a: List of tracks (with track_id attribute).
track_list_b: List of tracks (with track_id attribute) to
track_list_a: List of tracks (with internal_track_id attribute).
track_list_b: List of tracks (with internal_track_id attribute) to
be subtracted from track_list_a.
Returns:
List of remaining tracks from track_list_a after subtraction.
"""
tracks = {track.track_id: track for track in track_list_a}
track_ids_b = {track.track_id for track in track_list_b}
tracks = {track.internal_track_id: track for track in track_list_a}
track_ids_b = {track.internal_track_id for track in track_list_b}
for track_id in track_ids_b:
tracks.pop(track_id, None)
+103
View File
@@ -0,0 +1,103 @@
from functools import wraps
from typing import List
import cv2
import numpy as np
from PIL import Image
from supervision.annotators.base import ImageType
def convert_for_annotation_method(annotate_func):
"""
Decorates `BaseAnnotator.annotate` implementations, converts scene to
an image type used internally by the annotators, converts back when annotation
is complete.
"""
@wraps(annotate_func)
def wrapper(self, scene: ImageType, *args, **kwargs):
if isinstance(scene, np.ndarray):
return annotate_func(self, scene, *args, **kwargs)
if isinstance(scene, Image.Image):
scene = pillow_to_cv2(scene)
annotated = annotate_func(self, scene, *args, **kwargs)
return cv2_to_pillow(image=annotated)
raise ValueError(f"Unsupported image type: {type(scene)}")
return wrapper
def convert_for_image_processing(image_processing_fun):
"""
Decorates image processing functions that accept np.ndarray, converting `image` to
np.ndarray, converts back when processing is complete.
"""
@wraps(image_processing_fun)
def wrapper(image: ImageType, *args, **kwargs):
if isinstance(image, np.ndarray):
return image_processing_fun(image, *args, **kwargs)
if isinstance(image, Image.Image):
scene = pillow_to_cv2(image)
annotated = image_processing_fun(scene, *args, **kwargs)
return cv2_to_pillow(image=annotated)
raise ValueError(f"Unsupported image type: {type(image)}")
return wrapper
def images_to_cv2(images: List[ImageType]) -> List[np.ndarray]:
"""
Converts images provided either as Pillow images or OpenCV
images into OpenCV format.
Args:
images (List[ImageType]): Images to be converted
Returns:
List[np.ndarray]: List of input images in OpenCV format
(with order preserved).
"""
result = []
for image in images:
if issubclass(type(image), Image.Image):
image = pillow_to_cv2(image=image)
result.append(image)
return result
def pillow_to_cv2(image: Image.Image) -> np.ndarray:
"""
Converts Pillow image into OpenCV image, handling RGB -> BGR
conversion.
Args:
image (Image.Image): Pillow image (in RGB format).
Returns:
(np.ndarray): Input image converted to OpenCV format.
"""
scene = np.array(image)
scene = cv2.cvtColor(scene, cv2.COLOR_RGB2BGR)
return scene
def cv2_to_pillow(image: np.ndarray) -> Image.Image:
"""
Converts OpenCV image into Pillow image, handling BGR -> RGB
conversion.
Args:
image (np.ndarray): OpenCV image (in BGR format).
Returns:
(Image.Image): Input image converted to Pillow format.
"""
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
return Image.fromarray(image)
+654 -55
View File
@@ -1,107 +1,351 @@
import itertools
import math
import os
import shutil
from typing import Optional, Tuple
from functools import partial
from typing import Callable, List, Literal, Optional, Tuple, Union
import cv2
import numpy as np
import numpy.typing as npt
from supervision.annotators.base import ImageType
from supervision.draw.color import Color, unify_to_bgr
from supervision.draw.utils import calculate_optimal_text_scale, draw_text
from supervision.geometry.core import Point
from supervision.utils.conversion import (
convert_for_image_processing,
cv2_to_pillow,
images_to_cv2,
)
from supervision.utils.iterables import create_batches, fill
RelativePosition = Literal["top", "bottom"]
MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3
def crop_image(image: np.ndarray, xyxy: np.ndarray) -> np.ndarray:
@convert_for_image_processing
def crop_image(
image: ImageType,
xyxy: Union[npt.NDArray[int], List[int], Tuple[int, int, int, int]],
) -> ImageType:
"""
Crops the given image based on the given bounding box.
Args:
image (np.ndarray): The image to be cropped, represented as a numpy array.
xyxy (np.ndarray): A numpy array containing the bounding box coordinates
in the format (x1, y1, x2, y2).
image (ImageType): The image to be cropped. `ImageType` is a flexible type,
accepting either `numpy.ndarray` or `PIL.Image.Image`.
xyxy (Union[np.ndarray, List[int], Tuple[int, int, int, int]]): A bounding box
coordinates in the format `(x_min, y_min, x_max, y_max)`, accepted as either
a `numpy.ndarray`, a `list`, or a `tuple`.
Returns:
(np.ndarray): The cropped image as a numpy array.
(ImageType): The cropped image. The type is determined by the input type and
may be either a `numpy.ndarray` or `PIL.Image.Image`.
=== "OpenCV"
Examples:
```python
import cv2
import supervision as sv
detection = sv.Detections(...)
with sv.ImageSink(target_dir_path='target/directory/path') as sink:
for xyxy in detection.xyxy:
cropped_image = sv.crop_image(image=image, xyxy=xyxy)
sink.save_image(image=cropped_image)
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image.shape
# (1080, 1920, 3)
xyxy = [200, 400, 600, 800]
cropped_image = sv.crop_image(image=image, xyxy=xyxy)
cropped_image.shape
# (400, 400, 3)
```
"""
=== "Pillow"
```python
from PIL import Image
import supervision as sv
image = Image.open(<SOURCE_IMAGE_PATH>)
image.size
# (1920, 1080)
xyxy = [200, 400, 600, 800]
cropped_image = sv.crop_image(image=image, xyxy=xyxy)
cropped_image.size
# (400, 400)
```
![crop_image](https://media.roboflow.com/supervision-docs/crop-image.png){ align=center width="800" }
""" # noqa E501 // docs
if isinstance(xyxy, (list, tuple)):
xyxy = np.array(xyxy)
xyxy = np.round(xyxy).astype(int)
x1, y1, x2, y2 = xyxy
return image[y1:y2, x1:x2]
x_min, y_min, x_max, y_max = xyxy.flatten()
return image[y_min:y_max, x_min:x_max]
def resize_image(image: np.ndarray, scale_factor: float) -> np.ndarray:
@convert_for_image_processing
def scale_image(image: ImageType, scale_factor: float) -> ImageType:
"""
Resizes an image by a given scale factor using cv2.INTER_LINEAR interpolation.
Scales the given image based on the given scale factor.
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.
image (ImageType): The image to be scaled. `ImageType` is a flexible type,
accepting either `numpy.ndarray` or `PIL.Image.Image`.
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.
(ImageType): The scaled image. The type is determined by the input type and
may be either a `numpy.ndarray` or `PIL.Image.Image`.
Raises:
ValueError: If the scale factor is non-positive.
=== "OpenCV"
```python
import cv2
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image.shape
# (1080, 1920, 3)
scaled_image = sv.scale_image(image=image, scale_factor=0.5)
scaled_image.shape
# (540, 960, 3)
```
=== "Pillow"
```python
from PIL import Image
import supervision as sv
image = Image.open(<SOURCE_IMAGE_PATH>)
image.size
# (1920, 1080)
scaled_image = sv.scale_image(image=image, scale_factor=0.5)
scaled_image.size
# (960, 540)
```
"""
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)
width_old, height_old = image.shape[1], image.shape[0]
width_new = int(width_old * scale_factor)
height_new = int(height_old * scale_factor)
return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
def place_image(
scene: np.ndarray, image: np.ndarray, anchor: Tuple[int, int]
) -> np.ndarray:
@convert_for_image_processing
def resize_image(
image: ImageType,
resolution_wh: Tuple[int, int],
keep_aspect_ratio: bool = False,
) -> ImageType:
"""
Resizes the given image to a specified resolution. Can maintain the original aspect
ratio or resize directly to the desired dimensions.
Args:
image (ImageType): The image to be resized. `ImageType` is a flexible type,
accepting either `numpy.ndarray` or `PIL.Image.Image`.
resolution_wh (Tuple[int, int]): The target resolution as
`(width, height)`.
keep_aspect_ratio (bool, optional): Flag to maintain the image's original
aspect ratio. Defaults to `False`.
Returns:
(ImageType): The resized image. The type is determined by the input type and
may be either a `numpy.ndarray` or `PIL.Image.Image`.
=== "OpenCV"
```python
import cv2
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image.shape
# (1080, 1920, 3)
resized_image = sv.resize_image(
image=image, resolution_wh=(1000, 1000), keep_aspect_ratio=True
)
resized_image.shape
# (562, 1000, 3)
```
=== "Pillow"
```python
from PIL import Image
import supervision as sv
image = Image.open(<SOURCE_IMAGE_PATH>)
image.size
# (1920, 1080)
resized_image = sv.resize_image(
image=image, resolution_wh=(1000, 1000), keep_aspect_ratio=True
)
resized_image.size
# (1000, 562)
```
![resize_image](https://media.roboflow.com/supervision-docs/resize-image.png){ align=center width="800" }
""" # noqa E501 // docs
if keep_aspect_ratio:
image_ratio = image.shape[1] / image.shape[0]
target_ratio = resolution_wh[0] / resolution_wh[1]
if image_ratio >= target_ratio:
width_new = resolution_wh[0]
height_new = int(resolution_wh[0] / image_ratio)
else:
height_new = resolution_wh[1]
width_new = int(resolution_wh[1] * image_ratio)
else:
width_new, height_new = resolution_wh
return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
@convert_for_image_processing
def letterbox_image(
image: ImageType,
resolution_wh: Tuple[int, int],
color: Union[Tuple[int, int, int], Color] = Color.BLACK,
) -> ImageType:
"""
Resizes and pads an image to a specified resolution with a given color, maintaining
the original aspect ratio.
Args:
image (ImageType): The image to be resized. `ImageType` is a flexible type,
accepting either `numpy.ndarray` or `PIL.Image.Image`.
resolution_wh (Tuple[int, int]): The target resolution as
`(width, height)`.
color (Union[Tuple[int, int, int], Color]): The color to pad with. If tuple
provided it should be in BGR format.
Returns:
(ImageType): The resized image. The type is determined by the input type and
may be either a `numpy.ndarray` or `PIL.Image.Image`.
=== "OpenCV"
```python
import cv2
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image.shape
# (1080, 1920, 3)
letterboxed_image = sv.letterbox_image(image=image, resolution_wh=(1000, 1000))
letterboxed_image.shape
# (1000, 1000, 3)
```
=== "Pillow"
```python
from PIL import Image
import supervision as sv
image = Image.open(<SOURCE_IMAGE_PATH>)
image.size
# (1920, 1080)
letterboxed_image = sv.letterbox_image(image=image, resolution_wh=(1000, 1000))
letterboxed_image.size
# (1000, 1000)
```
![letterbox_image](https://media.roboflow.com/supervision-docs/letterbox-image.png){ align=center width="800" }
""" # noqa E501 // docs
color = unify_to_bgr(color=color)
resized_image = resize_image(
image=image, resolution_wh=resolution_wh, keep_aspect_ratio=True
)
height_new, width_new = resized_image.shape[:2]
padding_top = (resolution_wh[1] - height_new) // 2
padding_bottom = resolution_wh[1] - height_new - padding_top
padding_left = (resolution_wh[0] - width_new) // 2
padding_right = resolution_wh[0] - width_new - padding_left
return cv2.copyMakeBorder(
resized_image,
padding_top,
padding_bottom,
padding_left,
padding_right,
cv2.BORDER_CONSTANT,
value=color,
)
def overlay_image(
image: npt.NDArray[np.uint8],
overlay: npt.NDArray[np.uint8],
anchor: Tuple[int, int],
) -> npt.NDArray[np.uint8]:
"""
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
image (np.ndarray): The background scene onto which the image is placed.
overlay (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]
(np.ndarray): The result image with overlay.
Examples:
```python
import cv2
import numpy as np
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
overlay = np.zeros((400, 400, 3), dtype=np.uint8)
result_image = sv.overlay_image(image=image, overlay=overlay, anchor=(200, 400))
```
![overlay_image](https://media.roboflow.com/supervision-docs/overlay-image.png){ align=center width="800" }
""" # noqa E501 // docs
scene_height, scene_width = image.shape[:2]
image_height, image_width = overlay.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
return image
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)
x_min = max(anchor_x, 0)
y_min = max(anchor_y, 0)
x_max = min(scene_width, anchor_x + image_width)
y_max = min(scene_height, anchor_y + image_height)
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)
crop_x_min = max(-anchor_x, 0)
crop_y_min = max(-anchor_y, 0)
crop_x_max = image_width - max((anchor_x + image_width) - scene_width, 0)
crop_y_max = image_height - max((anchor_y + image_height) - scene_height, 0)
scene[start_y:end_y, start_x:end_x] = image[
crop_start_y:crop_end_y, crop_start_x:crop_end_x
image[y_min:y_max, x_min:x_max] = overlay[
crop_y_min:crop_y_max, crop_x_min:crop_x_max
]
return scene
return image
class ImageSink:
@@ -125,13 +369,13 @@ class ImageSink:
```python
import supervision as sv
with sv.ImageSink(target_dir_path='target/directory/path',
overwrite=True) as sink:
for image in sv.get_video_frames_generator(
source_path='source_video.mp4', stride=2):
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>, stride=2)
with sv.ImageSink(target_dir_path=<TARGET_CROPS_DIRECTORY>) as sink:
for image in frames_generator:
sink.save_image(image=image)
```
"""
""" # noqa E501 // docs
self.target_dir_path = target_dir_path
self.overwrite = overwrite
@@ -153,7 +397,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`.
@@ -167,3 +412,357 @@ class ImageSink:
def __exit__(self, exc_type, exc_value, exc_traceback):
pass
def create_tiles(
images: List[ImageType],
grid_size: Optional[Tuple[Optional[int], Optional[int]]] = None,
single_tile_size: Optional[Tuple[int, int]] = None,
tile_scaling: Literal["min", "max", "avg"] = "avg",
tile_padding_color: Union[Tuple[int, int, int], Color] = Color.from_hex("#D9D9D9"),
tile_margin: int = 10,
tile_margin_color: Union[Tuple[int, int, int], Color] = Color.from_hex("#BFBEBD"),
return_type: Literal["auto", "cv2", "pillow"] = "auto",
titles: Optional[List[Optional[str]]] = None,
titles_anchors: Optional[Union[Point, List[Optional[Point]]]] = None,
titles_color: Union[Tuple[int, int, int], Color] = Color.from_hex("#262523"),
titles_scale: Optional[float] = None,
titles_thickness: int = 1,
titles_padding: int = 10,
titles_text_font: int = cv2.FONT_HERSHEY_SIMPLEX,
titles_background_color: Union[Tuple[int, int, int], Color] = Color.from_hex(
"#D9D9D9"
),
default_title_placement: RelativePosition = "top",
) -> ImageType:
"""
Creates tiles mosaic from input images, automating grid placement and
converting images to common resolution maintaining aspect ratio. It is
also possible to render text titles on tiles, using optional set of
parameters specifying text drawing (see parameters description).
Automated grid placement will try to maintain square shape of grid
(with size being the nearest integer square root of #images), up to two exceptions:
* if there are up to 3 images - images will be displayed in single row
* if square-grid placement causes last row to be empty - number of rows is trimmed
until last row has at least one image
Args:
images (List[ImageType]): Images to create tiles. Elements can be either
np.ndarray or PIL.Image, common representation will be agreed by the
function.
grid_size (Optional[Tuple[Optional[int], Optional[int]]]): Expected grid
size in format (n_rows, n_cols). If not given - automated grid placement
will be applied. One may also provide only one out of two elements of the
tuple - then grid will be created with either n_rows or n_cols fixed,
leaving the other dimension to be adjusted by the number of images
single_tile_size (Optional[Tuple[int, int]]): sizeof a single tile element
provided in (width, height) format. If not given - size of tile will be
automatically calculated based on `tile_scaling` parameter.
tile_scaling (Literal["min", "max", "avg"]): If `single_tile_size` is not
given - parameter will be used to calculate tile size - using
min / max / avg size of image provided in `images` list.
tile_padding_color (Union[Tuple[int, int, int], sv.Color]): Color to be used in
images letterbox procedure (while standardising tiles sizes) as a padding.
If tuple provided - should be BGR.
tile_margin (int): size of margin between tiles (in pixels)
tile_margin_color (Union[Tuple[int, int, int], sv.Color]): Color of tile margin.
If tuple provided - should be BGR.
return_type (Literal["auto", "cv2", "pillow"]): Parameter dictates the format of
return image. One may choose specific type ("cv2" or "pillow") to enforce
conversion. "auto" mode takes a majority vote between types of elements in
`images` list - resolving draws in favour of OpenCV format. "auto" can be
safely used when all input images are of the same type.
titles (Optional[List[Optional[str]]]): Optional titles to be added to tiles.
Elements of that list may be empty - then specific tile (in order presented
in `images` parameter) will not be filled with title. It is possible to
provide list of titles shorter than `images` - then remaining titles will
be assumed empty.
titles_anchors (Optional[Union[Point, List[Optional[Point]]]]): Parameter to
specify anchor points for titles. It is possible to specify anchor either
globally or for specific tiles (following order of `images`).
If not given (either globally, or for specific element of the list),
it will be calculated automatically based on `default_title_placement`.
titles_color (Union[Tuple[int, int, int], Color]): Color of titles text.
If tuple provided - should be BGR.
titles_scale (Optional[float]): Scale of titles. If not provided - value will
be calculated using `calculate_optimal_text_scale(...)`.
titles_thickness (int): Thickness of titles text.
titles_padding (int): Size of titles padding.
titles_text_font (int): Font to be used to render titles. Must be integer
constant representing OpenCV font.
(See docs: https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html)
titles_background_color (Union[Tuple[int, int, int], Color]): Color of title
text padding.
default_title_placement (Literal["top", "bottom"]): Parameter specifies title
anchor placement in case if explicit anchor is not provided.
Returns:
ImageType: Image with all input images located in tails grid. The output type is
determined by `return_type` parameter.
Raises:
ValueError: In case when input images list is empty, provided `grid_size` is too
small to fit all images, `tile_scaling` mode is invalid.
"""
if len(images) == 0:
raise ValueError("Could not create image tiles from empty list of images.")
if return_type == "auto":
return_type = _negotiate_tiles_format(images=images)
tile_padding_color = unify_to_bgr(color=tile_padding_color)
tile_margin_color = unify_to_bgr(color=tile_margin_color)
images = images_to_cv2(images=images)
if single_tile_size is None:
single_tile_size = _aggregate_images_shape(images=images, mode=tile_scaling)
resized_images = [
letterbox_image(
image=i, resolution_wh=single_tile_size, color=tile_padding_color
)
for i in images
]
grid_size = _establish_grid_size(images=images, grid_size=grid_size)
if len(images) > grid_size[0] * grid_size[1]:
raise ValueError(
f"Could not place {len(images)} in grid with size: {grid_size}."
)
if titles is not None:
titles = fill(sequence=titles, desired_size=len(images), content=None)
titles_anchors = (
[titles_anchors]
if not issubclass(type(titles_anchors), list)
else titles_anchors
)
titles_anchors = fill(
sequence=titles_anchors, desired_size=len(images), content=None
)
titles_color = unify_to_bgr(color=titles_color)
titles_background_color = unify_to_bgr(color=titles_background_color)
tiles = _generate_tiles(
images=resized_images,
grid_size=grid_size,
single_tile_size=single_tile_size,
tile_padding_color=tile_padding_color,
tile_margin=tile_margin,
tile_margin_color=tile_margin_color,
titles=titles,
titles_anchors=titles_anchors,
titles_color=titles_color,
titles_scale=titles_scale,
titles_thickness=titles_thickness,
titles_padding=titles_padding,
titles_text_font=titles_text_font,
titles_background_color=titles_background_color,
default_title_placement=default_title_placement,
)
if return_type == "pillow":
tiles = cv2_to_pillow(image=tiles)
return tiles
def _negotiate_tiles_format(images: List[ImageType]) -> Literal["cv2", "pillow"]:
number_of_np_arrays = sum(issubclass(type(i), np.ndarray) for i in images)
if number_of_np_arrays >= (len(images) // 2):
return "cv2"
return "pillow"
def _calculate_aggregated_images_shape(
images: List[np.ndarray], aggregator: Callable[[List[int]], float]
) -> Tuple[int, int]:
height = round(aggregator([i.shape[0] for i in images]))
width = round(aggregator([i.shape[1] for i in images]))
return width, height
SHAPE_AGGREGATION_FUN = {
"min": partial(_calculate_aggregated_images_shape, aggregator=np.min),
"max": partial(_calculate_aggregated_images_shape, aggregator=np.max),
"avg": partial(_calculate_aggregated_images_shape, aggregator=np.average),
}
def _aggregate_images_shape(
images: List[np.ndarray], mode: Literal["min", "max", "avg"]
) -> Tuple[int, int]:
if mode not in SHAPE_AGGREGATION_FUN:
raise ValueError(
f"Could not aggregate images shape - provided unknown mode: {mode}. "
f"Supported modes: {list(SHAPE_AGGREGATION_FUN.keys())}."
)
return SHAPE_AGGREGATION_FUN[mode](images)
def _establish_grid_size(
images: List[np.ndarray], grid_size: Optional[Tuple[Optional[int], Optional[int]]]
) -> Tuple[int, int]:
if grid_size is None or all(e is None for e in grid_size):
return _negotiate_grid_size(images=images)
if grid_size[0] is None:
return math.ceil(len(images) / grid_size[1]), grid_size[1]
if grid_size[1] is None:
return grid_size[0], math.ceil(len(images) / grid_size[0])
return grid_size
def _negotiate_grid_size(images: List[np.ndarray]) -> Tuple[int, int]:
if len(images) <= MAX_COLUMNS_FOR_SINGLE_ROW_GRID:
return 1, len(images)
nearest_sqrt = math.ceil(np.sqrt(len(images)))
proposed_columns = nearest_sqrt
proposed_rows = nearest_sqrt
while proposed_columns * (proposed_rows - 1) >= len(images):
proposed_rows -= 1
return proposed_rows, proposed_columns
def _generate_tiles(
images: List[np.ndarray],
grid_size: Tuple[int, int],
single_tile_size: Tuple[int, int],
tile_padding_color: Tuple[int, int, int],
tile_margin: int,
tile_margin_color: Tuple[int, int, int],
titles: Optional[List[Optional[str]]],
titles_anchors: List[Optional[Point]],
titles_color: Tuple[int, int, int],
titles_scale: Optional[float],
titles_thickness: int,
titles_padding: int,
titles_text_font: int,
titles_background_color: Tuple[int, int, int],
default_title_placement: RelativePosition,
) -> np.ndarray:
images = _draw_texts(
images=images,
titles=titles,
titles_anchors=titles_anchors,
titles_color=titles_color,
titles_scale=titles_scale,
titles_thickness=titles_thickness,
titles_padding=titles_padding,
titles_text_font=titles_text_font,
titles_background_color=titles_background_color,
default_title_placement=default_title_placement,
)
rows, columns = grid_size
tiles_elements = list(create_batches(sequence=images, batch_size=columns))
while len(tiles_elements[-1]) < columns:
tiles_elements[-1].append(
_generate_color_image(shape=single_tile_size, color=tile_padding_color)
)
while len(tiles_elements) < rows:
tiles_elements.append(
[_generate_color_image(shape=single_tile_size, color=tile_padding_color)]
* columns
)
return _merge_tiles_elements(
tiles_elements=tiles_elements,
grid_size=grid_size,
single_tile_size=single_tile_size,
tile_margin=tile_margin,
tile_margin_color=tile_margin_color,
)
def _draw_texts(
images: List[np.ndarray],
titles: Optional[List[Optional[str]]],
titles_anchors: List[Optional[Point]],
titles_color: Tuple[int, int, int],
titles_scale: Optional[float],
titles_thickness: int,
titles_padding: int,
titles_text_font: int,
titles_background_color: Tuple[int, int, int],
default_title_placement: RelativePosition,
) -> List[np.ndarray]:
if titles is None:
return images
titles_anchors = _prepare_default_titles_anchors(
images=images,
titles_anchors=titles_anchors,
default_title_placement=default_title_placement,
)
if titles_scale is None:
image_height, image_width = images[0].shape[:2]
titles_scale = calculate_optimal_text_scale(
resolution_wh=(image_width, image_height)
)
result = []
for image, text, anchor in zip(images, titles, titles_anchors):
if text is None:
result.append(image)
continue
processed_image = draw_text(
scene=image,
text=text,
text_anchor=anchor,
text_color=Color.from_bgr_tuple(titles_color),
text_scale=titles_scale,
text_thickness=titles_thickness,
text_padding=titles_padding,
text_font=titles_text_font,
background_color=Color.from_bgr_tuple(titles_background_color),
)
result.append(processed_image)
return result
def _prepare_default_titles_anchors(
images: List[np.ndarray],
titles_anchors: List[Optional[Point]],
default_title_placement: RelativePosition,
) -> List[Point]:
result = []
for image, anchor in zip(images, titles_anchors):
if anchor is not None:
result.append(anchor)
continue
image_height, image_width = image.shape[:2]
if default_title_placement == "top":
default_anchor = Point(x=image_width / 2, y=image_height * 0.1)
else:
default_anchor = Point(x=image_width / 2, y=image_height * 0.9)
result.append(default_anchor)
return result
def _merge_tiles_elements(
tiles_elements: List[List[np.ndarray]],
grid_size: Tuple[int, int],
single_tile_size: Tuple[int, int],
tile_margin: int,
tile_margin_color: Tuple[int, int, int],
) -> np.ndarray:
vertical_padding = (
np.ones((single_tile_size[1], tile_margin, 3)) * tile_margin_color
)
merged_rows = [
np.concatenate(
list(
itertools.chain.from_iterable(
zip(row, [vertical_padding] * grid_size[1])
)
)[:-1],
axis=1,
)
for row in tiles_elements
]
row_width = merged_rows[0].shape[1]
horizontal_padding = (
np.ones((tile_margin, row_width, 3), dtype=np.uint8) * tile_margin_color
)
rows_with_paddings = []
for row in merged_rows:
rows_with_paddings.append(row)
rows_with_paddings.append(horizontal_padding)
return np.concatenate(
rows_with_paddings[:-1],
axis=0,
).astype(np.uint8)
def _generate_color_image(
shape: Tuple[int, int], color: Tuple[int, int, int]
) -> np.ndarray:
return np.ones(shape[::-1] + (3,), dtype=np.uint8) * color
+41 -1
View File
@@ -1,7 +1,8 @@
import functools
import inspect
import os
import warnings
from typing import Callable
from typing import Any, Callable, Set
class SupervisionWarnings(Warning):
@@ -141,3 +142,42 @@ class classproperty(property):
The result of calling the function stored in 'fget' with 'owner_cls'.
"""
return self.fget(owner_cls)
def get_instance_variables(instance: Any, include_properties=False) -> Set[str]:
"""
Get the public variables of a class instance.
Args:
instance (Any): The instance of a class
include_properties (bool): Whether to include properties in the result
Usage:
```python
detections = Detections(xyxy=np.array([1,2,3,4]))
variables = get_class_variables(detections)
# ["xyxy", "mask", "confidence", ..., "data"]
```
"""
if isinstance(instance, type):
raise ValueError("Only class instances are supported, not classes.")
fields = set(
(
name
for name, val in inspect.getmembers(instance)
if not callable(val) and not name.startswith("_")
)
)
if not include_properties:
properties = set(
(
name
for name, val in inspect.getmembers(instance.__class__)
if isinstance(val, property)
)
)
fields -= properties
return fields
+70
View File
@@ -0,0 +1,70 @@
from typing import Generator, Iterable, List, TypeVar
V = TypeVar("V")
def create_batches(
sequence: Iterable[V], batch_size: int
) -> Generator[List[V], None, None]:
"""
Provides a generator that yields chunks of the input sequence
of the size specified by the `batch_size` parameter. The last
chunk may be a smaller batch.
Args:
sequence (Iterable[V]): The sequence to be split into batches.
batch_size (int): The expected size of a batch.
Returns:
(Generator[List[V], None, None]): A generator that yields chunks
of `sequence` of size `batch_size`, up to the length of
the input `sequence`.
Examples:
```python
list(create_batches([1, 2, 3, 4, 5], 2))
# [[1, 2], [3, 4], [5]]
list(create_batches("abcde", 3))
# [['a', 'b', 'c'], ['d', 'e']]
```
"""
batch_size = max(batch_size, 1)
current_batch = []
for element in sequence:
if len(current_batch) == batch_size:
yield current_batch
current_batch = []
current_batch.append(element)
if current_batch:
yield current_batch
def fill(sequence: List[V], desired_size: int, content: V) -> List[V]:
"""
Fill the sequence with padding elements until the sequence reaches
the desired size.
Args:
sequence (List[V]): The input sequence.
desired_size (int): The expected size of the output list. The
difference between this value and the actual length of `sequence`
(if positive) dictates how many elements will be added as padding.
content (V): The element to be placed at the end of the input
`sequence` as padding.
Returns:
(List[V]): A padded version of the input `sequence` (if needed).
Examples:
```python
fill([1, 2], 4, 0)
# [1, 2, 0, 0]
fill(['a', 'b'], 3, 'c')
# ['a', 'b', 'c']
```
"""
missing_size = max(0, desired_size - len(sequence))
sequence.extend([content] * missing_size)
return sequence
+1 -1
View File
@@ -5,7 +5,7 @@ import matplotlib.pyplot as plt
from PIL import Image
from supervision.annotators.base import ImageType
from supervision.annotators.utils import pillow_to_cv2
from supervision.utils.conversion import pillow_to_cv2
def plot_image(
+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):

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