Compare commits

...
285 Commits
Author SHA1 Message Date
SkalskiP 86a13d0f87 bump version from 0.26.0rc6 to 0.26.0rc7 2025-04-25 14:56:18 +02:00
Piotr Skalski 21fc9afbdf Merge pull request #1829 from roboflow/fix/process_video
fix: `UnboundLocalError` in `process_video`
2025-04-25 14:55:29 +02:00
soumik12345 00df1cbb04 fix: UnboundLocalError 2025-04-25 02:29:08 +05:30
Piotr Skalski 49d5505efa Merge pull request #1828 from roboflow/fix/make-trace-annotator-to-work-with-unassigned-trackers
make the `TraceAnnotator` to work with unassigned trackers
2025-04-24 00:30:13 +02:00
pre-commit-ci[bot] 1cf95972f3 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-04-23 22:26:28 +00:00
SkalskiP 047489b96e bump version from 0.26.0rc5 to 0.26.0rc6 2025-04-24 00:22:27 +02:00
SkalskiP da498b091b fix circular import 2025-04-24 00:13:32 +02:00
SkalskiP 2ea0f443f8 test new approach 2025-04-24 00:08:06 +02:00
SkalskiP 639318832b ready for tests 2025-04-23 23:55:34 +02:00
Piotr Skalski b9731ef9f3 Merge pull request #1826 from roboflow/feature/make-supervision-annotators-compatible-with-trackers-pending-tracker-id
make `supervision` annotators compatible with `trackers` pending tracker ID
2025-04-23 15:52:05 +02:00
pre-commit-ci[bot] b9475f14f3 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-04-23 13:45:43 +00:00
SkalskiP 587c61883b bump version from 0.26.0rc4 to 0.26.0rc5 2025-04-23 15:42:24 +02:00
SkalskiP f866fe046f ready for tests 2025-04-23 15:41:42 +02:00
Piotr Skalski 185322fcd1 Merge pull request #1824 from roboflow/docs/class/formatter
feat(mkdocs):  📝 add options for root heading and pygments language class in markdown extensions
2025-04-23 09:00:26 +02:00
Piotr Skalski 1dd6ff6ce5 Merge pull request #1825 from roboflow/feat/support-int-labels
feat(LabelAnnotator): add support for non-str labels
2025-04-23 08:58:57 +02:00
Piotr Skalski 9e79cb06d0 Merge pull request #1823 from roboflow/utils/xyxy_to_xyah
feat: ✨ add xyxy_to_xcycarh conversion function
2025-04-22 21:40:24 +02:00
Soumik Rakshit 94131ff56b add: support for non-str labels 2025-04-22 18:13:34 +00:00
pre-commit-ci[bot] ec0add34ee fix(pre_commit): 🎨 auto format pre-commit hooks 2025-04-22 18:08:21 +00:00
SkalskiP 6387513805 update test cases 2025-04-22 20:07:54 +02:00
Onuralp SEZER 9ed9cd2592 feat(mkdocs): 📝 add options for root heading and pygments language class in markdown extensions 2025-04-22 17:53:54 +03:00
Onuralp SEZER 5d6ad66618 feat: ✨ add xyxy_to_xcycarh conversion function 2025-04-22 17:00:52 +03:00
Onuralp SEZER 6f7ca11ea9 Merge pull request #1822 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2025-04-22 16:31:35 +03:00
pre-commit-ci[bot] 51a108b5ab chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.5 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.5...v0.11.6)
2025-04-21 17:20:07 +00:00
Onuralp SEZER cfe18afcc9 Merge pull request #1821 from roboflow/dependabot/github_actions/develop/astral-sh/setup-uv-5.4.2
⬆️ Bump astral-sh/setup-uv from 5.4.1 to 5.4.2
2025-04-21 05:13:12 +03:00
dependabot[bot] 39acd616a6 ⬆️ Bump astral-sh/setup-uv from 5.4.1 to 5.4.2
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5.4.1 to 5.4.2.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/0c5e2b8115b80b4c7c5ddf6ffdd634974642d182...d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 5.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 00:41:11 +00:00
Onuralp SEZER 1d0747fbfe Merge pull request #1816 from roboflow/feat/process-video-progress-bar
feat(supervision): Add frame limitations + progressbar for `sv.process_video`
2025-04-15 12:08:25 +03:00
Onuralp SEZER c3618652cd Merge pull request #1819 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2025-04-15 12:05:31 +03:00
pre-commit-ci[bot] 1496888d92 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.11.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.11.5)
2025-04-14 17:22:40 +00:00
Onuralp SEZER 058dc169f1 Merge pull request #1817 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2025-04-07 20:42:53 +03:00
pre-commit-ci[bot] 0d534346bf chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4)
2025-04-07 17:34:35 +00:00
soumik12345 e73cc438e1 fix: variable video_frames_generator 2025-04-07 21:45:47 +05:30
pre-commit-ci[bot] 6a378a32f7 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-04-07 16:13:08 +00:00
Soumik RakshitandOnuralp SEZER 1534501a1d update: supervision/utils/video.py
Co-authored-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-04-07 21:42:45 +05:30
Soumik RakshitandOnuralp SEZER 941700b9f8 update: supervision/utils/video.py
Co-authored-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-04-07 21:42:35 +05:30
Soumik RakshitandOnuralp SEZER 4743701286 update: supervision/utils/video.py
Co-authored-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-04-07 21:42:18 +05:30
soumik12345 b58862d9f3 chore: address feedback 2025-04-07 19:44:50 +05:30
soumik12345 4b245dd5f1 add: max_frames and profressbar options to supervision 2025-04-07 19:10:51 +05:30
Onuralp SEZER 32bf9be5d1 Merge pull request #1815 from roboflow/dependabot/github_actions/develop/actions/create-github-app-token-2.0.2
⬆️ Bump actions/create-github-app-token from 1.12.0 to 2.0.2
2025-04-07 03:53:55 +03:00
dependabot[bot] f56eb2503e ⬆️ Bump actions/create-github-app-token from 1.12.0 to 2.0.2
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.12.0 to 2.0.2.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/d72941d797fd3113feb6b93fd0dec494b13a2547...3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: 2.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 00:11:28 +00:00
Onuralp SEZER b1c0c1b00a Merge pull request #1812 from roboflow/dependabot/github_actions/develop/astral-sh/setup-uv-5.4.1
⬆️ Bump astral-sh/setup-uv from 5.4.0 to 5.4.1
2025-03-31 04:57:44 +03:00
Onuralp SEZER 4c88106d14 Merge pull request #1813 from roboflow/dependabot/github_actions/develop/actions/create-github-app-token-1.12.0
⬆️ Bump actions/create-github-app-token from 1.11.7 to 1.12.0
2025-03-31 04:57:23 +03:00
dependabot[bot] 3d0b4cba65 ⬆️ Bump actions/create-github-app-token from 1.11.7 to 1.12.0
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.11.7 to 1.12.0.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/af35edadc00be37caa72ed9f3e6d5f7801bfdf09...d72941d797fd3113feb6b93fd0dec494b13a2547)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 00:31:09 +00:00
dependabot[bot] f7fd508400 ⬆️ Bump astral-sh/setup-uv from 5.4.0 to 5.4.1
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5.4.0 to 5.4.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/22695119d769bdb6f7032ad67b9bca0ef8c4a174...0c5e2b8115b80b4c7c5ddf6ffdd634974642d182)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 00:31:06 +00:00
Onuralp SEZER 32b10b5335 Merge pull request #1809 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2025-03-24 20:32:10 +03:00
pre-commit-ci[bot] 5f981bf4d9 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2)
2025-03-24 17:26:28 +00:00
Onuralp SEZER 5634632944 Merge pull request #1808 from roboflow/dependabot/pip/develop/mkdocstrings-gte-0.25.2-and-lt-0.30.0
⬆️ Update mkdocstrings requirement from <0.29.0,>=0.25.2 to >=0.25.2,<0.30.0
2025-03-24 13:25:25 +03:00
Onuralp SEZER fc62e6a0bc Merge pull request #1807 from roboflow/dependabot/github_actions/develop/astral-sh/setup-uv-5.4.0
⬆️ Bump astral-sh/setup-uv from 5.3.1 to 5.4.0
2025-03-24 13:25:04 +03:00
Onuralp SEZER 837772392c Merge pull request #1806 from roboflow/dependabot/github_actions/develop/actions/create-github-app-token-1.11.7
⬆️ Bump actions/create-github-app-token from 1.11.6 to 1.11.7
2025-03-24 13:24:40 +03:00
dependabot[bot] 386fb2e15f ⬆️ Update mkdocstrings requirement
Updates the requirements on [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) to permit the latest version.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.25.2...0.29.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 00:52:55 +00:00
dependabot[bot] ad59b39257 ⬆️ Bump astral-sh/setup-uv from 5.3.1 to 5.4.0
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/f94ec6bedd8674c4426838e6b50417d36b6ab231...22695119d769bdb6f7032ad67b9bca0ef8c4a174)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 00:38:37 +00:00
dependabot[bot] 57966ce9ec ⬆️ Bump actions/create-github-app-token from 1.11.6 to 1.11.7
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.11.6 to 1.11.7.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/21cfef2b496dd8ef5b904c159339626a10ad380e...af35edadc00be37caa72ed9f3e6d5f7801bfdf09)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 00:38:33 +00:00
Piotr Skalski ca6609571a Merge pull request #1796 from roboflow/dependabot/pip/develop/mkdocstrings-gte-0.25.2-and-lt-0.29.0
⬆️ Update mkdocstrings requirement from <0.27.0,>=0.25.2 to >=0.25.2,<0.29.0
2025-03-21 16:57:49 +01:00
Onuralp SEZER 6690bd678a Merge pull request #1775 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2025-03-21 18:43:50 +03:00
Piotr Skalski 1c489eff98 Merge pull request #1804 from blakeburch/add-segment-tracking
Add segment tracking
2025-03-21 16:19:25 +01:00
pre-commit-ci[bot] f1e067041e fix(pre_commit): 🎨 auto format pre-commit hooks 2025-03-20 19:58:09 +00:00
Blake Burch 73d69172db Add segment tracking 2025-03-20 14:46:07 -05:00
pre-commit-ci[bot] 8881b5c588 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-03-17 17:29:14 +00:00
pre-commit-ci[bot] 83d9868350 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/PyCQA/bandit: 1.8.0 → 1.8.3](https://github.com/PyCQA/bandit/compare/1.8.0...1.8.3)
- [github.com/astral-sh/ruff-pre-commit: v0.8.6 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.6...v0.11.0)
- [github.com/codespell-project/codespell: v2.3.0 → v2.4.1](https://github.com/codespell-project/codespell/compare/v2.3.0...v2.4.1)
2025-03-17 17:28:37 +00:00
Onuralp SEZER c9a3069297 Merge pull request #1802 from roboflow/dependabot/github_actions/develop/actions/create-github-app-token-1.11.6
⬆️ Bump actions/create-github-app-token from 1.11.5 to 1.11.6
2025-03-10 09:59:51 +03:00
dependabot[bot] 8864762ff1 ⬆️ Bump actions/create-github-app-token from 1.11.5 to 1.11.6
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.11.5 to 1.11.6.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/0d564482f06ca65fa9e77e2510873638c82206f2...21cfef2b496dd8ef5b904c159339626a10ad380e)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 00:27:31 +00:00
Onuralp SEZER 39a12db1d4 Merge pull request #1798 from roboflow/dependabot/github_actions/develop/astral-sh/setup-uv-5.3.1
⬆️ Bump astral-sh/setup-uv from 5.3.0 to 5.3.1
2025-03-03 05:21:51 +03:00
dependabot[bot] 55fafd0a40 ⬆️ Bump astral-sh/setup-uv from 5.3.0 to 5.3.1
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/1edb52594c857e2b5b13128931090f0640537287...f94ec6bedd8674c4426838e6b50417d36b6ab231)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 00:16:42 +00:00
Piotr Skalski cc28a51c18 Merge pull request #1797 from roboflow/docs/keypoints_yolo
docs: 📝 Update documentation for ultralytics KeyPoints to include YOLO11-pose and adjust example to latest model
2025-02-25 11:00:17 +01:00
Onuralp SEZER 9938c1d2ad docs: 📝 Update documentation for ultralytics KeyPoints to include YOLO11-pose and adjust example to latest model
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-02-24 11:38:21 +03:00
Onuralp SEZER 51dba14fee Merge branch 'develop' into dependabot/pip/develop/mkdocstrings-gte-0.25.2-and-lt-0.29.0 2025-02-24 06:26:54 +03:00
Onuralp SEZER 78d70085cb pacakge: 📦 Update mkdocs configuration and bump mkdocstrings version to <0.29.0
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-02-24 06:26:01 +03:00
Onuralp SEZER e2cb82a7da Merge pull request #1794 from roboflow/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.12.4
⬆️ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4
2025-02-24 04:57:08 +03:00
Onuralp SEZER 78e1c2ceba Merge pull request #1793 from roboflow/dependabot/github_actions/develop/actions/create-github-app-token-1.11.5
⬆️ Bump actions/create-github-app-token from 1.11.1 to 1.11.5
2025-02-24 04:48:52 +03:00
Onuralp SEZER 898f2ba72c Merge pull request #1795 from roboflow/dependabot/github_actions/develop/astral-sh/setup-uv-5.3.0
⬆️ Bump astral-sh/setup-uv from 5.2.2 to 5.3.0
2025-02-24 04:02:16 +03:00
dependabot[bot] 11f2a005e2 ⬆️ Update mkdocstrings requirement
Updates the requirements on [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) to permit the latest version.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.25.2...0.28.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 00:45:58 +00:00
dependabot[bot] 3ba7c1b8e2 ⬆️ Bump astral-sh/setup-uv from 5.2.2 to 5.3.0
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5.2.2 to 5.3.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/4db96194c378173c656ce18a155ffc14a9fc4355...1edb52594c857e2b5b13128931090f0640537287)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 00:41:32 +00:00
dependabot[bot] 3562bbf2ed ⬆️ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.3 to 1.12.4.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.3...76f52bc884231f62b9a034ebfe128415bbaabdfc)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 00:41:28 +00:00
dependabot[bot] 408a9ad0ff ⬆️ Bump actions/create-github-app-token from 1.11.1 to 1.11.5
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.11.1 to 1.11.5.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/c1a285145b9d317df6ced56c09f525b5c2b6f755...0d564482f06ca65fa9e77e2510873638c82206f2)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 00:41:26 +00:00
Piotr Skalski acf1ef6d90 Merge pull request #1786 from Armaggheddon/refactor/heatmap-perf
perf: improved hsv color mapping in HeatMapAnnotator by 20x
2025-02-19 15:02:13 +01:00
Piotr Skalski 752818e577 Merge pull request #1790 from roboflow/feature/support_for_qwen_2_5_vl
support for converting Qwen2.5-VL into sv.Detections
2025-02-18 15:12:08 +01:00
Onuralp SEZER 2e38e8998f Merge branch 'develop' into feature/support_for_qwen_2_5_vl 2025-02-18 17:07:35 +03:00
Piotr Skalski 7878df14be Merge pull request #1791 from roboflow/feature/0.26.0-deprecations
merge 0.26.0 deprecation changes
2025-02-18 15:02:01 +01:00
Piotr Skalski dc449c4fff Merge pull request #1771 from roboflow/ci/build/changes
ci: 👷 replace Poetry workflow with uv and switch to mkdocs-material-insiders
2025-02-18 15:01:35 +01:00
Onuralp SEZER afcd0d6e5a chore: update email address for author and maintainer in pyproject.toml 2025-02-18 16:39:34 +03:00
pre-commit-ci[bot] 6b48881de7 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-02-18 13:21:43 +00:00
SkalskiP 10beacabc0 Merge remote-tracking branch 'origin/feature/0.26.0-deprecations' into feature/0.26.0-deprecations
# Conflicts:
#	supervision/detection/core.py
2025-02-18 14:20:28 +01:00
SkalskiP f180f0f5be fix sv.Detections.from_lmm 2025-02-18 14:19:53 +01:00
pre-commit-ci[bot] f906a2978f fix(pre_commit): 🎨 auto format pre-commit hooks 2025-02-18 12:44:01 +00:00
SkalskiP e4949a7de7 Merge remote-tracking branch 'origin/feature/0.26.0-deprecations' into feature/0.26.0-deprecations
# Conflicts:
#	supervision/detection/core.py
2025-02-18 13:43:32 +01:00
SkalskiP 4e41e9b34f fix sv.Detections.from_lmm and sv.Detections.from_vlm 2025-02-18 13:41:44 +01:00
pre-commit-ci[bot] e804a01e85 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-02-18 11:52:48 +00:00
SkalskiP d0559e104d Merge branch 'feature/support_for_qwen_2_5_vl' into feature/0.26.0-deprecations 2025-02-18 12:52:21 +01:00
SkalskiP fa8b6e125c Merge branch 'feature/support_for_qwen_2_5_vl' into feature/0.26.0-deprecations 2025-02-18 12:12:29 +01:00
pre-commit-ci[bot] 47295cd5f7 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-02-18 09:20:57 +00:00
SkalskiP 2d48c5dba7 fix line length 2025-02-18 10:20:29 +01:00
SkalskiP 13b847d1dc push poetry.lock 2025-02-18 10:09:46 +01:00
SkalskiP 7e19c633a6 merge 0.26.0 deprecation changes 2025-02-18 10:04:38 +01:00
pre-commit-ci[bot] 8df41a5683 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-02-18 08:17:51 +00:00
SkalskiP 59b652d0f2 update from_paligemma adding resolution_wh validation; update from_paligemma unit tests as well 2025-02-18 09:17:25 +01:00
pre-commit-ci[bot] 76429548b6 fix(pre_commit): 🎨 auto format pre-commit hooks 2025-02-18 08:04:57 +00:00
SkalskiP 23301bbd17 experiment with fixing numpy dependency 2025-02-18 08:54:08 +01:00
SkalskiP 77b7d3733a added experimental implementation of from_qwen_2_5_vl 2025-02-18 08:37:51 +01:00
Piotr Skalski 63388fdee9 Merge pull request #1788 from roboflow/feat/transformers-keypoints
feat: ✨ Add xyxy_xywh function and from_transformers method for KeyPoints class added
2025-02-18 00:00:19 +01:00
Onuralp SEZER c06d5f0131 docs: ✏️ Update docstrings to correct example wording and formatting 2025-02-18 01:38:11 +03:00
Onuralp SEZER a151685a57 docs: ✏️ Add documentation for xyxy_to_xywh function in utils.md 2025-02-18 01:05:56 +03:00
Onuralp SEZER cbaa605e08 refactor: 🔄 Rename xyxy_xywh function to xyxy_to_xywh
docs: 📝 Update docstring for KeyPoints.from_transformers

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-02-18 00:54:12 +03:00
Onuralp SEZER fb1dbfd084 feat: ✨ Add xyxy_xywh function and from_transformers method for KeyPoints class
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-02-17 03:18:23 +03:00
Armaggheddon fefd005c2c perf: improved hsv color mapping in HeatMapAnnotator by 20x 2025-02-10 20:59:45 +01:00
Onuralp SEZER 2c53eb10a8 chore: simplify numpy and scipy version constraints in pyproject.toml and uv lock update 2025-02-09 03:53:35 +03:00
Onuralp SEZER d381f43e50 Merge pull request #1784 from rafaelpadilla/fix_docstring
correct docstring to match parameter order as per documentation.
2025-02-08 23:12:32 +03:00
Onuralp SEZER dc08d88c5c chore: update astral-sh/setup-uv to version 5.2.2 in workflow files 2025-02-08 23:07:12 +03:00
rcvsq af562950a3 correct docstring to match parameter order as per documentation. 2025-02-04 16:02:08 +00:00
Onuralp SEZER ee9568945a Merge branch 'develop' into ci/build/changes 2025-02-04 01:08:38 +03:00
Onuralp SEZER ea03d55d6a chore: delete removed B410 bandit rule 2025-01-27 21:01:17 +03:00
Onuralp SEZER 3f55c3022e Merge pull request #1781 from roboflow/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.12.4
⬆️ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4
2025-01-27 21:00:14 +03:00
dependabot[bot] a54e491d49 ⬆️ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.3 to 1.12.4.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/67339c736fd9354cd4f8cb0b744f2b82a74b5c70...76f52bc884231f62b9a034ebfe128415bbaabdfc)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 00:33:20 +00:00
Onuralp SEZER 4c03f899b9 Merge branch 'develop' into ci/build/changes 2025-01-09 19:32:39 +03:00
Piotr Skalski 1b5c592abb Merge pull request #1773 from pirnerjonas/patch-2
Fix typos in Detections merge doc string
2025-01-09 16:53:22 +01:00
Onuralp SEZER a5cad09782 ci: 🔧 add version string to uv setup actions and refine test workflow triggers 2025-01-09 14:34:47 +03:00
Onuralp SEZER 483cc0677f build: 🛠️ set include-package-data to false in pyproject.toml 2025-01-09 14:12:50 +03:00
Jonas Pirner b2a1eb5b21 fix: remove tabs 2025-01-09 10:46:25 +01:00
Jonas Pirner fa979cf0ab docs: fix doc string in Detections merge function 2025-01-09 10:36:33 +01:00
Onuralp SEZER 2a1542c465 ci: 👷 rename release actions files
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-01-09 01:24:47 +03:00
Onuralp SEZER e543aac6dd ci: 🔑 separate TestPyPI publishing workflows 2025-01-09 01:23:01 +03:00
Onuralp SEZER 3ee6de3ed3 docs: 📝 update error message to include uv as an alternative for installing metrics extra 2025-01-09 00:32:26 +03:00
Onuralp SEZER 16848e9160 docs: 📝 update installation instructions to replace Poetry with uv in index.md 2025-01-09 00:30:45 +03:00
Onuralp SEZER 071feeadce docs: 📝 update CONTRIBUTING.md to replace Poetry with uv for dependency management 2025-01-09 00:26:02 +03:00
Onuralp SEZER 9587439f8d ci: 🔑 add GitHub App token creation for mkdocs and install mkdocs-material-insiders dependency 2025-01-09 00:18:29 +03:00
Onuralp SEZER a61a6c7144 chore: update author and maintainer email addresses in pyproject.toml
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-01-08 23:54:50 +03:00
Onuralp SEZER 6ae1dfdcb0 ci: 🐍 update workflows to use specific uv version and improve environment configuration 2025-01-08 23:49:43 +03:00
Onuralp SEZER fc40438f35 ci: 🐍 migrate from Poetry to uv for Python setup and dependency installation in doc workflows
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-01-08 23:18:52 +03:00
Onuralp SEZER 730bbd246c ci: 👷 replace Poetry workflow with UV testing workflow and update dependencies in pyproject.toml
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-01-08 23:10:40 +03:00
Piotr Skalski 656f639db5 Merge pull request #1767 from roboflow/fix/loosing_data_during_detection_filtering
fix loosing data during detection filtering
2025-01-08 13:02:19 +01:00
SkalskiP 663522bed8 Merge branch 'develop' into fix/loosing_data_during_detection_filtering
# Conflicts:
#	pyproject.toml
2025-01-08 12:53:23 +01:00
Piotr Skalski 324872303b Merge pull request #1763 from roboflow/deps/updates
chore(deps): 📦 poetry lock file  deps are updated
2025-01-08 12:51:26 +01:00
Piotr Skalski 651ed9d470 Merge pull request #1636 from patel-zeel/feat/expand-image-formats
Allow "tiff" and more extensions in `DetectionDataset.from_yolo` function
2025-01-08 12:42:53 +01:00
patel-zeel 6aedf4874f Merge branch 'feat/expand-image-formats' of https://github.com/patel-zeel/supervision into feat/expand-image-formats 2025-01-08 15:02:12 +05:30
patel-zeel d33c159a26 explicit extensions, simplified code, speed improvement 2025-01-08 15:02:05 +05:30
Zeel B Patel 572ea32c0c Merge branch 'roboflow:develop' into feat/expand-image-formats 2025-01-07 22:53:55 +05:30
patel-zeel 3415c15365 Merge branch 'feat/expand-image-formats' of https://github.com/patel-zeel/supervision into feat/expand-image-formats 2025-01-07 22:16:17 +05:30
patel-zeel 2ed66bfffc expand support to grayscale similar to cv2 2025-01-07 22:16:05 +05:30
Onuralp SEZER c6591f609f refactor: 🛠️ update pandas dependency specifications for improved version compatibility based on Python version 2025-01-07 05:04:26 +03:00
Onuralp SEZER ba7760f9ad refactor: 🛠️ enhance numpy dependency specifications for improved version compatibility across Python versions 2025-01-07 04:40:03 +03:00
Onuralp SEZER 3f05b107ed refactor: 🛠️ update numpy, scipy, and matplotlib dependencies for improved version compatibility across Python versions 2025-01-07 02:48:05 +03:00
Onuralp SEZER d4c6fd7426 Merge pull request #1768 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2025-01-07 00:57:49 +03:00
pre-commit-ci[bot] 6d8d4fdddb chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.4 → v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.4...v0.8.6)
2025-01-06 18:03:46 +00:00
SkalskiP adec783d49 fix loosing data during detection filtering 2025-01-06 18:56:31 +01:00
Onuralp SEZER 2cca69be00 refactor: 🛠️ update scipy dependency specifications for improved version constraints based on Python version 2025-01-06 17:51:00 +03:00
Onuralp SEZER 894ee7f5f9 refactor: 🛠️ streamline testing workflow by removing deprecated test.yml and adding pytest step to poetry-test.yml 2025-01-06 17:18:18 +03:00
Onuralp SEZER 23085eaf8a Merge branch 'develop' into deps/updates 2025-01-06 17:15:25 +03:00
Onuralp SEZER 4deb9597b2 refactor: 🛠️ update dependencies in pyproject.toml and improve versioning for Python compatibility 2025-01-06 16:58:56 +03:00
Piotr Skalski 392d769483 Merge pull request #1734 from roboflow/fix/enable-kapa
Enable kapa for 0.27.0
2025-01-06 11:56:23 +01:00
Piotr Skalski f088e16fe2 Merge pull request #1765 from roboflow/dependabot/github_actions/develop/abatilo/actions-poetry-4.0.0
⬆️ Bump abatilo/actions-poetry from 3.0.1 to 4.0.0
2025-01-06 10:59:03 +01:00
Onuralp SEZER 30cf394a7b refactor: 🛠️ update pyproject.toml structure with PEP621 compatibility and improve metadata organization 2025-01-06 05:15:21 +03:00
Onuralp SEZER beabf6810c fix: 🐛 Simplify pytest execution in workflow
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-01-06 04:26:04 +03:00
dependabot[bot] dbd6104c8b ⬆️ Bump abatilo/actions-poetry from 3.0.1 to 4.0.0
Bumps [abatilo/actions-poetry](https://github.com/abatilo/actions-poetry) from 3.0.1 to 4.0.0.
- [Release notes](https://github.com/abatilo/actions-poetry/releases)
- [Changelog](https://github.com/abatilo/actions-poetry/blob/master/.releaserc)
- [Commits](https://github.com/abatilo/actions-poetry/compare/e78f54a89cb052fff327414dd9ff010b5d2b4dbd...3765cf608f2d4a72178a9fc5b918668e542b89b1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 00:33:51 +00:00
Onuralp SEZER 2a5ce95472 chore(deps): 📦 poetry lock file deps are updated
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2025-01-02 20:11:50 +03:00
Onuralp SEZER 77ae682120 Merge pull request #1756 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-12-25 02:07:51 +03:00
pre-commit-ci[bot] 4a0b3bd2cf chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.3 → v0.8.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.3...v0.8.4)
2024-12-23 17:55:38 +00:00
Onuralp SEZER a9aad6727c Merge pull request #1747 from roboflow/fix/pre-release-env
fix: 🐛  env value corrected for pypi pre release action to run properly
2024-12-16 21:11:23 +03:00
Onuralp SEZER 7c6668b58e fix: 🐛 env value corrected for action to run properly
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-12-16 21:10:14 +03:00
Onuralp SEZER 385c26ad78 Merge pull request #1745 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-12-16 21:00:52 +03:00
Piotr Skalski d57078a7ed Merge pull request #1744 from roboflow/fix/release-twine-package 2024-12-16 18:55:13 +01:00
pre-commit-ci[bot] 7a5672bbcc chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.2 → v0.8.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.2...v0.8.3)
2024-12-16 17:54:25 +00:00
Onuralp SEZER e6f0a8f3f0 fix: 🐛 run twine via poetry run command to make sure existence of twine package
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-12-16 20:52:43 +03:00
Piotr Skalski 7b50eed7d9 Merge pull request #1741 from roboflow/main
merge `supervision-0.25.1` hot fix into `main` before `supervision-0.26.0` release
2024-12-16 16:26:56 +01:00
Piotr Skalski 7c2711745b Merge branch 'develop' into main 2024-12-16 16:22:01 +01:00
Piotr Skalski dbade52364 Merge pull request #1739 from Anirudh2112/fix/move-masks-negative-offset
fix: allow negative offsets in move_masks function (#1715)
2024-12-16 14:35:22 +01:00
pre-commit-ci[bot] 63392ab640 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-12-16 13:27:42 +00:00
SkalskiP b466076383 making sure code works with mixed offset like [2, -2]; dostring updates 2024-12-16 14:27:13 +01:00
Onuralp SEZER 14b3c0cf8a Merge pull request #1738 from roboflow/update_codeowners
Update CODEOWNERS
2024-12-14 23:47:26 +03:00
Anirudh 092085efb1 fix: allow negative offsets in move_masks function (#1715) 2024-12-14 14:59:58 -05:00
Alex Norell b8d62f6e29 Update CODEOWNERS 2024-12-13 07:56:45 -08:00
Onuralp SEZER 6f55d9de9e Merge pull request #1737 from roboflow/fix/release_docs
Update docs checkout to use fetch-depth: 0
2024-12-13 18:37:00 +03:00
Alex Norell 4b75b9bd82 Update the test docs action too 2024-12-13 07:31:43 -08:00
Alex Norell 55378df392 Update docs checkout to use fetch-depth: 0 2024-12-13 07:29:50 -08:00
Onuralp SEZER 4fd1450f80 Merge pull request #1736 from roboflow/fix/release_docs
Fix release docs workflow file
2024-12-13 18:26:13 +03:00
Alex Norell c894a7a763 Fix release docs workflow file 2024-12-13 07:23:46 -08:00
LinasKo a4a2d8c05d Merge pull request #1735 from roboflow/hotfix/v0_25_1
release:  📦  supervision version to 0.25.1 for hotfix dependencies
2024-12-13 15:10:13 +02:00
pre-commit-ci[bot] 30010b9773 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-12-13 13:04:57 +00:00
Onuralp SEZER fd61460cc2 feat: ✨ poetry updates and include numpy/poetry installation error hotfix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-12-13 16:00:01 +03:00
Onuralp SEZER a80be9422f Merge pull request #1732 from roboflow/dependabot/pip/ruff-0.8.3
⬆️ Bump ruff from 0.8.2 to 0.8.3
2024-12-13 15:52:26 +03:00
Onuralp SEZER eb3b172139 feat: ✨ poetry lock file correction
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-12-13 15:44:11 +03:00
LinasKo 9975b63216 Enable kapa for 0.27.0 2024-12-13 12:22:45 +02:00
LinasKo 3c4793a092 Merge pull request #1733 from roboflow/fix/remove-kappa
Disable kapa until 0.27.0
2024-12-13 12:21:43 +02:00
LinasKo b6a6746f8b Disable kapa until we test its functionality 2024-12-13 12:13:39 +02:00
LinasKo a9831323f3 Merge pull request #1722 from roboflow/sec/improve-github-workflow-security
Improve GitHub Actions Security
2024-12-13 11:42:19 +02:00
dependabot[bot] e6f29a45a3 ⬆️ Bump ruff from 0.8.2 to 0.8.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.2 to 0.8.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.8.2...0.8.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-12-13 00:28:00 +00:00
pre-commit-ci[bot] 06f678e5d2 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-12-12 16:49:04 +00:00
Sachin Agarwal 47c98173ea Added codeowners 2024-12-12 11:48:26 -05:00
Alex Norell c9ea75e3ca Add workflow job names 2024-12-11 11:27:39 -08:00
Alex Norell 517b25f27f Run tests on all platforms 2024-12-11 11:16:51 -08:00
Alex Norell 40340ef8a6 Update builds and tests 2024-12-11 11:14:11 -08:00
Alex Norell 3d62caf4f3 Update workflows 2024-12-11 11:10:28 -08:00
Alex Norell dab2194f47 Merge branch 'develop' of github.com:roboflow/supervision into sec/improve-github-workflow-security 2024-12-11 11:02:45 -08:00
Alex Norell c257221fe0 Fix EOF 2024-12-11 10:57:58 -08:00
Alex Norell 7c5320409c Change timeouts and bump action versions 2024-12-11 10:55:04 -08:00
Alex Norell 10f372fe28 Refactor builds 2024-12-11 10:46:10 -08:00
LinasKo 64dc2e91c8 Merge pull request #1730 from roboflow/fix/remove-assets-import-check
Remove import check for `assets`
2024-12-11 18:45:28 +02:00
LinasKo 9a4781f2a5 Remove import check for assets
* The dependencies were already merged into the main requirements
2024-12-11 18:33:06 +02:00
LinasKo 1633522768 Merge pull request #1716 from roboflow/feat/comparison-annotator
Add `ComparisonAnnotator`, `xyxy_to_polygons`
2024-12-11 18:26:06 +02:00
LinasKo d061a4c89b Add CropAnnotator image example 2024-12-11 18:09:31 +02:00
pre-commit-ci[bot] 059633435c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-12-11 15:05:57 +00:00
LinasKo 001f85ad0f Add ComparisonAnnotator docs, remove Crop & RoundBox annotator from preview
* Up to 20 tabs can be made at once. This is likely a bug relating to the underlying mkdocs plugin, but a quick check for hardcoded values yielded nothing.
2024-12-11 16:58:36 +02:00
LinasKo d519861b1e Merge pull request #1726 from roboflow/fix/kapa-init
Kapa: remove duplicate initialization
2024-12-10 13:26:34 +02:00
Onuralp SEZER 12509e756a Merge pull request #1724 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-12-10 14:03:02 +03:00
LinasKo fcf8b7f378 Fix Kapa widget initialization 2024-12-10 12:59:36 +02:00
pre-commit-ci[bot] ea3b9a5584 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.1 → v0.8.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.2)
2024-12-09 17:52:49 +00:00
pre-commit-ci[bot] f7d7786c56 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-12-09 12:30:18 +00:00
Sachin Agarwal a713d3650a Improving github actions
1. Default permission restrictions
2. Action version pinning
3. Timeout limits
4. Python dependency version pinning in scripts
5. Adding basic CODEOWNERS
6. More eror handling
7. Concurrency controls (same action cannot be running in parallel)
2024-12-09 07:27:24 -05:00
Onuralp SEZER 5385ca744b Merge pull request #1714 from roboflow/dependabot/pip/notebook-7.3.1
⬆️ Bump notebook,numpy,ruff and poetry to latest
2024-12-09 12:26:49 +03:00
Onuralp SEZER 34229c7ad7 chore: poetry version updated 1.8.5, notebook,ruff,numpy package updated properly
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-12-08 23:33:30 +03:00
LinasKo 4c88e03b6c Add xyxy_to_polygons, ComparisonAnnotator 2024-12-06 17:00:51 +02:00
dependabot[bot] 37943b2d6f ⬆️ Bump notebook from 7.2.2 to 7.3.1
Bumps [notebook](https://github.com/jupyter/notebook) from 7.2.2 to 7.3.1.
- [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.2.2...@jupyter-notebook/tree@7.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-06 01:11:02 +00:00
LinasKo f3d27d8381 Cap ultralytics version 2024-12-05 17:23:05 +02:00
LinasKo c416474b63 Merge pull request #1704 from roboflow/fix/from_inference_to_support_deployed_roboflow_results
from_inference now supports roboflow results without json()
2024-12-04 12:36:14 +02:00
LinasKo ae8e6b2976 Merge pull request #1698 from souhhmm/fix/xyxy-shape-crash
fix: is_empty check for filtering
2024-12-04 12:15:31 +02:00
LinasKo 7141f11c0b detections __getitem__ test: minor class name correction 2024-12-04 12:13:33 +02:00
Soham 37eadfe305 test: empty detection check in getitem 2024-12-03 19:24:41 +05:30
LinasKo a4dfcacbfd detections __getitem__: earlier check for empty detections 2024-12-03 13:12:14 +02:00
Onuralp SEZER 3d1416790b Merge pull request #1705 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-12-02 22:03:57 +03:00
pre-commit-ci[bot] 348069fb07 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/PyCQA/bandit: 1.7.10 → 1.8.0](https://github.com/PyCQA/bandit/compare/1.7.10...1.8.0)
- [github.com/astral-sh/ruff-pre-commit: v0.8.0 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.0...v0.8.1)
2024-12-02 18:00:40 +00:00
LinasKo fac1581fcd from_inference now supports roboflow results without json() 2024-12-02 16:38:59 +02:00
LinasKo d075eb1e47 Merge pull request #1693 from roboflow/fix/poetry/py313
fix: 🐞 updating poetry lock file version and fixing numpy,pandas,maplotlib and python versions compatibility
2024-12-02 14:57:42 +02:00
Onuralp SEZER eac2691c6e chore: 📦 lock file updated
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-12-02 15:33:35 +03:00
Onuralp SEZER a0242b0594 Merge branch 'develop' into fix/poetry/py313 2024-12-02 15:30:22 +03:00
Onuralp SEZER 1e2bdeee35 Merge pull request #1703 from roboflow/dependabot/pip/pytest-8.3.4
⬆️ Bump pytest from 8.3.3 to 8.3.4
2024-12-02 11:55:31 +03:00
Onuralp SEZER 62a396fe90 Merge pull request #1702 from roboflow/dependabot/pip/mkdocs-material-9.5.47
⬆️ Bump mkdocs-material from 9.5.46 to 9.5.47
2024-12-02 11:54:58 +03:00
Onuralp SEZER 9941417ced Merge pull request #1701 from roboflow/dependabot/pip/twine-6.0.1
⬆️ Bump twine from 5.1.1 to 6.0.1
2024-12-02 11:54:26 +03:00
Onuralp SEZER 79390062ce Merge pull request #1700 from roboflow/dependabot/pip/ruff-0.8.1
⬆️ Bump ruff from 0.8.0 to 0.8.1
2024-12-02 11:54:06 +03:00
dependabot[bot] 66e02b77b0 ⬆️ Bump pytest from 8.3.3 to 8.3.4
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.3 to 8.3.4.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.3...8.3.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 01:15:47 +00:00
dependabot[bot] 816a990083 ⬆️ Bump mkdocs-material from 9.5.46 to 9.5.47
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.46 to 9.5.47.
- [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.46...9.5.47)

---
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-12-02 01:12:05 +00:00
dependabot[bot] 93d0313460 ⬆️ Bump twine from 5.1.1 to 6.0.1
Bumps [twine](https://github.com/pypa/twine) from 5.1.1 to 6.0.1.
- [Release notes](https://github.com/pypa/twine/releases)
- [Changelog](https://github.com/pypa/twine/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/twine/compare/v5.1.1...6.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 01:09:19 +00:00
dependabot[bot] d229d2b069 ⬆️ Bump ruff from 0.8.0 to 0.8.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.8.0...0.8.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-12-02 01:07:27 +00:00
Soham 28fa054fdc fix: is_empty check for filtering 2024-11-30 13:56:18 +05:30
Onuralp SEZER 6889e33da5 Merge pull request #1696 from roboflow/docs/installations
docs: 📝 extend supervision installation methods
2024-11-29 22:31:49 +03:00
Onuralp SEZER 228b4cff0c docs: 📝 extend supervision installation methods
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-29 20:59:32 +03:00
LinasKo a5948c8992 Merge pull request #1697 from roboflow/docs/how-to-benchmark-model
How To Benchmark Models
2024-11-29 19:05:29 +02:00
LinasKo 853cbc428f How to Benchmark: typos, images, Colab, correct class remap 2024-11-29 18:58:33 +02:00
LinasKo d3f6570a89 Update model benchmarking guide with model running and metrics
* Install inference directly for now
2024-11-29 18:16:44 +02:00
LinasKo d1c8adb33a How To: Benchmark Model
* Initial version of model benchmarking guide
2024-11-28 15:53:44 +02:00
Onuralp SEZER 5cd6844ff4 Merge branch 'develop' into fix/poetry/py313 2024-11-28 14:46:31 +03:00
Onuralp SEZER c0d5a7fd25 chore: some ci files tidied
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-28 03:46:25 +03:00
Onuralp SEZER 159c507d7f fix: 🐞 update matplotlib version constraints for Python 3.8 and 3.13 compatibility
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-28 03:35:31 +03:00
Onuralp SEZER fdd317e53e fix: 🐞 update matplotlib version constraints for Python versions compatibility
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-28 02:54:04 +03:00
Onuralp SEZER 370996358b docs: 📝 update pandas version to support Python 3.13 comment added into pyproject.toml
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-28 02:40:28 +03:00
Onuralp SEZER def1cc4b33 ci: 👷 supervision import check and version print added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-28 02:36:45 +03:00
Onuralp SEZER 5f0f90b699 fix: 🐞 pandas 3.13 support fixed
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-28 02:29:20 +03:00
Onuralp SEZER ae9628e537 fix: 🐞 add poetry lock file re-generated via poetry 1.8.4
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-28 02:11:02 +03:00
LinasKo 32eaf4eb50 Merge pull request #1691 from Erol444/patch-1
Mask datatype specify in docs
2024-11-26 12:53:18 +02:00
LinasKo 02dac5adfe Merge pull request #1682 from roboflow/typing/_all
fix: 🐞 add missing exports to __all__ in supervision module
2024-11-26 12:49:04 +02:00
Erol444 b3d12ed652 Update core.py
Specified that mask is in bool datatype. Used int8 (gpt generated code) and was debugging this issue for 3 hours until I looked into source code of mask annotator
2024-11-26 17:31:28 +07:00
Onuralp SEZER 7bca2fe765 Merge pull request #1690 from roboflow/dependabot/pip/mkdocs-material-9.5.46
⬆️ Bump mkdocs-material from 9.5.45 to 9.5.46
2024-11-26 11:40:33 +03:00
dependabot[bot] a90b8717ee ⬆️ Bump mkdocs-material from 9.5.45 to 9.5.46
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.45 to 9.5.46.
- [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.45...9.5.46)

---
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-11-26 00:58:45 +00:00
pre-commit-ci[bot] b7e02add30 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-11-25 20:31:50 +00:00
Onuralp SEZER 7626c32a56 fix: 🐛 add missing exports to __all__ in supervision modules for assets and metrics
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-25 23:31:21 +03:00
Onuralp SEZER fe93979e0b Merge pull request #1689 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-11-25 22:52:23 +03:00
pre-commit-ci[bot] 684522a7e8 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.4 → v0.8.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.4...v0.8.0)
2024-11-25 17:51:21 +00:00
LinasKo bbbb37434c Merge pull request #1675 from roboflow/docs/git
docs: 📝 git clone methods are extended and shallow copy method also added
2024-11-25 13:20:41 +02:00
LinasKo 4bfee1096e docs: removed git checkout develop
Develop is the primary branch, so explicit checkout is not needed
2024-11-25 13:11:54 +02:00
Onuralp SEZER dd0ab6a2a7 Merge pull request #1686 from roboflow/dependabot/pip/tqdm-4.67.1
⬆️ Bump tqdm from 4.67.0 to 4.67.1
2024-11-25 10:41:05 +03:00
Onuralp SEZER cdf66fd78d Merge pull request #1685 from roboflow/dependabot/pip/wheel-0.45.1
⬆️ Bump wheel from 0.45.0 to 0.45.1
2024-11-25 10:40:43 +03:00
Onuralp SEZER 6d4b08c753 Merge pull request #1684 from roboflow/dependabot/pip/ruff-0.8.0
⬆️ Bump ruff from 0.7.4 to 0.8.0
2024-11-25 10:40:19 +03:00
dependabot[bot] 7c57a1b8de ⬆️ Bump tqdm from 4.67.0 to 4.67.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.67.0 to 4.67.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.67.0...v4.67.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 00:26:16 +00:00
dependabot[bot] 17e35e2bcf ⬆️ Bump wheel from 0.45.0 to 0.45.1
Bumps [wheel](https://github.com/pypa/wheel) from 0.45.0 to 0.45.1.
- [Release notes](https://github.com/pypa/wheel/releases)
- [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst)
- [Commits](https://github.com/pypa/wheel/compare/0.45.0...0.45.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 00:24:18 +00:00
dependabot[bot] 69017093e1 ⬆️ Bump ruff from 0.7.4 to 0.8.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.4 to 0.8.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.4...0.8.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-11-25 00:23:09 +00:00
Onuralp SEZER e54ce38458 fix: 🐞 add missing exports to __all__ in supervision module
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-24 23:52:22 +03:00
Onuralp SEZER 46416e430c Merge pull request #1680 from roboflow/dependabot/pip/pip-661d9d4597
⬆️ Bump tornado from 6.4.1 to 6.4.2 in the pip group
2024-11-24 15:05:29 +03:00
dependabot[bot] 2ad5b60c66 ⬆️ Bump tornado from 6.4.1 to 6.4.2 in the pip group
Bumps the pip group with 1 update: [tornado](https://github.com/tornadoweb/tornado).


Updates `tornado` from 6.4.1 to 6.4.2
- [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-22 22:14:53 +00:00
Zeel B Patel 512edd0c9b Merge branch 'roboflow:develop' into feat/expand-image-formats 2024-11-21 17:48:58 +05:30
Onuralp SEZER 60b9b716e9 Merge pull request #1677 from roboflow/dependabot/pip/mkdocs-material-9.5.45
⬆️ Bump mkdocs-material from 9.5.44 to 9.5.45
2024-11-21 05:55:17 +03:00
Onuralp SEZER 4b64d4ddca Merge pull request #1678 from roboflow/dependabot/pip/types-tqdm-4.67.0.20241119
⬆️ Bump types-tqdm from 4.66.0.20240417 to 4.67.0.20241119
2024-11-21 05:54:40 +03:00
dependabot[bot] 9ded219dfb ⬆️ Bump types-tqdm from 4.66.0.20240417 to 4.67.0.20241119
Bumps [types-tqdm](https://github.com/python/typeshed) from 4.66.0.20240417 to 4.67.0.20241119.
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-21 00:51:34 +00:00
dependabot[bot] 987a8f269f ⬆️ Bump mkdocs-material from 9.5.44 to 9.5.45
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.44 to 9.5.45.
- [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.44...9.5.45)

---
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-11-21 00:50:26 +00:00
Onuralp SEZER a57f4f22ae docs: 📝 git clone examples with single branch and single depth added into example docs
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-19 17:59:58 +03:00
Onuralp SEZER 88f113d14b docs: 📝 git clone methods are extended and shallow copy method also added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-19 17:59:58 +03:00
pre-commit-ci[bot] 8d18328dff chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.7.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.7.4)
2024-11-19 17:59:27 +03:00
Onuralp SEZER 7ea4b2ac13 Merge pull request #1674 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-11-19 11:56:05 +03:00
LinasKo 284901c06b Merge pull request #1662 from roboflow/fix/depricated-tests
fix(tests): 🐞 update inference slicer tests to new overlap system for removing test warnings
2024-11-18 20:08:28 +02:00
pre-commit-ci[bot] b2dc1b2a17 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.7.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.7.4)
2024-11-18 17:55:51 +00:00
LinasKo d9e3ee9465 Merge pull request #1673 from roboflow/release/0.26.0rc1
bump version from 0.25.0 to 0.26.0rc1
2024-11-18 19:38:05 +02:00
LinasKo 37b5dd477b bump version from 0.25.0 to 0.26.0rc1 2024-11-18 19:30:02 +02:00
LinasKo 092809b2be Merge pull request #1672 from roboflow/removal/assets
chore: 📦 assets extra removed and merged in to main supervision
2024-11-18 19:21:35 +02:00
Onuralp SEZER 742a824d2c fix: 🐛 install section removed from assets
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-18 20:15:38 +03:00
Onuralp SEZER c88de3641c chore: 📦 assets extra removed and merged in to main supervision
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-18 20:03:07 +03:00
Onuralp SEZER aec0bc3529 Merge pull request #1671 from roboflow/refactor/numpy/pyproject_toml
ci: 👷 update numpy dependency to a simplified version constraint
2024-11-18 19:46:33 +03:00
Onuralp SEZER 89769e49f7 ci: 👷 update numpy dependency to a simplified version constraint
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-18 19:34:44 +03:00
LinasKo 86f95f914a Merge pull request #1669 from roboflow/main
Merge main into develop after 0.25.0 release
2024-11-13 00:04:53 +02:00
Onuralp SEZER fdb036ecd5 fix(tests): 🐞 update inference slicer tests to new overlap system for removing test warnings
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-11-08 09:44:15 +03:00
patel-zeel f4850b870f replace cv2 with PIL for speed 2024-10-31 16:45:19 +05:30
79 changed files with 8370 additions and 6048 deletions
+4
View File
@@ -0,0 +1,4 @@
# These owners will be the default owners for everything in
# the repo. They will be requested for review when someone
# opens a pull request.
* @SkalskiP @onuralpszr
+4 -2
View File
@@ -4,13 +4,15 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
commit-message:
prefix: ⬆️
target-branch: "develop"
# Python
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
commit-message:
prefix: ⬆️
target-branch: "develop"
+23 -14
View File
@@ -1,33 +1,42 @@
name: Clear cache
on:
schedule:
- cron: '0 0 1 * *'
workflow_dispatch:
schedule:
- cron: "0 0 1 * *" # Run at midnight on the first day of every month
workflow_dispatch:
# Restrict permissions by default
permissions:
actions: write
actions: write # Required for cache management
jobs:
clear-cache:
name: Clear cache
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Clear cache
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
console.log("About to clear")
console.log("Starting cache cleanup...")
const caches = await github.rest.actions.getActionsCacheList({
owner: context.repo.owner,
repo: context.repo.repo,
})
let deletedCount = 0
for (const cache of caches.data.actions_caches) {
console.log(cache)
github.rest.actions.deleteActionsCacheById({
owner: context.repo.owner,
repo: context.repo.repo,
cache_id: cache.id,
})
console.log(`Deleting cache: ${cache.key} (${cache.size_in_bytes} bytes)`)
try {
await github.rest.actions.deleteActionsCacheById({
owner: context.repo.owner,
repo: context.repo.repo,
cache_id: cache.id,
})
deletedCount++
} catch (error) {
console.error(`Failed to delete cache ${cache.key}: ${error.message}`)
}
}
console.log("Clear completed")
console.log(`Cache cleanup completed. Deleted ${deletedCount} caches.`)
@@ -0,0 +1,22 @@
name: Combine Dependabot PRs
on:
schedule:
- cron: "0 1 * * 3" # Wednesday at 01:00
workflow_dispatch: # allows you to manually trigger the workflow
permissions:
contents: write
pull-requests: write
checks: read
jobs:
combine-prs:
name: Combine
runs-on: ubuntu-latest
steps:
- name: combine-prs
id: combine-prs
uses: github/combine-prs@2909f404763c3177a456e052bdb7f2e85d3a7cb3 # v5.2.0
with:
labels: combined-pr
-64
View File
@@ -1,64 +0,0 @@
name: Notebook Check Pull Request
on:
pull_request_target:
types: [opened, reopened]
permissions:
contents: read
jobs:
comment-welcome:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Fetch pull request branch
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
- name: Fetch base develop branch
run: git fetch -u "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" develop:develop
- name: Create message
env:
HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_NUM: ${{ github.event.pull_request.number }}
run: |
# Preview links and tool usage only needed for notebook changes.
readarray -t changed_notebooks < <(git diff --name-only develop | grep '\.ipynb$' || true)
if [[ ${#changed_notebooks[@]} == 0 ]]; then
echo "No notebooks modified in this pull request."
else
msg="<h4>Preview</h4>\n"
msg+="Preview and run these notebook edits with Google Colab:\n<ul>\n"
# Link to PR branch in user's fork that is always current.
for fp in "${changed_notebooks[@]}"; do
gh_path="${HEAD_REPOSITORY}/blob/${HEAD_REF}/${fp}"
colab_url="https://colab.research.google.com/github/${gh_path}"
msg+="<li><a href='${colab_url}'>${fp}</a></li>\n"
done
msg+="</ul>\n"
reviewnb_url="https://app.reviewnb.com/${GITHUB_REPOSITORY}/pull/${PR_NUM}/files/"
msg+="Rendered <a href='${reviewnb_url}'>notebook diffs</a> available on ReviewNB.com.\n"
msg+="If commits are added to the pull request, synchronize your local branch: <code>git pull origin $HEAD_REF</code>\n"
fi
echo "MESSAGE=$msg" >> $GITHUB_ENV
- name: Post comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_URL: ${{ github.event.pull_request.issue_url }}
run: |
# Env var defined in previous step. Escape string for JSON.
body="$(echo -n -e $MESSAGE | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
# Add comment to pull request.
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $GITHUB_TOKEN" \
"${ISSUE_URL}/comments" \
--data "{\"body\": $body}"
-37
View File
@@ -1,37 +0,0 @@
name: 🔧 Poetry Check and Installation Test Workflow
on:
push:
paths:
- 'poetry.lock'
- 'pyproject.toml'
pull_request:
paths:
- 'poetry.lock'
- 'pyproject.toml'
workflow_dispatch:
jobs:
poetry-tests:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.os }}
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@v4
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: 📦 Install the base dependencies
run: python -m pip install --upgrade poetry
- name: 🔍 Check the correctness of the project config
run: poetry check
- name: 🚀 Do Install the package Test
run: poetry install
-53
View File
@@ -1,53 +0,0 @@
name: Docs WorkFlow - Develop Tag 📚
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
steps:
- name: 🔄 Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- 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 with mike
run: |
MKDOCS_GIT_COMMITTERS_APIKEY=${{ secrets.GITHUB_TOKEN }} mike deploy --push develop
+70
View File
@@ -0,0 +1,70 @@
name: Build and Publish Docs
on:
push:
branches:
- develop
workflow_dispatch:
release:
types: [published]
# Ensure only one concurrent deployment
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref}}
cancel-in-progress: true
# Restrict permissions by default
permissions:
contents: write # Required for committing to gh-pages
pages: write # Required for deploying to Pages
pull-requests: write # Required for PR comments
jobs:
deploy:
name: Publish Docs
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: 🐍 Install uv and set Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python-version }}
- name: 🔑 Create GitHub App token (mkdocs)
id: mkdocs_token
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2
with:
app-id: ${{ secrets.MKDOCS_APP_ID }}
private-key: ${{ secrets.MKDOCS_PEM }}
owner: roboflow
repositories: mkdocs-material-insiders
- name: 🏗️ Install dependencies
run: |
uv pip install -r pyproject.toml --extra docs
# Install mkdocs-material-insiders using the GitHub App token
uv pip install "git+https://roboflow:${{ steps.mkdocs_token.outputs.token }}@github.com/roboflow/mkdocs-material-insiders.git@9.5.49-insiders-4.53.14#egg=mkdocs-material[imaging]"
- 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 Development Docs
if: (github.event_name == 'push' && github.ref == 'refs/heads/develop') || github.event_name == 'workflow_dispatch'
run: |
MKDOCS_GIT_COMMITTERS_APIKEY=${{ secrets.GITHUB_TOKEN }} uv run mike deploy --push develop
- name: 🚀 Deploy Release Docs
if: github.event_name == 'release' && github.event.action == 'published'
run: |
latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
MKDOCS_GIT_COMMITTERS_APIKEY=${{ secrets.GITHUB_TOKEN }} uv run mike deploy --push --update-aliases $latest_tag latest
+45
View File
@@ -0,0 +1,45 @@
name: Publish Supervision Pre-Releases to PyPI
on:
push:
tags:
- "[0-9]+.[0-9]+[0-9]+.[0-9]+a[0-9]"
- "[0-9]+.[0-9]+[0-9]+.[0-9]+b[0-9]"
- "[0-9]+.[0-9]+[0-9]+.[0-9]+rc[0-9]"
workflow_dispatch:
permissions: {} # Explicitly remove all permissions by default
jobs:
publish-pre-release:
name: Publish Pre-release Package
runs-on: ubuntu-latest
environment:
name: test
url: https://pypi.org/project/supervision/
timeout-minutes: 10
permissions:
id-token: write # Required for PyPI publishing
contents: read # Required for checkout
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python-version }}
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --extra build
uv build
uv run twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
attestations: true
@@ -1,55 +0,0 @@
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
+43
View File
@@ -0,0 +1,43 @@
name: Publish Supervision Releases to PyPI
on:
push:
tags:
- "[0-9]+.[0-9]+[0-9]+.[0-9]"
workflow_dispatch:
permissions: {} # Explicitly remove all permissions by default
jobs:
publish-release:
name: Publish Release Package
runs-on: ubuntu-latest
environment:
name: release
url: https://pypi.org/project/supervision/
timeout-minutes: 10
permissions:
id-token: write # Required for PyPI publishing
contents: read # Required for checkout
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python-version }}
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --extra build
uv build
uv run twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
attestations: true
-43
View File
@@ -1,43 +0,0 @@
name: Publish Supervision Pre-Releases to PyPI and TestPyPI
on:
push:
tags:
- "[0-9]+.[0-9]+[0-9]+.[0-9]+a[0-9]"
- "[0-9]+.[0-9]+[0-9]+.[0-9]+b[0-9]"
- "[0-9]+.[0-9]+[0-9]+.[0-9]+rc[0-9]"
workflow_dispatch:
jobs:
build-and-publish-pre-release-pypi:
name: Build and publish to PyPI
runs-on: ubuntu-latest
environment: test
permissions:
id-token: write
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: 🏗️ Build source and wheel distributions
run: |
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1.10
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@release/v1.10
with:
repository-url: https://test.pypi.org/legacy/
+41
View File
@@ -0,0 +1,41 @@
name: Publish Supervision Releases to TestPyPI
on:
workflow_dispatch:
permissions: {} # Explicitly remove all permissions by default
jobs:
publish-testpypi:
name: Publish Release Package
runs-on: ubuntu-latest
environment:
name: release
url: https://pypi.org/project/supervision/
timeout-minutes: 10
permissions:
id-token: write # Required for PyPI publishing
contents: read # Required for checkout
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python-version }}
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --extra build
uv build
uv run twine check --strict dist/*
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
repository-url: https://test.pypi.org/legacy/
attestations: true
-40
View File
@@ -1,40 +0,0 @@
name: Publish Supervision Releases to PyPI and TestPyPI
on:
push:
tags:
- "[0-9]+.[0-9]+[0-9]+.[0-9]"
workflow_dispatch:
jobs:
build-and-publish-pre-release:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: 🏗️ Build source and wheel distributions
run: |
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1.10
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@release/v1.10
with:
repository-url: https://test.pypi.org/legacy/
+25 -9
View File
@@ -4,18 +4,34 @@ on:
pull_request:
branches: [main, develop]
# Restrict permissions by default
permissions:
contents: read # Required for checkout
checks: write # Required for test reporting
jobs:
docs-build-test:
name: Test docs build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 🔄 Checkout code
uses: actions/checkout@v4
- name: 🐍 Set up Python
uses: actions/setup-python@v5
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
python-version: '3.10'
- name: 🏗️ Install dependencies and Test Docs Build
fetch-depth: 0
- name: 🐍 Install uv and set Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python-version }}
- name: 🏗️ Install dependencies
run: |
python -m pip install --upgrade pip
pip install "mkdocs-material" "mkdocstrings[python]" "mkdocs-material[imaging]" mike "mkdocs-git-revision-date-localized-plugin" jupyterlab mkdocs-jupyter mkdocs-git-committers-plugin-2
mkdocs build --verbose
uv pip install -r pyproject.toml --extra docs
- name: 🧪 Test Docs Build
run: uv run mkdocs build --verbose
-58
View File
@@ -1,58 +0,0 @@
name: Python 3.8 - Min Dep Test WorkFlow
on:
pull_request:
branches: [main, develop]
jobs:
build-min-dep-test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
# id based on python version
id: python-setup
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: 📦 Install dependencies
run: |
python -m pip install --upgrade pip
pip install \
attrs==23.1.0 \
certifi==2023.7.22 \
charset-normalizer==2.0.12 \
cycler==0.12.1 \
exceptiongroup==1.1.3 \
fonttools==4.43.1 \
idna==3.4 \
iniconfig==2.0.0 \
kiwisolver==1.4.5 \
matplotlib==3.5.0 \
numpy==1.21.2 \
opencv-python==4.5.5.64 \
Pillow==10.1.0 \
packaging==23.2 \
pluggy==1.3.0 \
pyparsing==3.1.1 \
pytest==7.2.0 \
python-dateutil==2.8.2 \
PyYAML==5.3 \
requests==2.26.0 \
scipy==1.10.0 \
setuptools-scm==8.0.4 \
six==1.16.0 \
tomli==2.0.1 \
tqdm==4.62.3 \
typing_extensions==4.8.0 \
urllib3==1.26.18 \
defusedxml==0.7.1
- name: 🧪 Test
run: "python -m pytest ./test"
-29
View File
@@ -1,29 +0,0 @@
name: Test WorkFlow
on:
pull_request:
branches: [main, develop]
jobs:
build-dev-test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v4
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
check-latest: true
- name: 📦 Install dependencies
run: |
python -m pip install --upgrade pip
pip install .
pip install pytest
- name: 🧪 Test
run: "python -m pytest ./test"
+33
View File
@@ -0,0 +1,33 @@
name: 🔧 Pytest/Test Workflow
on:
pull_request:
branches: [main, develop]
jobs:
run-tests:
name: Import Test and Pytest Run
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.os }}
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
python-version: ${{ matrix.python-version }}
- name: 🚀 Install Packages
run: uv pip install -r pyproject.toml --extra dev --extra docs --extra metrics
- name: 🧪 Run the Import test
run: uv run python -c "import supervision; from supervision import assets; from supervision import metrics; print(supervision.__version__)"
- name: 🧪 Run the Test
run: uv run pytest
+3 -3
View File
@@ -25,14 +25,14 @@ repos:
- id: mixed-line-ending
- repo: https://github.com/PyCQA/bandit
rev: '1.7.10'
rev: '1.8.3'
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.3
rev: v0.11.6
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
@@ -48,7 +48,7 @@ repos:
# args: ["--number"]
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
rev: v2.4.1
hooks:
- id: codespell
additional_dependencies:
+20 -20
View File
@@ -128,15 +128,26 @@ PRs must pass all tests and linting requirements before they can be merged.
Before starting your work on the project, set up your development environment:
1. Clone your fork of the project:
1. Clone your fork of the project (recommended to use shallow clone of develop branch):
**Option A: Recommended for most contributors (shallow clone of develop branch):**
```bash
git clone --depth 1 -b develop https://github.com/YOUR_USERNAME/supervision.git
cd supervision
```
Replace `YOUR_USERNAME` with your GitHub username.
> Note: Using `--depth 1` creates a shallow clone with minimal history and `-b develop` ensures you start with the development branch. This significantly reduces download size while providing everything needed to contribute.
**Option B: Full repository clone (if you need complete history):**
```bash
git clone https://github.com/YOUR_USERNAME/supervision.git
cd supervision
```
Replace `YOUR_USERNAME` with your GitHub username.
2. Create and activate a virtual environment:
```bash
@@ -144,31 +155,20 @@ Before starting your work on the project, set up your development environment:
source .venv/bin/activate
```
3. Install Poetry:
3. Install `uv`:
Using pip:
```bash
pip install -U pip setuptools
pip install poetry
```
Or using pipx (recommended for global installation):
```bash
pipx install poetry
```
Follow the instructions on the [uv installation page](https://docs.astral.sh/uv/getting-started/installation/).
4. Install project dependencies:
```bash
poetry install
uv pip install -r pyproject.toml --extra dev --extra docs --extra metrics
```
5. Run pytest to verify the setup:
```bash
poetry run pytest
uv run pytest
```
## 🎨 Code Style and Quality
@@ -181,7 +181,7 @@ Furthermore, we have integrated a pre-commit GitHub Action into our workflow. Th
To run the pre-commit tool, follow these steps:
1. Install pre-commit by running the following command: `poetry install --with dev`. It will not only install pre-commit but also install all the deps and dev-deps of project
1. Install pre-commit by running the following command: `uv pip install -r pyproject.toml --extra dev`. It will not only install pre-commit but also install all the deps and dev-deps of project
2. Once pre-commit is installed, navigate to the project's root directory.
@@ -203,7 +203,7 @@ So far, **there is no type checking with mypy**. See [issue](https://github.com/
The `supervision` documentation is stored in a folder called `docs`. The project documentation is built using `mkdocs`.
To run the documentation, install the project requirements with `poetry install --with dev`. Then, run `mkdocs serve` to start the documentation server.
To run the documentation, install the project requirements with `uv pip install -r pyproject.toml --extra dev --extra docs`. Then, run `mkdocs serve` to start the documentation server.
You can learn more about mkdocs on the [mkdocs website](https://www.mkdocs.org/).
Vendored
+1 -1
View File
@@ -353,7 +353,7 @@
},
"outputs": [],
"source": [
"!pip install -q ultralytics"
"!pip install -q \"ultralytics<=8.3.40\""
]
},
{
-12
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Assets
@@ -8,17 +7,6 @@ status: new
Supervision offers an assets download utility that allows you to download video files
that you can use in your demos.
## Install extra
To install the Supervision assets utility, you can use `pip`. This utility is available
as an extra within the Supervision package.
!!! example "pip install"
```bash
pip install "supervision[assets]"
```
<div class="md-typeset">
<h2><a href="#supervision.assets.downloader.download_assets.download_assets">download_assets</a></h2>
</div>
+83 -35
View File
@@ -28,26 +28,28 @@ Annotators accept detections and apply box or mask visualizations to the detecti
</div>
=== "RoundBox"
<!--
=== "RoundBox"
```python
import supervision as sv
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
image = ...
detections = sv.Detections(...)
round_box_annotator = sv.RoundBoxAnnotator()
annotated_frame = round_box_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
round_box_annotator = sv.RoundBoxAnnotator()
annotated_frame = round_box_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
<div class="result" markdown>
<div class="result" markdown>
![round-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/round-box-annotator-example-purple.png){ align=center width="800" }
![round-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/round-box-annotator-example-purple.png){ align=center width="800" }
</div>
</div>
-->
=== "BoxCorner"
@@ -234,7 +236,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/mask-annotator-example-purple.png){ align=center width="800" }
![mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
mask-annotator-example-purple.png){ align=center width="800" }
</div>
@@ -255,7 +258,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![polygon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/polygon-annotator-example-purple.png){ align=center width="800" }
![polygon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
polygon-annotator-example-purple.png){ align=center width="800" }
</div>
@@ -283,7 +287,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
label-annotator-example-purple.png){ align=center width="800" }
</div>
@@ -314,7 +319,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
label-annotator-example-purple.png){ align=center width="800" }
</div>
@@ -341,24 +347,32 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![icon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/icon-annotator-example.png){ align=center width="800" }
![icon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
icon-annotator-example.png){ align=center width="800" }
</div>
=== "Crop"
<!-- === "Crop"
```python
import supervision as sv
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
image = ...
detections = sv.Detections(...)
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
<div class="result" markdown>
![crop-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
crop-annotator-example.png){ align=center width="800" }
</div>
-->
=== "Blur"
@@ -377,7 +391,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![blur-annotator-example](https://media.roboflow.com/supervision-annotator-examples/blur-annotator-example-purple.png){ align=center width="800" }
![blur-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
blur-annotator-example-purple.png){ align=center width="800" }
</div>
@@ -398,7 +413,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![pixelate-annotator-example](https://media.roboflow.com/supervision-annotator-examples/pixelate-annotator-example-10.png){ align=center width="800" }
![pixelate-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
pixelate-annotator-example-10.png){ align=center width="800" }
</div>
@@ -429,7 +445,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/trace-annotator-example-purple.png){ align=center width="800" }
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
trace-annotator-example-purple.png){ align=center width="800" }
</div>
@@ -458,7 +475,8 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![heat-map-annotator-example](https://media.roboflow.com/supervision-annotator-examples/heat-map-annotator-example-purple.png){ align=center width="800" }
![heat-map-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
heat-map-annotator-example-purple.png){ align=center width="800" }
</div>
@@ -479,7 +497,31 @@ Annotators accept detections and apply box or mask visualizations to the detecti
<div class="result" markdown>
![background-overlay-annotator-example](https://media.roboflow.com/supervision-annotator-examples/background-color-annotator-example-purple.png)
![background-overlay-annotator-example](https://media.roboflow.com/supervision-annotator-examples/background-color-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Comparison"
```python
import supervision as sv
image = ...
detections_1 = sv.Detections(...)
detections_2 = sv.Detections(...)
comparison_annotator = sv.ComparisonAnnotator()
annotated_frame = comparison_annotator.annotate(
scene=image.copy(),
detections_1=detections_1,
detections_2=detections_2
)
```
<div class="result" markdown>
![comparison-annotator-example](https://media.roboflow.com/supervision-annotator-examples/
comparison-annotator-example.png){ align=center width="800" }
</div>
@@ -622,6 +664,12 @@ Annotators accept detections and apply box or mask visualizations to the detecti
:::supervision.annotators.core.BackgroundOverlayAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.ComparisonAnnotator">ComparisonAnnotator</a></h2>
</div>
:::supervision.annotators.core.ComparisonAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.ColorLookup">ColorLookup</a></h2>
</div>
+12
View File
@@ -89,6 +89,18 @@ status: new
:::supervision.detection.utils.xywh_to_xyxy
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.xyxy_to_xywh">xyxy_to_xywh</a></h2>
</div>
:::supervision.detection.utils.xyxy_to_xywh
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.xyxy_to_xcycarh">xyxy_to_xcycarh</a></h2>
</div>
:::supervision.detection.utils.xyxy_to_xcycarh
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.xcycwh_to_xyxy">xcycwh_to_xyxy</a></h2>
</div>
+441
View File
@@ -0,0 +1,441 @@
---
comments: true
status: new
---
![Corgi Example](https://media.roboflow.com/supervision/image-examples/how-to/benchmark-models/corgi-sorted-2.png)
# Benchmark a Model
Have you ever trained multiple detection models and wondered which one performs best on your specific use case? Or maybe you've downloaded a pre-trained model and want to verify its performance on your dataset? Model benchmarking is essential for making informed decisions about which model to deploy in production.
This guide will show an easy way to benchmark your results using `supervision`. It will go over:
1. [Loading a dataset](#loading-a-dataset)
2. [Loading a model](#loading-a-model)
3. [Benchmarking Basics](#benchmarking-basics)
4. [Running a Model](#running-a-model)
5. [Remapping Classes](#remapping-classes)
6. [Visual Benchmarking](#visual-benchmarking)
7. [Benchmarking Metrics](#benchmarking-metrics)
8. [Mean Average Precision (mAP)](#mean-average-precision-map)
9. [F1 Score](#f1-score)
10. [Bonus: Model Leaderboard](#model-leaderboard)
This guide will use an instance segmentation model, but it applies to object detection, instance segmentation, and oriented bounding box models (OBB) too.
A condensed version of this guide is available as a [Colab Notebook](https://colab.research.google.com/drive/1HoOY9pZoVwGiRMmLHtir0qT6Uj45w6Ps?usp=sharing).
## Loading a Dataset
Suppose you start with a dataset. Perhaps you found it on [Universe](https://universe.roboflow.com/); perhaps you [labeled your own](https://roboflow.com/how-to-label/yolo11). In either case, this guide assumes you know of a labelled dataset at hand.
We'll use the following libraries:
- `roboflow` to manage the dataset and deploy models
- `inference` to run the models
- `supervision` to evaluate the model results
```bash
pip install roboflow supervision
pip install git+https://github.com/roboflow/inference.git@linas/allow-latest-rc-supervision
```
!!! info
We're updating `inference` at the moment. Please install it as shown above.
Here's how you can download a dataset:
```python
from roboflow import Roboflow
rf = Roboflow(api_key="<YOUR_API_KEY>")
project = rf.workspace("<WORKSPACE_NAME>").project("<PROJECT_NAME>")
dataset = project.version(<DATASET_VERSION_NUMBER>).download("<FORMAT>")
```
If your dataset is from Universe, go to `Dataset` > `Download Dataset` > select the format (e.g. `YOLOv11`) > `Show download code`.
If labeling your own data, go to the [dashboard](https://app.roboflow.com/) and check this [guide](https://docs.roboflow.com/api-reference/workspace-and-project-ids) to find your workspace and project IDs.
In this guide, we shall use a small [Corgi v2](https://universe.roboflow.com/model-examples/segmented-animals-basic) dataset. It is well-labeled and comes with a test set.
```python
from roboflow import Roboflow
rf = Roboflow(api_key="<YOUR_API_KEY>")
project = rf.workspace("fbamse1-gm2os").project("corgi-v2")
dataset = project.version(4).download("yolov11")
```
This will create a folder called `Corgi-v2-4` with the dataset in the current working directory, with `train`, `test`, and `valid` folders and a `data.yaml` file.
## Loading a Model
Let's load a model.
=== "Inference, Local"
Roboflow supports a range of state-of-the-art [pre-trained models](https://inference.roboflow.com/quickstart/aliases/) for object detection, instance segmentation, and pose tracking. You don't even need an API key!
Let's load such a model with inference [`inference`](https://inference.roboflow.com/).
```python
from inference import get_model
model = get_model(model_id="yolov11s-seg-640")
```
=== "Inference, Deployed"
You can train and deploy a model without leaving the Roboflow platform. See this [guide](https://docs.roboflow.com/train/train/train-from-scratch) for more details.
To load a model, you can use inference:
```python
from inference import get_model
model_id = "<PROJECT_NAME>/<MODEL_VERSION>"
model = get_model(model_id=model_id)
```
=== "Ultralytics"
Similarly to Inference, Ultralytics allows you to run a variety of models.
```bash
pip install "ultralytics<=8.3.40"
```
```python
from ultralytics import YOLO
model = YOLO("yolo11s-seg.pt")
```
## Benchmarking Basics
Evaluating your model requires careful selection of the dataset. Which images should you use?Let's go over the different scenarios.
- **Unrelated Dataset**: If you have a dataset that was not used to train the model, this is the best choice.
- **Training Set**: This is the set of images used to train the model. This is fine if the model was not trained on this dataset. Otherwise, **never** use it for benchmarking - the results will seem unrealistically good.
- **Validation Set**: This is the set of images used to validate the model during training. Every Nth training epoch, the model is evaluated on the validation set. Often the training is stopped once the validation loss stops improving. Therefore, even while the images aren't used to train the model, it still indirectly influences the training outcome.
- **Test Set**: This is the set of images kept aside for model testing. It is exactly the set you should use for benchmarking. If the dataset was split correctly, none of these images would be shown to the model during training.
Therefore, an unrelated dataset or the `test` set is the best choice for benchmarking.
Several other problems may arise:
- **Extra Classes**: An unrelated dataset may contain additional classes which you may need to [filter out](https://supervision.roboflow.com/how_to/filter_detections/#by-set-of-classes) before computing metrics.
- **Class Mismatch**: In an unrelated dataset, the class names or IDs may be different to what your model produces, you'll need to remap them, which is [shown in this guide](#running-a-model).
- **Data Contamination**: The `test` set may not be split correctly, with images from the test set also present in `training` or `validation` set and used during training. In this case, the results will be overly optimistic. This also applies when **very similar** images are used for training and testing - e.g. those taken in the same environment, same lighting conditions, similar angle, etc.
- **Missing Test Set**: Some datasets do not come with a test set. In this case, you should collect and [label](https://roboflow.com/annotate) your own data. Alternatively, a validation set could be used, but the results could be overly optimistic. Make sure to test in the real world as soon as possible.
## Running a Model
At this stage, you should have:
- A dataset of labeled images to evaluate the model.
- A model prepared for benchmarking.
With these ready, we can now run the model and obtain predictions.
We'll use `supervision` to create a dataset iterator, and then run the model on each image.
=== "Inference"
```python
import supervision as sv
test_set = sv.DetectionDataset.from_yolo(
images_directory_path=f"{dataset.location}/test/images",
annotations_directory_path=f"{dataset.location}/test/labels",
data_yaml_path=f"{dataset.location}/data.yaml"
)
image_paths = []
predictions_list = []
targets_list = []
for image_path, image, label in test_set:
result = model.infer(image)[0]
predictions = sv.Detections.from_inference(result)
image_paths.append(image_path)
predictions_list.append(predictions)
targets_list.append(label)
```
=== "Ultralytics"
```python
import supervision as sv
test_set = sv.DetectionDataset.from_yolo(
images_directory_path=f"{dataset.location}/test/images",
annotations_directory_path=f"{dataset.location}/test/labels",
data_yaml_path=f"{dataset.location}/data.yaml"
)
image_paths = []
predictions_list = []
targets_list = []
for image_path, image, label in test_set:
result = model(image)[0]
predictions = sv.Detections.from_ultralytics(result)
image_paths.append(image_path)
predictions_list.append(predictions)
targets_list.append(label)
```
## Remapping classes
Did you notice an issue in the above logic?
Since we're using an unrelated dataset, the class names and IDs may be different from what the model was trained on.
We need to remap them to match the dataset classes. Here's how to do it:
```python
def remap_classes(
detections: sv.Detections,
class_ids_from_to: dict[int, int],
class_names_from_to: dict[str, str]
) -> None:
new_class_ids = [
class_ids_from_to.get(class_id, class_id) for class_id in detections.class_id]
detections.class_id = np.array(new_class_ids)
new_class_names = [
class_names_from_to.get(name, name) for name in detections["class_name"]]
predictions["class_name"] = np.array(new_class_names)
```
Let's also remove the predictions that are not in the dataset classes.
=== "Inference"
Dataset class names and IDs can be found in the `data.yaml` file, or by printing `dataset.classes`.
```python
import supervision as sv
test_set = sv.DetectionDataset.from_yolo(
images_directory_path=f"{dataset.location}/test/images",
annotations_directory_path=f"{dataset.location}/test/labels",
data_yaml_path=f"{dataset.location}/data.yaml"
)
image_paths = []
predictions_list = []
targets_list = []
for image_path, image, label in test_set:
result = model.infer(image)[0]
predictions = sv.Detections.from_inference(result)
remap_classes(
detections=predictions,
class_ids_from_to={16: 0},
class_names_from_to={"dog": "Corgi"}
)
predictions = predictions[
np.isin(predictions["class_name"], test_set.classes)
]
image_paths.append(image_path)
predictions_list.append(predictions)
targets_list.append(label)
```
=== "Ultralytics"
Dataset class names and IDs can be found in the `data.yaml` file, or by printing `dataset.classes`.
Each model will have a different class mapping, so make sure to check the model's documentation. In this case, the model was trained on the COCO dataset, with a class
configuration found [here](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/datasets/coco8.yaml).
```python
import supervision as sv
test_set = sv.DetectionDataset.from_yolo(
images_directory_path=f"{dataset.location}/test/images",
annotations_directory_path=f"{dataset.location}/test/labels",
data_yaml_path=f"{dataset.location}/data.yaml"
)
image_paths = []
predictions_list = []
targets_list = []
for image_path, image, label in test_set:
result = model(image)[0]
predictions = sv.Detections.from_ultralytics(result)
remap_classes(
detections=predictions,
class_ids_from_to={16: 0},
class_names_from_to={"dog": "Corgi"}
)
predictions = predictions[
np.isin(predictions["class_name"], test_set.classes)
]
image_paths.append(image_path)
predictions_list.append(predictions)
targets_list.append(label)
```
## Visualizing Predictions
The first step in evaluating your model’s performance is to visualize its predictions.
This gives an intuitive sense of how well your model is detecting objects and where it might be failing.
```python
import supervision as sv
N = 9
GRID_SIZE = (3, 3)
target_annotator = sv.PolygonAnnotator(color=sv.Color.from_hex("#8315f9"), thickness=8)
prediction_annotator = sv.PolygonAnnotator(color=sv.Color.from_hex("#00cfc6"), thickness=6)
annotated_images = []
for image_path, predictions, targets in zip(
image_paths[:N], predictions_list[:N], targets_list[:N]
):
annotated_image = cv2.imread(image_path)
annotated_image = target_annotator.annotate(scene=annotated_image, detections=targets)
annotated_image = prediction_annotator.annotate(scene=annotated_image, detections=prediction)
annotated_images.append(annotated_image)
sv.plot_images_grid(images=annotated_images, grid_size=GRID_SIZE)
```
Here, predictions in purple are targets (ground truth), and predictions in teal are model predictions.
![Basic Model Comparison](https://media.roboflow.com/supervision/image-examples/how-to/benchmark-models/basic-model-comparison-corgi.png)
!!! tip
Use `sv.BoxAnnotator` for object detection and `sv.OrientedBoxAnnotator` for OBB.
See [annotator documentation](https://supervision.roboflow.com/latest/detection/annotators/) for even more options.
## Benchmarking Metrics
With multiple models, fine details matter. Visual inspection may not be enough. `supervision` provides a collection of metrics that help obtain precise numerical results of model performance.
### Mean Average Precision (mAP)
We'll start with [MeanAveragePrecision (mAP)](https://supervision.roboflow.com/latest/metrics/mean_average_precision/#supervision.metrics.mean_average_precision.MeanAveragePrecision), which is the most commonly used metric for object detection. It measures the average precision across all classes and IoU thresholds.
For a thorough explanation, check out our [blog](https://blog.roboflow.com/mean-average-precision/) and [Youtube video](https://www.youtube.com/watch?v=oqXDdxF_Wuw).
Here, the most popular value is `mAP 50:95`. It represents the average precision across all classes and IoU thresholds (`0.5` to `0.95`), whereas other values such as `mAP 50` or `mAP 75` only consider a single IoU threshold (`0.5` and `0.75` respectively).
Let's compute the mAP:
```python
from supervision.metrics import MeanAveragePrecision, MetricTarget
map_metric = MeanAveragePrecision(metric_target=MetricTarget.MASKS)
map_result = map_metric.update(predictions_list, targets_list).compute()
```
Try printing the result to see it at a glance:
```python
print(map_result)
```
```
MeanAveragePrecisionResult:
Metric target: MetricTarget.MASKS
Class agnostic: False
mAP @ 50:95: 0.2409
mAP @ 50: 0.3591
mAP @ 75: 0.2915
mAP scores: [0.35909 0.3468 0.34556 ...]
IoU thresh: [0.5 0.55 0.6 ...]
AP per class:
0: [0.35909 0.3468 0.34556 ...]
...
Small objects: ...
Medium objects: ...
Large objects: ...
```
You can also plot the results:
```python
map_result.plot()
```
![mAP Plot](https://media.roboflow.com/supervision/image-examples/how-to/benchmark-models/mAP-plot-corgi.png)
The metric also breaks down the results by detected object area. Small, medium and large are simply those with area less than 32², between 32² and 96², and greater than 96² pixels respectively.
### F1 Score
The [F1 Score](https://supervision.roboflow.com/latest/metrics/f1_score/) is another useful metric, especially when dealing with an imbalance between false positives and false negatives. It’s the harmonic mean of **precision** (how many predictions are correct) and **recall** (how many actual instances were detected).
Here's how you can compute the F1 score:
```python
from supervision.metrics import F1Score, MetricTarget
f1_metric = F1Score(metric_target=MetricTarget.MASKS)
f1_result = f1_metric.update(predictions_list, targets_list).compute()
```
As with mAP, you can also print the result:
```python
print(f1_result)
```
```
F1ScoreResult:
Metric target: MetricTarget.MASKS
Averaging method: AveragingMethod.WEIGHTED
F1 @ 50: 0.5341
F1 @ 75: 0.4636
F1 @ thresh: [0.53406 0.5278 0.52153 ...]
IoU thresh: [0.5 0.55 0.6 ...]
F1 per class:
0: [0.53406 0.5278 0.52153 ...]
...
Small objects: ...
Medium objects: ...
Large objects: ...
```
Similarly, you can plot the results:
```python
f1_result.plot()
```
![F1 Plot](https://media.roboflow.com/supervision/image-examples/how-to/benchmark-models/f1-score-corgi.png)
As with mAP, the metric also breaks down the results by detected object area. Small, medium and large are simply those with area less than 32², between 32² and 96², and greater than 96² pixels respectively.
## Model Leaderboard
Here to compare the basic models? We've got you covered. Check out our [Model Leaderboard](https://leaderboard.roboflow.com/) to see how different models perform and to get a sense of the state-of-the-art results. It's a great place to understand what the leading models can achieve and to compare your own results.
Even better, the repository is open source! You can see how the models were benchmarked, run the evaluation yourself, and even add your own models to the leaderboard. Check it out on [GitHub](https://github.com/roboflow/model-leaderboard)!
![Model Leaderboard Example](https://media.roboflow.com/model-leaderboard/model-leaderboard-example.png)
## Conclusion
In this guide, you've learned how to set up your environment, train or use pre-trained models, visualize predictions, and evaluate model performance with metrics like [mAP](https://supervision.roboflow.com/latest/metrics/mean_average_precision/), [F1 score](https://supervision.roboflow.com/latest/metrics/f1_score/), and got to know our Model Leaderboard.
A condensed version of this guide is also available as a [Colab Notebook](https://colab.research.google.com/drive/1HoOY9pZoVwGiRMmLHtir0qT6Uj45w6Ps?usp=sharing).
For more details, be sure to check out our [documentation](https://supervision.roboflow.com/latest/) and join our community discussions. If you find any issues, please let us know on [GitHub](https://github.com/roboflow/supervision/issues).
Best of luck with your benchmarking!
+8 -8
View File
@@ -55,7 +55,7 @@ it will be modified to include tracking, labeling, and trace annotations.
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model(frame)[0]
@@ -77,7 +77,7 @@ it will be modified to include tracking, labeling, and trace annotations.
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="yolov8n-640", api_key=<ROBOFLOW API KEY>)
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model.infer(frame)[0]
@@ -112,7 +112,7 @@ enabling the continuous following of the object's motion path across different f
model = YOLO("yolov8n.pt")
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model(frame)[0]
@@ -136,7 +136,7 @@ enabling the continuous following of the object's motion path across different f
model = get_roboflow_model(model_id="yolov8n-640", api_key=<ROBOFLOW API KEY>)
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model.infer(frame)[0]
@@ -168,7 +168,7 @@ offering a clear visual representation of each object's class and unique identif
model = YOLO("yolov8n.pt")
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
@@ -203,7 +203,7 @@ offering a clear visual representation of each object's class and unique identif
model = get_roboflow_model(model_id="yolov8n-640", api_key=<ROBOFLOW API KEY>)
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
@@ -250,7 +250,7 @@ movement patterns and interactions between objects in the video.
model = YOLO("yolov8n.pt")
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
trace_annotator = sv.TraceAnnotator()
@@ -288,7 +288,7 @@ movement patterns and interactions between objects in the video.
model = get_roboflow_model(model_id="yolov8n-640", api_key=<ROBOFLOW API KEY>)
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
trace_annotator = sv.TraceAnnotator()
+47 -8
View File
@@ -33,8 +33,9 @@ We write your reusable computer vision tools. Whether you need to load your data
You can install `supervision` in a
[**Python>=3.8**](https://www.python.org/) environment.
!!! example "pip install (recommended)"
=== "pip"
!!! example "Installation"
=== "pip (recommended)"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
@@ -44,6 +45,43 @@ You can install `supervision` in a
pip install supervision
```
=== "poetry"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
poetry add supervision
```
=== "uv"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
uv pip install supervision
```
For uv projects:
```bash
uv add supervision
```
=== "rye"
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
```bash
rye add supervision
```
!!! 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)
@@ -63,7 +101,7 @@ You can install `supervision` in a
=== "virtualenv"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
@@ -75,18 +113,19 @@ You can install `supervision` in a
pip install -e "."
```
=== "poetry"
=== "uv"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
poetry env use python3.10
poetry shell
uv venv
source .venv/bin/activate
# installation
poetry install
uv pip install -r pyproject.toml -e . --all-extras
```
## 🚀 Quickstart
+4
View File
@@ -0,0 +1,4 @@
!function(){var i="analytics",analytics=window[i]=window[i]||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","screen","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware","register"];analytics.factory=function(e){return function(){if(window[i].initialized)return window[i][e].apply(window[i],arguments);var n=Array.prototype.slice.call(arguments);if(["track","screen","alias","group","page","identify"].indexOf(e)>-1){var c=document.querySelector("link[rel='canonical']");n.push({__t:"bpc",c:c&&c.getAttribute("href")||void 0,p:location.pathname,u:location.href,s:location.search,t:document.title,r:document.referrer})}n.unshift(e);analytics.push(n);return analytics}};for(var n=0;n<analytics.methods.length;n++){var key=analytics.methods[n];analytics[key]=analytics.factory(key)}analytics.load=function(key,n){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.setAttribute("data-global-segment-analytics-key",i);t.src="https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";var r=document.getElementsByTagName("script")[0];r.parentNode.insertBefore(t,r);analytics._loadOptions=n};analytics._writeKey="rMvrPeZBJYyOPJSGCNhMlnTJb8VhFiWU";;analytics.SNIPPET_VERSION="5.2.0";
analytics.load("eohFog7VZiAhGJGEr5Sh7BM1mFKmUvDC");
document$.subscribe(analytics.page);
}}();
+1 -1
View File
@@ -130,7 +130,7 @@
},
"outputs": [],
"source": [
"!pip install -q ultralytics supervision==0.18.0"
"!pip install -q \"ultralytics<=8.3.40\" \"supervision==0.18.0\""
]
},
{
+1 -1
View File
@@ -300,7 +300,7 @@
},
"outputs": [],
"source": [
"!pip install -q ultralytics"
"!pip install -q \"ultralytics<=8.3.40\""
]
},
{
+1 -1
View File
@@ -17,7 +17,7 @@ https://github.com/roboflow/supervision/assets/26109316/f84db7b5-79e2-4142-a1da-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/count_people_in_zone
```
@@ -35,9 +35,7 @@ def load_zones_config(file_path: str) -> List[np.ndarray]:
def initiate_annotators(
polygons: List[np.ndarray], resolution_wh: Tuple[int, int]
) -> Tuple[
List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator]
]:
) -> Tuple[List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoxAnnotator]]:
line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh)
@@ -54,7 +52,7 @@ def initiate_annotators(
text_thickness=line_thickness * 2,
text_scale=text_scale * 2,
)
box_annotator = sv.BoundingBoxAnnotator(
box_annotator = sv.BoxAnnotator(
color=COLORS.by_idx(index), thickness=line_thickness
)
zones.append(zone)
@@ -97,7 +95,7 @@ def annotate(
frame: np.ndarray,
zones: List[sv.PolygonZone],
zone_annotators: List[sv.PolygonZoneAnnotator],
box_annotators: List[sv.BoundingBoxAnnotator],
box_annotators: List[sv.BoxAnnotator],
detections: sv.Detections,
) -> np.ndarray:
"""
@@ -108,7 +106,7 @@ def annotate(
zones (List[sv.PolygonZone]): A list of polygon zones used for detection.
zone_annotators (List[sv.PolygonZoneAnnotator]): A list of annotators for
drawing zone annotations.
box_annotators (List[sv.BoundingBoxAnnotator]): A list of annotators for
box_annotators (List[sv.BoxAnnotator]): A list of annotators for
drawing box annotations.
detections (sv.Detections): Detections to be used for annotation.
@@ -2,4 +2,4 @@ gdown
inference==0.9.17
supervision>=0.20.0
tqdm
ultralytics
ultralytics<=8.3.40
@@ -33,9 +33,7 @@ def load_zones_config(file_path: str) -> List[np.ndarray]:
def initiate_annotators(
polygons: List[np.ndarray], resolution_wh: Tuple[int, int]
) -> Tuple[
List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator]
]:
) -> Tuple[List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoxAnnotator]]:
line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh)
@@ -52,7 +50,7 @@ def initiate_annotators(
text_thickness=line_thickness * 2,
text_scale=text_scale * 2,
)
box_annotator = sv.BoundingBoxAnnotator(
box_annotator = sv.BoxAnnotator(
color=COLORS.by_idx(index), thickness=line_thickness
)
zones.append(zone)
@@ -94,7 +92,7 @@ def annotate(
frame: np.ndarray,
zones: List[sv.PolygonZone],
zone_annotators: List[sv.PolygonZoneAnnotator],
box_annotators: List[sv.BoundingBoxAnnotator],
box_annotators: List[sv.BoxAnnotator],
detections: sv.Detections,
) -> np.ndarray:
"""
@@ -105,7 +103,7 @@ def annotate(
zones (List[sv.PolygonZone]): A list of polygon zones used for detection.
zone_annotators (List[sv.PolygonZoneAnnotator]): A list of annotators for
drawing zone annotations.
box_annotators (List[sv.BoundingBoxAnnotator]): A list of annotators for
box_annotators (List[sv.BoxAnnotator]): A list of annotators for
drawing box annotations.
detections (sv.Detections): Detections to be used for annotation.
+1 -1
View File
@@ -11,7 +11,7 @@ supervision package for multiple tasks such as drawing heatmap annotations, trac
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/heatmap_and_track
```
+1 -1
View File
@@ -1,2 +1,2 @@
supervision[assets]==0.19.0
ultralytics
ultralytics<=8.3.40
+1 -1
View File
@@ -22,7 +22,7 @@ https://github.com/roboflow/supervision/assets/26109316/d50118c1-2ae4-458d-915a-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/speed_estimation
```
+1 -1
View File
@@ -16,7 +16,7 @@ https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/time_in_zone
```
+1 -1
View File
@@ -1,5 +1,5 @@
opencv-python
supervision>=0.20.0
ultralytics
ultralytics<=8.3.40
inference==0.9.17
pytube
+1 -1
View File
@@ -10,7 +10,7 @@ detection and Supervision for tracking and annotation.
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/tracking
```
+1 -1
View File
@@ -18,7 +18,7 @@ def process_video(
model = get_roboflow_model(model_id=model_id, api_key=roboflow_api_key)
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
frame_generator = sv.get_video_frames_generator(source_path=source_video_path)
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
+1 -1
View File
@@ -1,4 +1,4 @@
inference==0.9.17
supervision==0.19.0
tqdm
ultralytics
ultralytics<=8.3.40
+1 -1
View File
@@ -16,7 +16,7 @@ def process_video(
model = YOLO(source_weights_path)
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
frame_generator = sv.get_video_frames_generator(source_path=source_video_path)
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
+1 -1
View File
@@ -13,7 +13,7 @@ https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision/examples/traffic_analysis
```
+1 -1
View File
@@ -2,4 +2,4 @@ gdown
inference==0.9.17
supervision>=0.20.0
tqdm
ultralytics
ultralytics<=8.3.40
+8 -2
View File
@@ -42,6 +42,7 @@ nav:
- Detect Small Objects: how_to/detect_small_objects.md
- Track Objects on Video: how_to/track_objects.md
- Process Datasets: how_to/process_datasets.md
- Benchmark a Model: how_to/benchmark_a_model.md
- Reference - Code API:
- Detection and Segmentation:
@@ -148,9 +149,12 @@ plugins:
group_by_category: true
docstring_style: google
show_symbol_type_heading: true
show_root_heading: True
show_symbol_type_toc: true
show_category_heading: true
domains: [std, py]
inventories:
- url: https://docs.python-requests.org/en/master/objects.inv
domains: [std, py]
- git-committers:
repository: roboflow/supervision
branch: develop
@@ -175,11 +179,13 @@ markdown_extensions:
check_paths: true
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
extra_javascript:
- "https://widget.kapa.ai/kapa-widget.bundle.js"
- "javascripts/init_kapa_widget.js"
- "javascripts/cookbooks-card.js"
- "javascripts/segment.js"
- "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.8/purify.min.js"
# Messages shown during document build
Generated
-4785
View File
File diff suppressed because it is too large Load Diff
+58 -93
View File
@@ -1,18 +1,16 @@
[tool.poetry]
[project]
name = "supervision"
version = "0.25.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>",
"Linas Kondrackis <linas@roboflow.com>",
]
license = { text = "MIT" }
version = "0.26.0rc7"
readme = "README.md"
license = "MIT"
packages = [{ include = "supervision" }, { include = "supervision/py.typed" }]
homepage = "https://github.com/roboflow/supervision"
repository = "https://github.com/roboflow/supervision"
documentation = "https://supervision.roboflow.com/latest/"
requires-python = ">=3.8"
authors = [
{ name = "Piotr Skalski", email = "piotr.skalski92@gmail.com" }
]
maintainers = [
{ name = "Piotr Skalski", email = "piotr.skalski92@gmail.com" },
]
keywords = [
"machine-learning",
"deep-learning",
@@ -22,7 +20,6 @@ keywords = [
"AI",
"Roboflow",
]
classifiers = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
@@ -40,95 +37,59 @@ classifiers = [
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS',
]
[tool.poetry.dependencies]
python = "^3.8"
numpy = [
{ version = ">=1.21.2,<1.23.3", python = "<=3.10" },
{ version = ">=1.23.3", python = ">3.10" },
{ version = ">=2.1.0", python = ">=3.13" },
]
scipy = [
{ version = "1.10.0", python = "<3.9" },
{ version = "^1.10.0", python = ">=3.9" },
{ version = ">=1.14.1", python = ">=3.13" },
dependencies = [
"numpy>=1.21.2",
"scipy>=1.10.0",
"matplotlib>=3.6.0",
"pyyaml>=5.3",
"defusedxml>=0.7.1",
"pillow>=9.4",
"requests>=2.26.0",
"tqdm>=4.62.3",
"opencv-python>=4.5.5.64"
]
# Matplotlib sub-dependency
# The 'contourpy' package is required by Matplotlib for contour plotting.
# We need to ensure compatibility with both Python 3.8 and Python 3.13.
#
# For Python 3.8 and above, we use version 1.0.7 or higher, as it is the lowest major version that supports Python 3.8.
# For Python 3.13 and above, we use version 1.3.0 or higher, as it is the first version that explicitly supports Python 3.13.
contourpy = [
{ version = ">=1.0.7", python = ">=3.8" },
{ version = ">=1.3.0", python = ">=3.13" },
[project.urls]
Homepage = "https://github.com/roboflow/supervision"
Repository = "https://github.com/roboflow/supervision"
Documentation = "https://supervision.roboflow.com/latest/"
[project.optional-dependencies]
metrics = [
"pandas>=2.0.0",
]
matplotlib = ">=3.6.0"
pyyaml = ">=5.3"
defusedxml = "^0.7.1"
pillow = ">=9.4"
requests = { version = ">=2.26.0,<=2.32.3", optional = true }
tqdm = { version = ">=4.62.3,<=4.67.0", optional = true }
# pandas: picked lowest major version that supports Python 3.8
pandas = { version = ">=2.0.0", optional = true }
pandas-stubs = { version = ">=2.0.0.230412", optional = true }
opencv-python = ">=4.5.5.64"
[tool.poetry.extras]
assets = ["requests", "tqdm"]
metrics = ["pandas", "pandas-stubs"]
[tool.poetry.group.dev.dependencies]
twine = "^5.1.1"
pytest = ">=7.2.2,<9.0.0"
wheel = ">=0.40,<0.46"
build = ">=0.10,<1.3"
ruff = ">=0.1.0"
mypy = "^1.4.1"
pre-commit = "^3.3.3"
tox = "^4.11.4"
notebook = ">=6.5.3,<8.0.0"
ipywidgets = "^8.1.1"
jupytext = "^1.16.1"
nbconvert = "^7.14.2"
docutils = [
{ version = "^0.20.1", python = "<3.9" },
{ version = "^0.21.1", python = ">=3.9" },
dev = [
"pytest>=7.2.2,<9.0.0",
"tox>=4.11.4",
"notebook>=6.5.3,<8.0.0",
"ipywidgets>=8.1.1",
"jupytext>=1.16.1",
"nbconvert>=7.14.2",
"docutils!=0.21"
]
docs = [
"mkdocs-material[imaging]>=9.5.5",
"mkdocstrings>=0.25.2,<0.30.0",
"mkdocstrings-python>=1.10.9",
"mike>=2.0.0",
"mkdocs-jupyter>=0.24.3",
"mkdocs-git-committers-plugin-2>=2.4.1; python_version >= '3.8' and python_version < '4'",
"mkdocs-git-revision-date-localized-plugin>=1.2.4"
]
build = [
"twine>=5.1.1,<7.0.0",
"wheel>=0.40,<0.46",
"build>=0.10,<1.3"
]
[tool.poetry.group.docs.dependencies]
mkdocs-material = { extras = ["imaging"], version = "^9.5.5" }
mkdocstrings = ">=0.25.2,<0.27.0"
mkdocstrings-python = "^1.10.9"
mike = "^2.0.0"
# For Documentation Development use Python 3.10 or above
# Use Latest mkdocs-jupyter min 0.24.6 for Jupyter Notebook Theme support
mkdocs-jupyter = "^0.24.3"
mkdocs-git-committers-plugin-2 = "^2.4.1"
mkdocs-git-revision-date-localized-plugin = "^1.2.4"
[tool.poetry.group.typecheck]
optional = true
[tool.poetry.group.typecheck.dependencies]
types-pyyaml = "^6.0.12.20240808"
types-cffi = "^1.16.0.20240331"
types-requests = "^2.32.0.20240712"
types-tqdm = "^4.66.0.20240417"
[tool.bandit]
target = ["test", "supervision"]
tests = ["B201", "B301", "B318", "B314", "B303", "B413", "B412", "B410"]
tests = ["B201", "B301", "B318", "B314", "B303", "B413", "B412"]
[tool.autoflake]
check = true
imports = ["cv2", "supervision"]
[tool.ruff]
target-version = "py38"
@@ -252,7 +213,7 @@ skip-magic-trailing-comma = false
line-ending = "auto"
[tool.codespell]
skip = "*.ipynb,poetry.lock"
skip = "*.ipynb"
count = true
quiet-level = 3
ignore-words-list = "STrack,sTrack,strack"
@@ -261,8 +222,12 @@ ignore-words-list = "STrack,sTrack,strack"
include-package-data = false
[tool.setuptools.packages.find]
include = ["supervision*"]
exclude = ["docs*", "test*", "examples*"]
[tool.setuptools.package-data]
supervision = ["py.typed"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
+113 -2
View File
@@ -9,11 +9,11 @@ except importlib_metadata.PackageNotFoundError:
from supervision.annotators.core import (
BackgroundOverlayAnnotator,
BlurAnnotator,
BoundingBoxAnnotator,
BoxAnnotator,
BoxCornerAnnotator,
CircleAnnotator,
ColorAnnotator,
ComparisonAnnotator,
CropAnnotator,
DotAnnotator,
EllipseAnnotator,
@@ -45,7 +45,6 @@ from supervision.detection.line_zone import (
LineZoneAnnotator,
LineZoneAnnotatorMulticlass,
)
from supervision.detection.lmm import LMM
from supervision.detection.overlap_filter import (
OverlapFilter,
box_non_max_merge,
@@ -76,7 +75,11 @@ from supervision.detection.utils import (
scale_boxes,
xcycwh_to_xyxy,
xywh_to_xyxy,
xyxy_to_polygons,
xyxy_to_xcycarh,
xyxy_to_xywh,
)
from supervision.detection.vlm import LMM, VLM
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import (
calculate_optimal_line_thickness,
@@ -118,3 +121,111 @@ from supervision.utils.video import (
get_video_frames_generator,
process_video,
)
__all__ = [
"LMM",
"BackgroundOverlayAnnotator",
"BaseDataset",
"BlurAnnotator",
"BoxAnnotator",
"BoxCornerAnnotator",
"ByteTrack",
"CSVSink",
"CircleAnnotator",
"ClassificationDataset",
"Classifications",
"Color",
"ColorAnnotator",
"ColorLookup",
"ColorPalette",
"ComparisonAnnotator",
"ConfusionMatrix",
"CropAnnotator",
"DetectionDataset",
"Detections",
"DetectionsSmoother",
"DotAnnotator",
"EdgeAnnotator",
"EllipseAnnotator",
"FPSMonitor",
"HaloAnnotator",
"HeatMapAnnotator",
"IconAnnotator",
"ImageSink",
"InferenceSlicer",
"JSONSink",
"KeyPoints",
"LabelAnnotator",
"LineZone",
"LineZoneAnnotator",
"LineZoneAnnotatorMulticlass",
"MaskAnnotator",
"MeanAveragePrecision",
"OrientedBoxAnnotator",
"OverlapFilter",
"PercentageBarAnnotator",
"PixelateAnnotator",
"Point",
"PolygonAnnotator",
"PolygonZone",
"PolygonZoneAnnotator",
"Position",
"Rect",
"RichLabelAnnotator",
"RoundBoxAnnotator",
"TraceAnnotator",
"TriangleAnnotator",
"VertexAnnotator",
"VertexLabelAnnotator",
"VideoInfo",
"VideoSink",
"box_iou_batch",
"box_non_max_merge",
"box_non_max_suppression",
"calculate_masks_centroids",
"calculate_optimal_line_thickness",
"calculate_optimal_text_scale",
"clip_boxes",
"contains_holes",
"contains_multiple_segments",
"create_tiles",
"crop_image",
"cv2_to_pillow",
"draw_filled_polygon",
"draw_filled_rectangle",
"draw_image",
"draw_line",
"draw_polygon",
"draw_rectangle",
"draw_text",
"filter_polygons_by_area",
"get_polygon_center",
"get_video_frames_generator",
"letterbox_image",
"list_files_with_extensions",
"mask_iou_batch",
"mask_non_max_suppression",
"mask_to_polygons",
"mask_to_rle",
"mask_to_xyxy",
"move_boxes",
"move_masks",
"oriented_box_iou_batch",
"overlay_image",
"pad_boxes",
"pillow_to_cv2",
"plot_image",
"plot_images_grid",
"polygon_to_mask",
"polygon_to_xyxy",
"process_video",
"resize_image",
"rle_to_mask",
"scale_boxes",
"scale_image",
"xcycwh_to_xyxy",
"xywh_to_xyxy",
"xyxy_to_polygons",
"xyxy_to_xyah",
"xyxy_to_xywh",
]
+275 -95
View File
@@ -9,6 +9,7 @@ from PIL import Image, ImageDraw, ImageFont
from supervision.annotators.base import BaseAnnotator, ImageType
from supervision.annotators.utils import (
PENDING_TRACK_ID,
ColorLookup,
Trace,
resolve_color,
@@ -16,10 +17,16 @@ from supervision.annotators.utils import (
)
from supervision.config import CLASS_NAME_DATA_FIELD, ORIENTED_BOX_COORDINATES
from supervision.detection.core import Detections
from supervision.detection.utils import clip_boxes, mask_to_polygons, spread_out_boxes
from supervision.detection.utils import (
clip_boxes,
mask_to_polygons,
polygon_to_mask,
spread_out_boxes,
xyxy_to_polygons,
)
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import draw_polygon
from supervision.geometry.core import Position
from supervision.draw.utils import draw_polygon, draw_rounded_rectangle, draw_text
from supervision.geometry.core import Point, Position, Rect
from supervision.utils.conversion import (
ensure_cv2_image_for_annotation,
ensure_pil_image_for_annotation,
@@ -30,7 +37,6 @@ from supervision.utils.image import (
overlay_image,
scale_image,
)
from supervision.utils.internal import deprecated
CV2_FONT = cv2.FONT_HERSHEY_SIMPLEX
@@ -118,92 +124,6 @@ class BoxAnnotator(BaseAnnotator):
return scene
@deprecated(
"`BoundingBoxAnnotator` is deprecated and has been renamed to `BoxAnnotator`."
" `BoundingBoxAnnotator` will be removed in supervision-0.26.0."
)
class BoundingBoxAnnotator(BaseAnnotator):
"""
A class for drawing bounding boxes on an image using provided detections.
"""
def __init__(
self,
color: Union[Color, ColorPalette] = ColorPalette.DEFAULT,
thickness: int = 2,
color_lookup: ColorLookup = ColorLookup.CLASS,
):
"""
Args:
color (Union[Color, ColorPalette]): The color or color palette to use for
annotating detections.
thickness (int): Thickness of the bounding box lines.
color_lookup (ColorLookup): Strategy for mapping colors to annotations.
Options are `INDEX`, `CLASS`, `TRACK`.
"""
self.color: Union[Color, ColorPalette] = color
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@ensure_cv2_image_for_annotation
def annotate(
self,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> ImageType:
"""
Annotates the given scene with bounding boxes based on the provided detections.
Args:
scene (ImageType): The image where bounding boxes will be drawn. `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, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
bounding_box_annotator = sv.BoundingBoxAnnotator()
annotated_frame = bounding_box_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
![bounding-box-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/bounding-box-annotator-example-purple.png)
"""
assert isinstance(scene, np.ndarray)
for detection_idx in range(len(detections)):
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
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,
)
cv2.rectangle(
img=scene,
pt1=(x1, y1),
pt2=(x2, y2),
color=color.as_bgr(),
thickness=self.thickness,
)
return scene
class OrientedBoxAnnotator(BaseAnnotator):
"""
A class for drawing oriented bounding boxes on an image using provided detections.
@@ -1211,7 +1131,7 @@ class LabelAnnotator(BaseAnnotator):
detections: Detections, custom_labels: Optional[List[str]]
) -> List[str]:
if custom_labels is not None:
return custom_labels
return [str(label) for label in custom_labels]
labels = []
for idx in range(len(detections)):
@@ -1820,6 +1740,7 @@ class TraceAnnotator(BaseAnnotator):
"The `tracker_id` field is missing in the provided detections."
" See more: https://supervision.roboflow.com/latest/how_to/track_objects"
)
detections = detections[detections.tracker_id != PENDING_TRACK_ID]
self.trace.put(detections)
for detection_idx in range(len(detections)):
@@ -1938,11 +1859,9 @@ class HeatMapAnnotator(BaseAnnotator):
temp = temp.astype(np.uint8)
if self.kernel_size is not None:
temp = cv2.blur(temp, (self.kernel_size, self.kernel_size))
hsv = np.zeros(scene.shape)
hsv = np.full(scene.shape, 255, dtype=np.uint8)
hsv[..., 0] = temp
hsv[..., 1] = 255
hsv[..., 2] = 255
temp = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
temp = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
mask = cv2.cvtColor(self.heat_mask.astype(np.uint8), cv2.COLOR_GRAY2BGR) > 0
scene[mask] = cv2.addWeighted(temp, self.opacity, scene, 1 - self.opacity, 0)[
mask
@@ -2527,6 +2446,9 @@ class CropAnnotator(BaseAnnotator):
detections=detections
)
```
![crop-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/crop-annotator-example.png)
"""
assert isinstance(scene, np.ndarray)
crops = [
@@ -2683,3 +2605,261 @@ class BackgroundOverlayAnnotator(BaseAnnotator):
np.copyto(scene, colored_mask)
return scene
class ComparisonAnnotator:
"""
Highlights the differences between two sets of detections.
Useful for comparing results from two different models, or the difference
between a ground truth and a prediction.
If present, uses the oriented bounding box data.
Otherwise, if present, uses a mask.
Otherwise, uses the bounding box data.
"""
def __init__(
self,
color_1: Color = Color.RED,
color_2: Color = Color.GREEN,
color_overlap: Color = Color.BLUE,
*,
opacity: float = 0.75,
label_1: str = "",
label_2: str = "",
label_overlap: str = "",
label_scale: float = 1.0,
):
"""
Args:
color_1 (Color): Color of areas only present in the first set of
detections.
color_2 (Color): Color of areas only present in the second set of
detections.
color_overlap (Color): Color of areas present in both sets of detections.
opacity (float): Annotator opacity, from `0` to `1`.
label_1 (str): Label for the first set of detections.
label_2 (str): Label for the second set of detections.
label_overlap (str): Label for areas present in both sets of detections.
label_scale (float): Controls how large the labels are.
"""
self.color_1 = color_1
self.color_2 = color_2
self.color_overlap = color_overlap
self.opacity = opacity
self.label_1 = label_1
self.label_2 = label_2
self.label_overlap = label_overlap
self.label_scale = label_scale
self.text_thickness = int(self.label_scale + 1.2)
@ensure_cv2_image_for_annotation
def annotate(
self, scene: ImageType, detections_1: Detections, detections_2: Detections
) -> ImageType:
"""
Highlights the differences between two sets of detections.
Args:
scene (ImageType): The image where detections will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections_1 (Detections): The first set of detections or predictions.
detections_2 (Detections): The second set of detections to compare or
ground truth.
Returns:
The annotated image.
Example:
```python
import supervision as sv
image = ...
detections_1 = sv.Detections(...)
detections_2 = sv.Detections(...)
comparison_annotator = sv.ComparisonAnnotator()
annotated_frame = comparison_annotator.annotate(
scene=image.copy(),
detections_1=detections_1,
detections_2=detections_2
)
```
![comparison-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/comparison-annotator-example.png)
"""
assert isinstance(scene, np.ndarray)
if detections_1.is_empty() and detections_2.is_empty():
return scene
use_obb = self._use_obb(detections_1, detections_2)
use_mask = self._use_mask(detections_1, detections_2)
if use_obb:
mask_1 = self._mask_from_obb(scene, detections_1)
mask_2 = self._mask_from_obb(scene, detections_2)
elif use_mask:
mask_1 = self._mask_from_mask(scene, detections_1)
mask_2 = self._mask_from_mask(scene, detections_2)
else:
mask_1 = self._mask_from_xyxy(scene, detections_1)
mask_2 = self._mask_from_xyxy(scene, detections_2)
mask_overlap = mask_1 & mask_2
mask_1 = mask_1 & ~mask_overlap
mask_2 = mask_2 & ~mask_overlap
color_layer = np.zeros_like(scene, dtype=np.uint8)
color_layer[mask_overlap] = self.color_overlap.as_bgr()
color_layer[mask_1] = self.color_1.as_bgr()
color_layer[mask_2] = self.color_2.as_bgr()
scene[mask_overlap] = (1 - self.opacity) * scene[
mask_overlap
] + self.opacity * color_layer[mask_overlap]
scene[mask_1] = (1 - self.opacity) * scene[mask_1] + self.opacity * color_layer[
mask_1
]
scene[mask_2] = (1 - self.opacity) * scene[mask_2] + self.opacity * color_layer[
mask_2
]
self._draw_labels(scene)
return scene
@staticmethod
def _use_obb(detections_1: Detections, detections_2: Detections) -> bool:
assert not detections_1.is_empty() or not detections_2.is_empty()
is_obb_1 = ORIENTED_BOX_COORDINATES in detections_1.data
is_obb_2 = ORIENTED_BOX_COORDINATES in detections_2.data
return (
(is_obb_1 and is_obb_2)
or (is_obb_1 and detections_2.is_empty())
or (detections_1.is_empty() and is_obb_2)
)
@staticmethod
def _use_mask(detections_1: Detections, detections_2: Detections) -> bool:
assert not detections_1.is_empty() or not detections_2.is_empty()
is_mask_1 = detections_1.mask is not None
is_mask_2 = detections_2.mask is not None
return (
(is_mask_1 and is_mask_2)
or (is_mask_1 and detections_2.is_empty())
or (detections_1.is_empty() and is_mask_2)
)
@staticmethod
def _mask_from_xyxy(scene: np.ndarray, detections: Detections) -> np.ndarray:
mask = np.zeros(scene.shape[:2], dtype=np.bool_)
if detections.is_empty():
return mask
resolution_wh = scene.shape[1], scene.shape[0]
polygons = xyxy_to_polygons(detections.xyxy)
for polygon in polygons:
polygon_mask = polygon_to_mask(polygon, resolution_wh=resolution_wh)
mask |= polygon_mask.astype(np.bool_)
return mask
@staticmethod
def _mask_from_obb(scene: np.ndarray, detections: Detections) -> np.ndarray:
mask = np.zeros(scene.shape[:2], dtype=np.bool_)
if detections.is_empty():
return mask
resolution_wh = scene.shape[1], scene.shape[0]
for polygon in detections.data[ORIENTED_BOX_COORDINATES]:
polygon_mask = polygon_to_mask(polygon, resolution_wh=resolution_wh)
mask |= polygon_mask.astype(np.bool_)
return mask
@staticmethod
def _mask_from_mask(scene: np.ndarray, detections: Detections) -> np.ndarray:
mask = np.zeros(scene.shape[:2], dtype=np.bool_)
if detections.is_empty():
return mask
assert detections.mask is not None
for detections_mask in detections.mask:
mask |= detections_mask.astype(np.bool_)
return mask
def _draw_labels(self, scene: np.ndarray) -> None:
"""
Draw the labels, explaining what each color represents, with automatically
computed positions.
Args:
scene (np.ndarray): The image where the labels will be drawn.
"""
margin = int(50 * self.label_scale)
gap = int(40 * self.label_scale)
y0 = int(50 * self.label_scale)
height = int(50 * self.label_scale)
marker_size = int(20 * self.label_scale)
padding = int(10 * self.label_scale)
text_box_corner_radius = int(10 * self.label_scale)
marker_corner_radius = int(4 * self.label_scale)
text_scale = self.label_scale
label_color_pairs = [
(self.label_1, self.color_1),
(self.label_2, self.color_2),
(self.label_overlap, self.color_overlap),
]
x0 = margin
for text, color in label_color_pairs:
if not text:
continue
(text_w, _) = cv2.getTextSize(
text=text,
fontFace=CV2_FONT,
fontScale=self.label_scale,
thickness=self.text_thickness,
)[0]
width = text_w + marker_size + padding * 4
center_x = x0 + width // 2
center_y = y0 + height // 2
draw_rounded_rectangle(
scene=scene,
rect=Rect(x=x0, y=y0, width=width, height=height),
color=Color.WHITE,
border_radius=text_box_corner_radius,
)
draw_rounded_rectangle(
scene=scene,
rect=Rect(
x=x0 + padding,
y=center_y - marker_size / 2,
width=marker_size,
height=marker_size,
),
color=color,
border_radius=marker_corner_radius,
)
draw_text(
scene,
text,
text_anchor=Point(x=center_x + marker_size, y=center_y),
text_scale=text_scale,
text_thickness=self.text_thickness,
)
x0 += width + gap
+5
View File
@@ -7,6 +7,9 @@ from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.geometry.core import Position
PENDING_TRACK_COLOR = Color.GREY
PENDING_TRACK_ID = -1
class ColorLookup(Enum):
"""
@@ -136,6 +139,8 @@ def resolve_color(
detection_idx=detection_idx,
color_lookup=color_lookup,
)
if color_lookup == ColorLookup.TRACK and idx == PENDING_TRACK_ID:
return PENDING_TRACK_COLOR
return get_color_by_index(color=color, idx=idx)
+2
View File
@@ -1,2 +1,4 @@
from supervision.assets.downloader import download_assets
from supervision.assets.list import VideoAssets
__all__ = ["VideoAssets", "download_assets"]
+3 -12
View File
@@ -4,19 +4,10 @@ from pathlib import Path
from shutil import copyfileobj
from typing import Union
from supervision.assets.list import VIDEO_ASSETS, VideoAssets
from requests import get
from tqdm.auto import tqdm
try:
from requests import get
from tqdm.auto import tqdm
except ImportError:
raise ValueError(
"\n"
"Please install requests and tqdm to download assets \n"
"or install supervision with assets \n"
"pip install supervision[assets] \n"
"\n"
)
from supervision.assets.list import VIDEO_ASSETS, VideoAssets
def is_md5_hash_matching(filename: str, original_md5_hash: str) -> bool:
-22
View File
@@ -705,28 +705,6 @@ class ClassificationDataset(BaseDataset):
"a list of paths `List[str]` instead."
)
@property
@deprecated(
"`DetectionDataset.images` property is deprecated and will be removed in "
"`supervision-0.26.0`. Iterate with `for path, image, annotation in dataset:` "
"instead."
)
def images(self) -> Dict[str, np.ndarray]:
"""
Load all images to memory and return them as a dictionary.
!!! warning
Only use this when you need all images at once.
It is much more memory-efficient to initialize dataset with
image paths and use `for path, image, annotation in dataset:`.
"""
if self._images_in_memory:
return self._images_in_memory
images = {image_path: cv2.imread(image_path) for image_path in self.image_paths}
return images
def _get_image(self, image_path: str) -> np.ndarray:
"""Assumes that image is in dataset"""
if self._images_in_memory:
+21 -4
View File
@@ -2,8 +2,8 @@ import os
from pathlib import Path
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
import cv2
import numpy as np
from PIL import Image
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.dataset.utils import approximate_mask_with_polygons
@@ -153,7 +153,18 @@ def load_yolo_annotations(
image_paths = [
str(path)
for path in list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
directory=images_directory_path,
extensions=[
"bmp",
"dng",
"jpg",
"jpeg",
"mpo",
"png",
"tif",
"tiff",
"webp",
],
)
]
@@ -167,10 +178,16 @@ def load_yolo_annotations(
annotations[image_path] = Detections.empty()
continue
image = cv2.imread(image_path)
# PIL is much faster than cv2 for checking image shape and mode: https://github.com/roboflow/supervision/issues/1554
image = Image.open(image_path)
lines = read_txt_file(file_path=annotation_path, skip_empty=True)
h, w, _ = image.shape
w, h = image.size
resolution_wh = (w, h)
if image.mode not in ("RGB", "L"):
raise ValueError(
f"Images must be 'RGB' or 'grayscale', \
but {image_path} mode is '{image.mode}'."
)
with_masks = _with_mask(lines=lines)
with_masks = force_masks if force_masks else with_masks
+72 -29
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from contextlib import suppress
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
import numpy as np
@@ -10,12 +10,6 @@ from supervision.config import (
CLASS_NAME_DATA_FIELD,
ORIENTED_BOX_COORDINATES,
)
from supervision.detection.lmm import (
LMM,
from_florence_2,
from_paligemma,
validate_lmm_parameters,
)
from supervision.detection.overlap_filter import (
box_non_max_merge,
box_non_max_suppression,
@@ -39,8 +33,16 @@ from supervision.detection.utils import (
process_roboflow_result,
xywh_to_xyxy,
)
from supervision.detection.vlm import (
LMM,
VLM,
from_florence_2,
from_paligemma,
from_qwen_2_5_vl,
validate_vlm_parameters,
)
from supervision.geometry.core import Position
from supervision.utils.internal import get_instance_variables
from supervision.utils.internal import deprecated, get_instance_variables
from supervision.validators import validate_detections_fields
@@ -117,7 +119,7 @@ class Detections:
xyxy (np.ndarray): An array of shape `(n, 4)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
mask: (Optional[np.ndarray]): An array of shape
`(n, H, W)` containing the segmentation masks.
`(n, H, W)` containing the segmentation masks (`bool` data type).
confidence (Optional[np.ndarray]): An array of shape
`(n,)` containing the confidence scores of the detections.
class_id (Optional[np.ndarray]): An array of shape
@@ -606,8 +608,10 @@ class Detections:
detections = sv.Detections.from_inference(result)
```
"""
with suppress(AttributeError):
if hasattr(roboflow_result, "dict"):
roboflow_result = roboflow_result.dict(exclude_none=True, by_alias=True)
elif hasattr(roboflow_result, "json"):
roboflow_result = roboflow_result.json()
xyxy, confidence, class_id, masks, trackers, data = process_roboflow_result(
roboflow_result=roboflow_result
)
@@ -711,7 +715,7 @@ class Detections:
"""
if "error" in azure_result:
raise ValueError(
f'Azure API returned an error {azure_result["error"]["message"]}'
f"Azure API returned an error {azure_result['error']['message']}"
)
xyxy, confidences, class_ids = [], [], []
@@ -798,6 +802,10 @@ class Detections:
)
@classmethod
@deprecated(
"`Detections.from_lmm` property is deprecated and will be removed in "
"`supervision-0.31.0`. Use Detections.from_vlm instead."
)
def from_lmm(
cls, lmm: Union[LMM, str], result: Union[str, dict], **kwargs: Any
) -> Detections:
@@ -836,16 +844,51 @@ class Detections:
# array([0])
```
"""
lmm = validate_lmm_parameters(lmm, result, kwargs)
# filler logic mapping old from_lmm to new from_vlm
lmm_to_vlm = {
LMM.PALIGEMMA: VLM.PALIGEMMA,
LMM.FLORENCE_2: VLM.FLORENCE_2,
LMM.QWEN_2_5_VL: VLM.QWEN_2_5_VL,
}
if lmm == LMM.PALIGEMMA:
assert isinstance(result, str)
# (this works even if the LMM enum is wrapped by @deprecated)
if isinstance(lmm, Enum) and lmm.__class__.__name__ == "LMM":
vlm = lmm_to_vlm[lmm]
elif isinstance(lmm, str):
try:
lmm_enum = LMM(lmm.lower())
except ValueError:
raise ValueError(
f"Invalid LMM string '{lmm}'. Must be one of "
f"{[m.value for m in LMM]}"
)
vlm = lmm_to_vlm[lmm_enum]
else:
raise ValueError(
f"Invalid type for 'lmm': {type(lmm)}. Must be LMM or str."
)
return cls.from_vlm(vlm=vlm, result=result, **kwargs)
@classmethod
def from_vlm(
cls, vlm: Union[VLM, str], result: Union[str, dict], **kwargs: Any
) -> Detections:
vlm = validate_vlm_parameters(vlm, result, kwargs)
if vlm == VLM.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)
if lmm == LMM.FLORENCE_2:
assert isinstance(result, dict)
if vlm == VLM.QWEN_2_5_VL:
xyxy, class_id, class_name = from_qwen_2_5_vl(result, **kwargs)
data = {CLASS_NAME_DATA_FIELD: class_name}
return cls(xyxy=xyxy, class_id=class_id, data=data)
if vlm == VLM.FLORENCE_2:
xyxy, labels, mask, xyxyxyxy = from_florence_2(result, **kwargs)
if len(xyxy) == 0:
return cls.empty()
@@ -858,8 +901,6 @@ class Detections:
return cls(xyxy=xyxy, mask=mask, data=data)
raise ValueError(f"Unsupported LMM: {lmm}")
@classmethod
def from_easyocr(cls, easyocr_results: list) -> Detections:
"""
@@ -1026,16 +1067,16 @@ class Detections:
detections_1 = sv.Detections(
xyxy=np.array([[15, 15, 100, 100], [200, 200, 300, 300]]),
class_id=np.array([1, 2]),
data={'feature_vector': np.array([0.1, 0.2)])}
)
data={'feature_vector': np.array([0.1, 0.2])}
)
detections_2 = sv.Detections(
xyxy=np.array([[30, 30, 120, 120]]),
class_id=np.array([1]),
data={'feature_vector': [np.array([0.3])]}
)
data={'feature_vector': np.array([0.3])}
)
merged_detections = Detections.merge([detections_1, detections_2])
merged_detections = sv.Detections.merge([detections_1, detections_2])
merged_detections.xyxy
array([[ 15, 15, 100, 100],
@@ -1201,6 +1242,8 @@ class Detections:
"""
if isinstance(index, str):
return self.data.get(index)
if self.is_empty():
return self
if isinstance(index, int):
index = [index]
return Detections(
@@ -1303,9 +1346,9 @@ class Detections:
if len(self) == 0:
return self
assert (
self.confidence is not None
), "Detections confidence must be given for NMS to be executed."
assert self.confidence is not None, (
"Detections confidence must be given for NMS to be executed."
)
if class_agnostic:
predictions = np.hstack((self.xyxy, self.confidence.reshape(-1, 1)))
@@ -1359,9 +1402,9 @@ class Detections:
if len(self) == 0:
return self
assert (
self.confidence is not None
), "Detections confidence must be given for NMM to be executed."
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)))
-184
View File
@@ -1,184 +0,0 @@
import re
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union
import numpy as np
from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy
class LMM(Enum):
PALIGEMMA = "paligemma"
FLORENCE_2 = "florence_2"
RESULT_TYPES: Dict[LMM, type] = {LMM.PALIGEMMA: str, LMM.FLORENCE_2: dict}
REQUIRED_ARGUMENTS: Dict[LMM, List[str]] = {
LMM.PALIGEMMA: ["resolution_wh"],
LMM.FLORENCE_2: ["resolution_wh"],
}
ALLOWED_ARGUMENTS: Dict[LMM, List[str]] = {
LMM.PALIGEMMA: ["resolution_wh", "classes"],
LMM.FLORENCE_2: ["resolution_wh"],
}
SUPPORTED_TASKS_FLORENCE_2 = [
"<OD>",
"<CAPTION_TO_PHRASE_GROUNDING>",
"<DENSE_REGION_CAPTION>",
"<REGION_PROPOSAL>",
"<OCR_WITH_REGION>",
"<REFERRING_EXPRESSION_SEGMENTATION>",
"<REGION_TO_SEGMENTATION>",
"<OPEN_VOCABULARY_DETECTION>",
"<REGION_TO_CATEGORY>",
"<REGION_TO_DESCRIPTION>",
]
def validate_lmm_parameters(
lmm: Union[LMM, str], result: Any, kwargs: Dict[str, Any]
) -> LMM:
if isinstance(lmm, str):
try:
lmm = LMM(lmm.lower())
except ValueError:
raise ValueError(
f"Invalid lmm value: {lmm}. Must be one of {[e.value for e in LMM]}"
)
if not isinstance(result, RESULT_TYPES[lmm]):
raise ValueError(
f"Invalid LMM result type: {type(result)}. Must be {RESULT_TYPES[lmm]}"
)
required_args = REQUIRED_ARGUMENTS.get(lmm, [])
for arg in required_args:
if arg not in kwargs:
raise ValueError(f"Missing required argument: {arg}")
allowed_args = ALLOWED_ARGUMENTS.get(lmm, [])
for arg in kwargs:
if arg not in allowed_args:
raise ValueError(f"Argument {arg} is not allowed for {lmm.name}")
return lmm
def from_paligemma(
result: str, resolution_wh: Tuple[int, int], classes: Optional[List[str]] = None
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
w, h = resolution_wh
pattern = re.compile(
r"(?<!<loc\d{4}>)<loc(\d{4})><loc(\d{4})><loc(\d{4})><loc(\d{4})> ([\w\s\-]+)"
)
matches = pattern.findall(result)
matches = np.array(matches) if matches else np.empty((0, 5))
xyxy, class_name = matches[:, [1, 0, 3, 2]], matches[:, 4]
xyxy = xyxy.astype(int) / 1024 * np.array([w, h, w, h])
class_name = np.char.strip(class_name.astype(str))
class_id = None
if classes is not None:
mask = np.array([name in classes for name in class_name]).astype(bool)
xyxy, class_name = xyxy[mask], class_name[mask]
class_id = np.array([classes.index(name) for name in class_name])
return xyxy, class_id, class_name
def from_florence_2(
result: dict, resolution_wh: Tuple[int, int]
) -> Tuple[
np.ndarray, Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray]
]:
"""
Parse results from the Florence 2 multi-model model.
https://huggingface.co/microsoft/Florence-2-large
Parameters:
result: dict containing the model output
Returns:
xyxy (np.ndarray): An array of shape `(n, 4)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
labels: (Optional[np.ndarray]): An array of shape `(n,)` containing
the class labels for each bounding box
masks: (Optional[np.ndarray]): An array of shape `(n, h, w)` containing
the segmentation masks for each bounding box
obb_boxes: (Optional[np.ndarray]): An array of shape `(n, 4, 2)` containing
oriented bounding boxes.
"""
assert len(result) == 1, f"Expected result with a single element. Got: {result}"
task = next(iter(result.keys()))
if task not in SUPPORTED_TASKS_FLORENCE_2:
raise ValueError(
f"{task} not supported. Supported tasks are: {SUPPORTED_TASKS_FLORENCE_2}"
)
result = result[task]
if task in ["<OD>", "<CAPTION_TO_PHRASE_GROUNDING>", "<DENSE_REGION_CAPTION>"]:
xyxy = np.array(result["bboxes"], dtype=np.float32)
labels = np.array(result["labels"])
return xyxy, labels, None, None
if task == "<REGION_PROPOSAL>":
xyxy = np.array(result["bboxes"], dtype=np.float32)
# provides labels, but they are ["", "", "", ...]
return xyxy, None, None, None
if task == "<OCR_WITH_REGION>":
xyxyxyxy = np.array(result["quad_boxes"], dtype=np.float32)
xyxyxyxy = xyxyxyxy.reshape(-1, 4, 2)
xyxy = np.array([polygon_to_xyxy(polygon) for polygon in xyxyxyxy])
labels = np.array(result["labels"])
return xyxy, labels, None, xyxyxyxy
if task in ["<REFERRING_EXPRESSION_SEGMENTATION>", "<REGION_TO_SEGMENTATION>"]:
xyxy_list = []
masks_list = []
for polygons_of_same_class in result["polygons"]:
for polygon in polygons_of_same_class:
polygon = np.reshape(polygon, (-1, 2)).astype(np.int32)
mask = polygon_to_mask(polygon, resolution_wh).astype(bool)
masks_list.append(mask)
xyxy = polygon_to_xyxy(polygon)
xyxy_list.append(xyxy)
# per-class labels also provided, but they are ["", "", "", ...]
# when we figure out how to set class names, we can do
# zip(result["labels"], result["polygons"])
xyxy = np.array(xyxy_list, dtype=np.float32)
masks = np.array(masks_list)
return xyxy, None, masks, None
if task == "<OPEN_VOCABULARY_DETECTION>":
xyxy = np.array(result["bboxes"], dtype=np.float32)
labels = np.array(result["bboxes_labels"])
# Also has "polygons" and "polygons_labels", but they don't seem to be used
return xyxy, labels, None, None
if task in ["<REGION_TO_CATEGORY>", "<REGION_TO_DESCRIPTION>"]:
assert isinstance(
result, str
), f"Expected string as <REGION_TO_CATEGORY> result, got {type(result)}"
if result == "No object detected.":
return np.empty((0, 4), dtype=np.float32), np.array([]), None, None
pattern = re.compile(r"<loc_(\d+)><loc_(\d+)><loc_(\d+)><loc_(\d+)>")
match = pattern.search(result)
assert (
match is not None
), f"Expected string to end in location tags, but got {result}"
w, h = resolution_wh
xyxy = np.array([match.groups()], dtype=np.float32)
xyxy *= np.array([w, h, w, h]) / 1000
result_string = result[: match.start()]
labels = np.array([result_string])
return xyxy, labels, None, None
assert False, f"Unimplemented task: {task}"
+1 -2
View File
@@ -226,8 +226,7 @@ def box_non_max_merge(
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}"
f"Empty group detected when non-max-merging detections: {merge_groups}"
)
return merge_groups
+163 -11
View File
@@ -11,6 +11,25 @@ from supervision.geometry.core import Vector
MIN_POLYGON_POINT_COUNT = 3
def xyxy_to_polygons(box: np.ndarray) -> np.ndarray:
"""
Convert an array of boxes to an array of polygons.
Retains the input datatype.
Args:
box (np.ndarray): An array of boxes (N, 4), where each box is represented as a
list of four coordinates in the format `(x_min, y_min, x_max, y_max)`.
Returns:
np.ndarray: An array of polygons (N, 4, 2), where each polygon is
represented as a list of four coordinates in the format `(x, y)`.
"""
polygon = np.zeros((box.shape[0], 4, 2), dtype=box.dtype)
polygon[:, :, 0] = box[:, [0, 2, 2, 0]]
polygon[:, :, 1] = box[:, [1, 1, 3, 3]]
return polygon
def polygon_to_mask(polygon: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
"""Generate a mask from a polygon.
@@ -302,6 +321,43 @@ def xywh_to_xyxy(xywh: np.ndarray) -> np.ndarray:
return xyxy
def xyxy_to_xywh(xyxy: np.ndarray) -> np.ndarray:
"""
Converts bounding box coordinates from `(x_min, y_min, x_max, y_max)`
format to `(x, y, width, height)` format.
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)`.
Returns:
np.ndarray: A numpy array of shape `(N, 4)` where each row corresponds
to a bounding box in the format `(x, y, width, height)`.
Examples:
```python
import numpy as np
import supervision as sv
xyxy = np.array([
[10, 20, 40, 60],
[15, 25, 50, 70]
])
sv.xyxy_to_xywh(xyxy=xyxy)
# array([
# [10, 20, 30, 40],
# [15, 25, 35, 45]
# ])
```
"""
xywh = xyxy.copy()
xywh[:, 2] = xyxy[:, 2] - xyxy[:, 0]
xywh[:, 3] = xyxy[:, 3] - xyxy[:, 1]
return xywh
def xcycwh_to_xyxy(xcycwh: np.ndarray) -> np.ndarray:
"""
Converts bounding box coordinates from `(center_x, center_y, width, height)`
@@ -341,6 +397,56 @@ def xcycwh_to_xyxy(xcycwh: np.ndarray) -> np.ndarray:
return xyxy
def xyxy_to_xcycarh(xyxy: np.ndarray) -> np.ndarray:
"""
Converts bounding box coordinates from `(x_min, y_min, x_max, y_max)`
into measurement space to format `(center x, center y, aspect ratio, height)`,
where the aspect ratio is `width / height`.
Args:
xyxy (np.ndarray): Bounding box in format `(x1, y1, x2, y2)`.
Expected shape is `(N, 4)`.
Returns:
np.ndarray: Bounding box in format
`(center x, center y, aspect ratio, height)`. Shape `(N, 4)`.
Examples:
```python
import numpy as np
import supervision as sv
xyxy = np.array([
[10, 20, 40, 60],
[15, 25, 50, 70]
])
sv.xyxy_to_xcycarh(xyxy=xyxy)
# array([
# [25. , 40. , 0.75, 40. ],
# [32.5 , 47.5 , 0.77777778, 45. ]
# ])
```
"""
if xyxy.size == 0:
return np.empty((0, 4), dtype=float)
x1, y1, x2, y2 = xyxy.T
width = x2 - x1
height = y2 - y1
center_x = x1 + width / 2
center_y = y1 + height / 2
aspect_ratio = np.divide(
width,
height,
out=np.zeros_like(width, dtype=float),
where=height != 0,
)
result = np.column_stack((center_x, center_y, aspect_ratio, height))
return result.astype(float)
def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
"""
Converts a 3D `np.array` of 2D bool masks into a 2D `np.array` of bounding boxes.
@@ -701,25 +807,71 @@ def move_masks(
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]`.
offset (npt.NDArray[np.int32]): An array of shape `(2,)` containing int values
`[dx, dy]`. Supports both positive and negative values for bidirectional
movement.
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.
Examples:
```python
import numpy as np
import supervision as sv
mask = np.array([[[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False]]], dtype=bool)
offset = np.array([1, 1])
sv.move_masks(mask, offset, resolution_wh=(4, 4))
# array([[[False, False, False, False],
# [False, False, False, False],
# [False, False, True, True],
# [False, False, True, True]]], dtype=bool)
offset = np.array([-2, 2])
sv.move_masks(mask, offset, resolution_wh=(4, 4))
# array([[[False, False, False, False],
# [False, False, False, False],
# [False, False, False, False],
# [True, False, False, False]]], dtype=bool)
```
"""
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
if offset[0] < 0:
source_x_start = -offset[0]
source_x_end = min(masks.shape[2], resolution_wh[0] - offset[0])
destination_x_start = 0
destination_x_end = min(resolution_wh[0], masks.shape[2] + offset[0])
else:
source_x_start = 0
source_x_end = min(masks.shape[2], resolution_wh[0] - offset[0])
destination_x_start = offset[0]
destination_x_end = offset[0] + source_x_end - source_x_start
if offset[1] < 0:
source_y_start = -offset[1]
source_y_end = min(masks.shape[1], resolution_wh[1] - offset[1])
destination_y_start = 0
destination_y_end = min(resolution_wh[1], masks.shape[1] + offset[1])
else:
source_y_start = 0
source_y_end = min(masks.shape[1], resolution_wh[1] - offset[1])
destination_y_start = offset[1]
destination_y_end = offset[1] + source_y_end - source_y_start
if source_x_end > source_x_start and source_y_end > source_y_start:
mask_array[
:,
destination_y_start:destination_y_end,
destination_x_start:destination_x_end,
] = masks[:, source_y_start:source_y_end, source_x_start:source_x_end]
return mask_array
+317
View File
@@ -0,0 +1,317 @@
import json
import re
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union
import numpy as np
from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy
from supervision.utils.internal import deprecated
@deprecated(
"`LMM` enum is deprecated and will be removed in "
"`supervision-0.31.0`. Use VLM instead."
)
class LMM(Enum):
PALIGEMMA = "paligemma"
FLORENCE_2 = "florence_2"
QWEN_2_5_VL = "qwen_2_5_vl"
class VLM(Enum):
PALIGEMMA = "paligemma"
FLORENCE_2 = "florence_2"
QWEN_2_5_VL = "qwen_2_5_vl"
RESULT_TYPES: Dict[VLM, type] = {
VLM.PALIGEMMA: str,
VLM.FLORENCE_2: dict,
VLM.QWEN_2_5_VL: str,
}
REQUIRED_ARGUMENTS: Dict[VLM, List[str]] = {
VLM.PALIGEMMA: ["resolution_wh"],
VLM.FLORENCE_2: ["resolution_wh"],
VLM.QWEN_2_5_VL: ["input_wh", "resolution_wh"],
}
ALLOWED_ARGUMENTS: Dict[VLM, List[str]] = {
VLM.PALIGEMMA: ["resolution_wh", "classes"],
VLM.FLORENCE_2: ["resolution_wh"],
VLM.QWEN_2_5_VL: ["input_wh", "resolution_wh", "classes"],
}
SUPPORTED_TASKS_FLORENCE_2 = [
"<OD>",
"<CAPTION_TO_PHRASE_GROUNDING>",
"<DENSE_REGION_CAPTION>",
"<REGION_PROPOSAL>",
"<OCR_WITH_REGION>",
"<REFERRING_EXPRESSION_SEGMENTATION>",
"<REGION_TO_SEGMENTATION>",
"<OPEN_VOCABULARY_DETECTION>",
"<REGION_TO_CATEGORY>",
"<REGION_TO_DESCRIPTION>",
]
def validate_vlm_parameters(
vlm: Union[VLM, str], result: Any, kwargs: Dict[str, Any]
) -> VLM:
if isinstance(vlm, str):
try:
vlm = VLM(vlm.lower())
except ValueError:
raise ValueError(
f"Invalid vlm value: {vlm}. Must be one of {[e.value for e in VLM]}"
)
if not isinstance(result, RESULT_TYPES[vlm]):
raise ValueError(
f"Invalid VLM result type: {type(result)}. Must be {RESULT_TYPES[vlm]}"
)
required_args = REQUIRED_ARGUMENTS.get(vlm, [])
for arg in required_args:
if arg not in kwargs:
raise ValueError(f"Missing required argument: {arg}")
allowed_args = ALLOWED_ARGUMENTS.get(vlm, [])
for arg in kwargs:
if arg not in allowed_args:
raise ValueError(f"Argument {arg} is not allowed for {vlm.name}")
return vlm
def from_paligemma(
result: str, resolution_wh: Tuple[int, int], classes: Optional[List[str]] = None
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
"""
Parse bounding boxes from paligemma-formatted text, scale them to the specified
resolution, and optionally filter by classes.
Args:
result: String containing paligemma-formatted locations and labels.
resolution_wh: Tuple (width, height) to which we scale the box coordinates.
classes: Optional list of valid class names. If provided, boxes and labels not
in this list are filtered out.
Returns:
xyxy (np.ndarray): An array of shape `(n, 4)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`.
class_id (Optional[np.ndarray]): An array of shape `(n,)` containing
the class indices for each bounding box (or `None` if classes is not
provided).
class_name (np.ndarray): An array of shape `(n,)` containing
the class labels for each bounding box.
"""
w, h = resolution_wh
if w <= 0 or h <= 0:
raise ValueError(
f"Both dimensions in resolution_wh must be positive. Got ({w}, {h})."
)
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))
if matches.shape[0] == 0:
return np.empty((0, 4)), None, np.empty(0, dtype=str)
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], dtype=bool)
xyxy = xyxy[mask]
class_name = class_name[mask]
class_id = np.array([classes.index(name) for name in class_name])
return xyxy, class_id, class_name
def from_qwen_2_5_vl(
result: str,
input_wh: Tuple[int, int],
resolution_wh: Tuple[int, int],
classes: Optional[List[str]] = None,
) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
"""
Parse and scale bounding boxes from Qwen-2.5-VL style JSON output.
The JSON is expected to be enclosed in triple backticks with the format:
```json
[
{"bbox_2d": [x1, y1, x2, y2], "label": "some class name"},
...
]
```
Args:
result: String containing the JSON snippet enclosed by triple backticks.
input_wh: (input_width, input_height) describing the original bounding box
scale.
resolution_wh: (output_width, output_height) to which we rescale the boxes.
classes: Optional list of valid class names. If provided, returned boxes/labels
are filtered to only those classes found here.
Returns:
xyxy (np.ndarray): An array of shape `(n, 4)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
class_id (Optional[np.ndarray]): An array of shape `(n,)` containing
the class indices for each bounding box (or None if `classes` is not
provided)
class_name (np.ndarray): An array of shape `(n,)` containing
the class labels for each bounding box
"""
in_w, in_h = input_wh
out_w, out_h = resolution_wh
if in_w <= 0 or in_h <= 0 or out_w <= 0 or out_h <= 0:
raise ValueError(
f"Both input and resolution dimensions must be positive. "
f"Got input_wh=({in_w}, {in_h}), resolution_wh=({out_w}, {out_h})."
)
pattern = re.compile(r"```json\s*(.*?)\s*```", re.DOTALL)
match = pattern.search(result)
if not match:
return np.empty((0, 4)), None, np.empty((0,), dtype=str)
json_snippet = match.group(1)
try:
data = json.loads(json_snippet)
except json.JSONDecodeError:
return np.empty((0, 4)), None, np.empty((0,), dtype=str)
boxes_list = []
labels_list = []
for item in data:
if "bbox_2d" not in item or "label" not in item:
continue
boxes_list.append(item["bbox_2d"])
labels_list.append(item["label"])
if not boxes_list:
return np.empty((0, 4)), None, np.empty((0,), dtype=str)
xyxy = np.array(boxes_list, dtype=float)
class_name = np.array(labels_list, dtype=str)
xyxy = xyxy / [in_w, in_h, in_w, in_h]
xyxy = xyxy * [out_w, out_h, out_w, out_h]
class_id = None
if classes is not None:
mask = np.array([label in classes for label in class_name], dtype=bool)
xyxy = xyxy[mask]
class_name = class_name[mask]
class_id = np.array([classes.index(label) for label in class_name], dtype=int)
return xyxy, class_id, class_name
def from_florence_2(
result: dict, resolution_wh: Tuple[int, int]
) -> Tuple[
np.ndarray, Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray]
]:
"""
Parse results from the Florence 2 multi-model model.
https://huggingface.co/microsoft/Florence-2-large
Parameters:
result: dict containing the model output
Returns:
xyxy (np.ndarray): An array of shape `(n, 4)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
labels: (Optional[np.ndarray]): An array of shape `(n,)` containing
the class labels for each bounding box
masks: (Optional[np.ndarray]): An array of shape `(n, h, w)` containing
the segmentation masks for each bounding box
obb_boxes: (Optional[np.ndarray]): An array of shape `(n, 4, 2)` containing
oriented bounding boxes.
"""
assert len(result) == 1, f"Expected result with a single element. Got: {result}"
task = next(iter(result.keys()))
if task not in SUPPORTED_TASKS_FLORENCE_2:
raise ValueError(
f"{task} not supported. Supported tasks are: {SUPPORTED_TASKS_FLORENCE_2}"
)
result = result[task]
if task in ["<OD>", "<CAPTION_TO_PHRASE_GROUNDING>", "<DENSE_REGION_CAPTION>"]:
xyxy = np.array(result["bboxes"], dtype=np.float32)
labels = np.array(result["labels"])
return xyxy, labels, None, None
if task == "<REGION_PROPOSAL>":
xyxy = np.array(result["bboxes"], dtype=np.float32)
# provides labels, but they are ["", "", "", ...]
return xyxy, None, None, None
if task == "<OCR_WITH_REGION>":
xyxyxyxy = np.array(result["quad_boxes"], dtype=np.float32)
xyxyxyxy = xyxyxyxy.reshape(-1, 4, 2)
xyxy = np.array([polygon_to_xyxy(polygon) for polygon in xyxyxyxy])
labels = np.array(result["labels"])
return xyxy, labels, None, xyxyxyxy
if task in ["<REFERRING_EXPRESSION_SEGMENTATION>", "<REGION_TO_SEGMENTATION>"]:
xyxy_list = []
masks_list = []
for polygons_of_same_class in result["polygons"]:
for polygon in polygons_of_same_class:
polygon = np.reshape(polygon, (-1, 2)).astype(np.int32)
mask = polygon_to_mask(polygon, resolution_wh).astype(bool)
masks_list.append(mask)
xyxy = polygon_to_xyxy(polygon)
xyxy_list.append(xyxy)
# per-class labels also provided, but they are ["", "", "", ...]
# when we figure out how to set class names, we can do
# zip(result["labels"], result["polygons"])
xyxy = np.array(xyxy_list, dtype=np.float32)
masks = np.array(masks_list)
return xyxy, None, masks, None
if task == "<OPEN_VOCABULARY_DETECTION>":
xyxy = np.array(result["bboxes"], dtype=np.float32)
labels = np.array(result["bboxes_labels"])
# Also has "polygons" and "polygons_labels", but they don't seem to be used
return xyxy, labels, None, None
if task in ["<REGION_TO_CATEGORY>", "<REGION_TO_DESCRIPTION>"]:
assert isinstance(result, str), (
f"Expected string as <REGION_TO_CATEGORY> result, got {type(result)}"
)
if result == "No object detected.":
return np.empty((0, 4), dtype=np.float32), np.array([]), None, None
pattern = re.compile(r"<loc_(\d+)><loc_(\d+)><loc_(\d+)><loc_(\d+)>")
match = pattern.search(result)
assert match is not None, (
f"Expected string to end in location tags, but got {result}"
)
w, h = resolution_wh
xyxy = np.array([match.groups()], dtype=np.float32)
xyxy *= np.array([w, h, w, h]) / 1000
result_string = result[: match.start()]
labels = np.array([result_string])
return xyxy, labels, None, None
assert False, f"Unimplemented task: {task}"
+14 -9
View File
@@ -83,15 +83,16 @@ class Color:
# Color(r=255, g=255, b=255)
```
| Constant | Hex Code | RGB |
|------------|------------|------------------|
| `WHITE` | `#FFFFFF` | `(255, 255, 255)`|
| `BLACK` | `#000000` | `(0, 0, 0)` |
| `RED` | `#FF0000` | `(255, 0, 0)` |
| `GREEN` | `#00FF00` | `(0, 255, 0)` |
| `BLUE` | `#0000FF` | `(0, 0, 255)` |
| `YELLOW` | `#FFFF00` | `(255, 255, 0)` |
| `ROBOFLOW` | `#A351FB` | `(163, 81, 251)` |
| Constant | Hex Code | RGB |
|------------|------------|-------------------|
| `WHITE` | `#FFFFFF` | `(255, 255, 255)` |
| `BLACK` | `#000000` | `(0, 0, 0)` |
| `GREY` | `#808080` | `(128, 128, 128)` |
| `RED` | `#FF0000` | `(255, 0, 0)` |
| `GREEN` | `#00FF00` | `(0, 255, 0)` |
| `BLUE` | `#0000FF` | `(0, 0, 255)` |
| `YELLOW` | `#FFFF00` | `(255, 255, 0)` |
| `ROBOFLOW` | `#A351FB` | `(163, 81, 251)` |
"""
r: int
@@ -235,6 +236,10 @@ class Color:
def BLACK(cls) -> Color:
return Color.from_hex("#000000")
@classproperty
def GREY(cls) -> Color:
return Color.from_hex("#808080")
@classproperty
def RED(cls) -> Color:
return Color.from_hex("#FF0000")
+97 -10
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
from contextlib import suppress
from dataclasses import dataclass, field
from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
@@ -24,8 +23,8 @@ class KeyPoints:
=== "Ultralytics"
Use [`sv.KeyPoints.from_ultralytics`](/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics)
method, which accepts [YOLOv8](https://github.com/ultralytics/ultralytics)
pose result.
method, which accepts [YOLOv8-pose](https://docs.ultralytics.com/models/yolov8/), [YOLO11-pose](https://docs.ultralytics.com/models/yolo11/)
[pose](https://docs.ultralytics.com/tasks/pose/) result.
```python
import cv2
@@ -33,7 +32,7 @@ class KeyPoints:
from ultralytics import YOLO
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = YOLO('yolov8s-pose.pt')
model = YOLO('yolo11s-pose.pt')
result = model(image)[0]
key_points = sv.KeyPoints.from_ultralytics(result)
@@ -62,6 +61,7 @@ class KeyPoints:
method, which accepts [MediaPipe](https://github.com/google-ai-edge/mediapipe)
pose result.
```python
import cv2
import mediapipe as mp
@@ -92,10 +92,10 @@ class KeyPoints:
xy (np.ndarray): An array of shape `(n, m, 2)` containing
`n` detected objects, each composed of `m` equally-sized
sets of keypoints, where each point is `[x, y]`.
confidence (Optional[np.ndarray]): An array of shape
`(n, m)` containing the confidence scores of each keypoint.
class_id (Optional[np.ndarray]): An array of shape
`(n,)` containing the class ids of the detected objects.
confidence (Optional[np.ndarray]): An array of shape
`(n, m)` containing the confidence scores of each keypoint.
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 of length `n`
@@ -204,9 +204,10 @@ class KeyPoints:
"You can retrieve it like so: inference_result = model.infer(image)[0]"
)
with suppress(AttributeError):
if hasattr(inference_result, "dict"):
inference_result = inference_result.dict(exclude_none=True, by_alias=True)
elif hasattr(inference_result, "json"):
inference_result = inference_result.json()
if not inference_result.get("predictions"):
return cls.empty()
@@ -314,6 +315,7 @@ class KeyPoints:
key_points = sv.KeyPoints.from_mediapipe(
face_landmarker_result, (image_width, image_height))
```
""" # noqa: E501 // docs
if hasattr(mediapipe_results, "pose_landmarks"):
results = mediapipe_results.pose_landmarks
@@ -473,7 +475,7 @@ class KeyPoints:
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
and class names, and confidences of each keypoint.
Example:
Examples:
```python
import cv2
import supervision as sv
@@ -510,6 +512,91 @@ class KeyPoints:
else:
return cls.empty()
@classmethod
def from_transformers(cls, transfomers_results: Any) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the
[Transformers](https://github.com/huggingface/transformers) inference result.
Args:
transfomers_results (Any): The output of a
Transformers model containing instances with prediction data.
Returns:
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
and class names, and confidences of each keypoint.
Examples:
```python
from PIL import Image
import requests
import supervision as sv
import torch
from transformers import (
AutoProcessor,
RTDetrForObjectDetection,
VitPoseForPoseEstimation,
)
device = "cuda" if torch.cuda.is_available() else "cpu"
image = Image.open(<SOURCE_IMAGE_PATH>)
DETECTION_MODEL_ID = "PekingU/rtdetr_r50vd_coco_o365"
detection_processor = AutoProcessor.from_pretrained(DETECTION_MODEL_ID, use_fast=True)
detection_model = RTDetrForObjectDetection.from_pretrained(DETECTION_MODEL_ID, device_map=DEVICE)
inputs = detection_processor(images=frame, return_tensors="pt").to(DEVICE)
with torch.no_grad():
outputs = detection_model(**inputs)
target_size = torch.tensor([(frame.height, frame.width)])
results = detection_processor.post_process_object_detection(
outputs, target_sizes=target_size, threshold=0.3)
detections = sv.Detections.from_transformers(results[0])
boxes = sv.xyxy_to_xywh(detections[detections.class_id == 0].xyxy)
POSE_ESTIMATION_MODEL_ID = "usyd-community/vitpose-base-simple"
pose_estimation_processor = AutoProcessor.from_pretrained(POSE_ESTIMATION_MODEL_ID)
pose_estimation_model = VitPoseForPoseEstimation.from_pretrained(
POSE_ESTIMATION_MODEL_ID, device_map=DEVICE)
inputs = pose_estimation_processor(frame, boxes=[boxes], return_tensors="pt").to(DEVICE)
with torch.no_grad():
outputs = pose_estimation_model(**inputs)
results = pose_estimation_processor.post_process_pose_estimation(outputs, boxes=[boxes])
key_point = sv.KeyPoints.from_transformers(results[0])
```
""" # noqa: E501 // docs
if "keypoints" in transfomers_results[0]:
if transfomers_results[0]["keypoints"].cpu().numpy().size == 0:
return cls.empty()
result_data = [
(
result["keypoints"].cpu().numpy(),
result["scores"].cpu().numpy(),
)
for result in transfomers_results
]
xy, scores = zip(*result_data)
return cls(
xy=np.stack(xy).astype(np.float32),
confidence=np.stack(scores).astype(np.float32),
class_id=np.arange(len(xy)).astype(int),
)
else:
return cls.empty()
def __getitem__(
self, index: Union[int, slice, List[int], np.ndarray, str]
) -> Union[KeyPoints, List, np.ndarray, None]:
@@ -639,7 +726,7 @@ class KeyPoints:
Returns:
detections (Detections): The converted detections object.
Example:
Examples:
```python
keypoints = sv.KeyPoints.from_inference(...)
detections = keypoints.as_detections()
+19
View File
@@ -19,3 +19,22 @@ from supervision.metrics.utils.object_size import (
get_detection_size_category,
get_object_size_category,
)
__all__ = [
"AveragingMethod",
"F1Score",
"F1ScoreResult",
"MeanAveragePrecision",
"MeanAveragePrecisionResult",
"MeanAverageRecall",
"MeanAverageRecallResult",
"Metric",
"MetricTarget",
"ObjectSizeCategory",
"Precision",
"PrecisionResult",
"Recall",
"RecallResult",
"get_detection_size_category",
"get_object_size_category",
]
+1 -2
View File
@@ -670,8 +670,7 @@ class MeanAverageRecallResult:
ax.set_ylim(0, 1)
ax.set_ylabel("Value", fontweight="bold")
title = (
f"Mean Average Recall, by Object Size"
f"\n(target: {self.metric_target.value})"
f"Mean Average Recall, by Object Size\n(target: {self.metric_target.value})"
)
ax.set_title(title, fontweight="bold")
+2 -1
View File
@@ -5,5 +5,6 @@ def ensure_pandas_installed():
raise ImportError(
"`metrics` extra is required to run the function."
" Run `pip install 'supervision[metrics]'` or"
" `poetry add supervision -E metrics`"
" `poetry add supervision -E metrics` or"
" `uv pip install 'supervision[metrics]'`"
)
+25 -1
View File
@@ -7,6 +7,7 @@ from typing import Callable, Generator, Optional, Tuple
import cv2
import numpy as np
from tqdm.auto import tqdm
@dataclass
@@ -195,6 +196,9 @@ def process_video(
source_path: str,
target_path: str,
callback: Callable[[np.ndarray, int], np.ndarray],
max_frames: Optional[int] = None,
show_progress: bool = False,
progress_message: str = "Processing video",
) -> None:
"""
Process a video file by applying a callback function on each frame
@@ -207,6 +211,9 @@ def process_video(
a numpy ndarray representation of a video frame and an
int index of the frame and returns a processed numpy ndarray
representation of the frame.
max_frames (Optional[int]): The maximum number of frames to process.
show_progress (bool): Whether to show a progress bar.
progress_message (str): The message to display in the progress bar.
Examples:
```python
@@ -223,12 +230,29 @@ def process_video(
```
"""
source_video_info = VideoInfo.from_video_path(video_path=source_path)
video_frames_generator = get_video_frames_generator(
source_path=source_path, end=max_frames
)
with VideoSink(target_path=target_path, video_info=source_video_info) as sink:
total_frames = (
min(source_video_info.total_frames, max_frames)
if max_frames is not None
else source_video_info.total_frames
)
for index, frame in enumerate(
get_video_frames_generator(source_path=source_path)
tqdm(
video_frames_generator,
total=total_frames,
disable=not show_progress,
desc=progress_message,
)
):
result_frame = callback(frame, index)
sink.write_frame(frame=result_frame)
else:
for index, frame in enumerate(video_frames_generator):
result_frame = callback(frame, index)
sink.write_frame(frame=result_frame)
class FPSMonitor:
+1 -1
View File
@@ -54,7 +54,7 @@ def validate_confidence(confidence: Any, n: int) -> None:
def validate_keypoint_confidence(confidence: Any, n: int, m: int) -> None:
expected_shape = f"({n,m})"
expected_shape = f"({n, m})"
actual_shape = str(getattr(confidence, "shape", None))
if confidence is not None:
+6
View File
@@ -223,6 +223,12 @@ TEST_DET_DIFFERENT_METADATA = Detections(
None,
pytest.raises(IndexError),
),
(
Detections.empty(),
np.isin(Detections.empty()["class_name"], ["cat", "dog"]),
Detections.empty(),
DoesNotRaise(),
), # Filter an empty detections by specific class names
],
)
def test_getitem(
+3 -3
View File
@@ -408,8 +408,8 @@ def assert_csv_equal(file_name, expected_rows):
with open(file_name, mode="r", newline="") as file:
reader = csv.reader(file)
for i, row in enumerate(reader):
assert (
[str(item) for item in expected_rows[i]] == row
), f"Row in CSV didn't match expected output: {row} != {expected_rows[i]}"
assert [str(item) for item in expected_rows[i]] == row, (
f"Row in CSV didn't match expected output: {row} != {expected_rows[i]}"
)
os.remove(file_name)
+3 -3
View File
@@ -242,8 +242,8 @@ def test_json_sink(
def assert_json_equal(file_name, expected_rows):
with open(file_name, "r") as file:
data = json.load(file)
assert (
data == expected_rows
), f"Data in JSON file didn't match expected output: {data} != {expected_rows}"
assert data == expected_rows, (
f"Data in JSON file didn't match expected output: {data} != {expected_rows}"
)
os.remove(file_name)
+18 -18
View File
@@ -250,12 +250,12 @@ def test_line_zone_one_detection_default_anchors(
crossed_in_list.append(crossed_in[0])
crossed_out_list.append(crossed_out[0])
assert (
crossed_in_list == expected_crossed_in
), f"expected {expected_crossed_in}, got {crossed_in_list}"
assert (
crossed_out_list == expected_crossed_out
), f"expected {expected_crossed_out}, got {crossed_out_list}"
assert crossed_in_list == expected_crossed_in, (
f"expected {expected_crossed_in}, got {crossed_in_list}"
)
assert crossed_out_list == expected_crossed_out, (
f"expected {expected_crossed_out}, got {crossed_out_list}"
)
@pytest.mark.parametrize(
@@ -415,12 +415,12 @@ def test_line_zone_one_detection(
crossed_in_list.append(crossed_in[0])
crossed_out_list.append(crossed_out[0])
assert (
crossed_in_list == expected_crossed_in
), f"expected {expected_crossed_in}, got {crossed_in_list}"
assert (
crossed_out_list == expected_crossed_out
), f"expected {expected_crossed_out}, got {crossed_out_list}"
assert crossed_in_list == expected_crossed_in, (
f"expected {expected_crossed_in}, got {crossed_in_list}"
)
assert crossed_out_list == expected_crossed_out, (
f"expected {expected_crossed_out}, got {crossed_out_list}"
)
@pytest.mark.parametrize(
@@ -600,12 +600,12 @@ def test_line_zone_one_detection_long_horizon(
crossed_in_list.append(crossed_in[0])
crossed_out_list.append(crossed_out[0])
assert (
crossed_in_list == expected_crossed_in
), f"expected {expected_crossed_in}, got {crossed_in_list}"
assert (
crossed_out_list == expected_crossed_out
), f"expected {expected_crossed_out}, got {crossed_out_list}"
assert crossed_in_list == expected_crossed_in, (
f"expected {expected_crossed_in}, got {crossed_in_list}"
)
assert crossed_out_list == expected_crossed_out, (
f"expected {expected_crossed_out}, got {crossed_out_list}"
)
@pytest.mark.parametrize(
-151
View File
@@ -1,151 +0,0 @@
from typing import List, Optional, Tuple
import numpy as np
import pytest
from supervision.detection.lmm import from_paligemma
@pytest.mark.parametrize(
"result, resolution_wh, classes, expected_results",
[
(
"",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # empty response
(
"",
(1000, 1000),
["cat", "dog"],
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # empty response with classes
(
"\n",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # new line response
(
"the quick brown fox jumps over the lazy dog.",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # response with no location
(
"<loc0256><loc0768><loc0768> cat",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # response with missing location
(
"<loc0256><loc0256><loc0768><loc0768><loc0768> cat",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # response with extra location
(
"<loc0256><loc0256><loc0768><loc0768>",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # response with no class
(
"<loc0256><loc0256><loc0768><loc0768> catt",
(1000, 1000),
["cat", "dog"],
(np.empty((0, 4)), np.empty(0), np.empty(0).astype(str)),
), # response with invalid class
(
"<loc0256><loc0256><loc0768><loc0768> cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["cat"]).astype(str),
),
), # correct response; no classes
(
"<loc0256><loc0256><loc0768><loc0768> black cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["black cat"]).astype(np.dtype("U")),
),
), # correct response; class name with space; no classes
(
"<loc0256><loc0256><loc0768><loc0768> black-cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["black-cat"]).astype(np.dtype("U")),
),
), # correct response; class name with hyphen; no classes
(
"<loc0256><loc0256><loc0768><loc0768> black_cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["black_cat"]).astype(np.dtype("U")),
),
), # correct response; class name with underscore; no classes
(
"<loc0256><loc0256><loc0768><loc0768> cat ;",
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
np.array([0]),
np.array(["cat"]).astype(str),
),
), # correct response; with classes
(
"<loc0256><loc0256><loc0768><loc0768> cat ; <loc0256><loc0256><loc0768><loc0768> dog", # noqa: E501
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0], [250.0, 250.0, 750.0, 750.0]]),
np.array([0, 1]),
np.array(["cat", "dog"]).astype(np.dtype("U")),
),
), # correct response; with classes
(
"<loc0256><loc0256><loc0768><loc0768> cat ; <loc0256><loc0256><loc0768> cat", # noqa: E501
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
np.array([0]),
np.array(["cat"]).astype(str),
),
), # partially correct response; with classes
(
"<loc0256><loc0256><loc0768><loc0768> cat ; <loc0256><loc0256><loc0768><loc0768><loc0768> cat", # noqa: E501
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
np.array([0]),
np.array(["cat"]).astype(str),
),
), # partially correct response; with classes
],
)
def test_from_paligemma(
result: str,
resolution_wh: Tuple[int, int],
classes: Optional[List[str]],
expected_results: Tuple[np.ndarray, Optional[np.ndarray], np.ndarray],
) -> None:
result = from_paligemma(result=result, resolution_wh=resolution_wh, classes=classes)
np.testing.assert_array_equal(result[0], expected_results[0])
np.testing.assert_array_equal(result[1], expected_results[1])
np.testing.assert_array_equal(result[2], expected_results[2])
+357 -18
View File
@@ -16,10 +16,13 @@ from supervision.detection.utils import (
merge_data,
merge_metadata,
move_boxes,
move_masks,
process_roboflow_result,
scale_boxes,
xcycwh_to_xyxy,
xywh_to_xyxy,
xyxy_to_xcycarh,
xyxy_to_xywh,
)
TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool)
@@ -387,13 +390,13 @@ def test_process_roboflow_result(
)
for key in result[5]:
if isinstance(result[5][key], np.ndarray):
assert np.array_equal(
result[5][key], expected_result[5][key]
), f"Mismatch in arrays for key {key}"
assert np.array_equal(result[5][key], expected_result[5][key]), (
f"Mismatch in arrays for key {key}"
)
else:
assert (
result[5][key] == expected_result[5][key]
), f"Mismatch in non-array data for key {key}"
assert result[5][key] == expected_result[5][key], (
f"Mismatch in non-array data for key {key}"
)
@pytest.mark.parametrize(
@@ -442,6 +445,268 @@ def test_move_boxes(
assert np.array_equal(result, expected_result)
@pytest.mark.parametrize(
"masks, offset, resolution_wh, expected_result, exception",
[
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([0, 0]),
(4, 4),
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([-1, -1]),
(4, 4),
np.array(
[
[
[True, True, False, False],
[True, True, False, False],
[False, False, False, False],
[False, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([-2, -2]),
(4, 4),
np.array(
[
[
[True, False, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([-3, -3]),
(4, 4),
np.array(
[
[
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([-2, -1]),
(4, 4),
np.array(
[
[
[True, False, False, False],
[True, False, False, False],
[False, False, False, False],
[False, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([-1, -2]),
(4, 4),
np.array(
[
[
[True, True, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([-2, 2]),
(4, 4),
np.array(
[
[
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
[True, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([3, 3]),
(4, 4),
np.array(
[
[
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, False, False],
]
],
dtype=bool,
),
DoesNotRaise(),
),
(
np.array(
[
[
[False, False, False, False],
[False, True, True, False],
[False, True, True, False],
[False, False, False, False],
]
],
dtype=bool,
),
np.array([3, 3]),
(6, 6),
np.array(
[
[
[False, False, False, False, False, False],
[False, False, False, False, False, False],
[False, False, False, False, False, False],
[False, False, False, False, False, False],
[False, False, False, False, True, True],
[False, False, False, False, True, True],
]
],
dtype=bool,
),
DoesNotRaise(),
),
],
)
def test_move_masks(
masks: np.ndarray,
offset: np.ndarray,
resolution_wh: Tuple[int, int],
expected_result: np.ndarray,
exception: Exception,
) -> None:
with exception:
result = move_masks(masks=masks, offset=offset, resolution_wh=resolution_wh)
np.testing.assert_array_equal(result, expected_result)
@pytest.mark.parametrize(
"xyxy, factor, expected_result, exception",
[
@@ -778,13 +1043,13 @@ def test_merge_data(
for key in result:
if isinstance(result[key], np.ndarray):
assert np.array_equal(
result[key], expected_result[key]
), f"Mismatch in arrays for key {key}"
assert np.array_equal(result[key], expected_result[key]), (
f"Mismatch in arrays for key {key}"
)
else:
assert (
result[key] == expected_result[key]
), f"Mismatch in non-array data for key {key}"
assert result[key] == expected_result[key], (
f"Mismatch in non-array data for key {key}"
)
@pytest.mark.parametrize(
@@ -951,13 +1216,13 @@ def test_get_data_item(
result = get_data_item(data=data, index=index)
for key in result:
if isinstance(result[key], np.ndarray):
assert np.array_equal(
result[key], expected_result[key]
), f"Mismatch in arrays for key {key}"
assert np.array_equal(result[key], expected_result[key]), (
f"Mismatch in arrays for key {key}"
)
else:
assert (
result[key] == expected_result[key]
), f"Mismatch in non-array data for key {key}"
assert result[key] == expected_result[key], (
f"Mismatch in non-array data for key {key}"
)
@pytest.mark.parametrize(
@@ -1118,6 +1383,80 @@ def test_xywh_to_xyxy(xywh: np.ndarray, expected_result: np.ndarray) -> None:
np.testing.assert_array_equal(result, expected_result)
@pytest.mark.parametrize(
"xyxy, expected_result",
[
(np.array([[10, 20, 40, 60]]), np.array([[10, 20, 30, 40]])), # standard case
(np.array([[0, 0, 0, 0]]), np.array([[0, 0, 0, 0]])), # zero size bounding box
(
np.array([[50, 50, 150, 150]]),
np.array([[50, 50, 100, 100]]),
), # large bounding box
(
np.array([[-10, -20, 20, 20]]),
np.array([[-10, -20, 30, 40]]),
), # negative coordinates
(np.array([[50, 50, 50, 80]]), np.array([[50, 50, 0, 30]])), # zero width
(np.array([[50, 50, 70, 50]]), np.array([[50, 50, 20, 0]])), # zero height
(np.array([]).reshape(0, 4), np.array([]).reshape(0, 4)), # empty array
],
)
def test_xyxy_to_xywh(xyxy: np.ndarray, expected_result: np.ndarray) -> None:
result = xyxy_to_xywh(xyxy)
np.testing.assert_array_equal(result, expected_result)
@pytest.mark.parametrize(
"xyxy, expected_result",
[
# Empty and zero cases
(np.array([]).reshape(0, 4), np.array([]).reshape(0, 4)), # empty array
(
np.array([[0, 0, 0, 0]]),
np.array([[0, 0, 0.0, 0]]),
), # zero size bounding box
(
np.array([[10, 10, 10, 10]]),
np.array([[10, 10, 0.0, 0]]),
), # point (x1=x2, y1=y2)
# Zero width/height cases
(np.array([[50, 50, 80, 50]]), np.array([[65, 50, 0.0, 0]])), # zero height
(np.array([[50, 50, 50, 80]]), np.array([[50, 65, 0.0, 30]])), # zero width
# Standard cases
(np.array([[10, 20, 40, 60]]), np.array([[25, 40, 0.75, 40]])), # standard case
(
np.array([[-30, -40, -10, -20]]),
np.array([[-20, -30, 1.0, 20]]),
), # all negative values
(
np.array([[0.1, 0.2, 0.4, 0.6]]),
np.array([[0.25, 0.4, 0.75, 0.4]]),
), # values between 0-1
# Different aspect ratios
(
np.array([[10, 20, 50, 100]]),
np.array([[30, 60, 0.5, 80]]),
), # tall rectangle (height > width)
(
np.array([[20, 10, 100, 50]]),
np.array([[60, 30, 2.0, 40]]),
), # wide rectangle (width > height)
(
np.array([[50, 50, 150, 150]]),
np.array([[100, 100, 1.0, 100]]),
), # height == width
# Multiple boxes in one array
(
np.array([[0, 0, 0, 0], [10, 20, 40, 60]]),
np.array([[0, 0, 0.0, 0], [25, 40, 0.75, 40]]),
), # one zero-sized box and one normal box
],
)
def test_xyxy_to_xcycarh(xyxy: np.ndarray, expected_result: np.ndarray) -> None:
result = xyxy_to_xcycarh(xyxy)
np.testing.assert_allclose(result, expected_result)
@pytest.mark.parametrize(
"xcycwh, expected_result",
[
+355
View File
@@ -0,0 +1,355 @@
from contextlib import nullcontext as does_not_raise
from typing import List, Optional, Tuple
import numpy as np
import pytest
from supervision.detection.vlm import from_paligemma, from_qwen_2_5_vl
@pytest.mark.parametrize(
"exception, result, resolution_wh, classes, expected_results",
[
(
does_not_raise(),
"",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # empty text
(
does_not_raise(),
"",
(1000, 1000),
["cat", "dog"],
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # empty text, classes
(
does_not_raise(),
"\n",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # newline only
(
does_not_raise(),
"the quick brown fox jumps over the lazy dog.",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # random text, no location
(
does_not_raise(),
"<loc0256><loc0768><loc0768> cat",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # partial location
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768><loc0768> cat",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # extra loc
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768>",
(1000, 1000),
None,
(np.empty((0, 4)), None, np.empty(0).astype(str)),
), # no class
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> catt",
(1000, 1000),
["cat", "dog"],
(np.empty((0, 4)), np.empty(0), np.empty(0).astype(str)),
), # invalid class
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["cat"]).astype(str),
),
), # single box, no classes
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> black cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["black cat"]).astype(np.dtype("U")),
),
), # class with space
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> black-cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["black-cat"]).astype(np.dtype("U")),
),
), # class with hyphen
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> black_cat",
(1000, 1000),
None,
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
None,
np.array(["black_cat"]).astype(np.dtype("U")),
),
), # class with underscore
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> cat ;",
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
np.array([0]),
np.array(["cat"]).astype(str),
),
), # correct class filter
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> cat ; "
"<loc0256><loc0256><loc0768><loc0768> dog",
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0], [250.0, 250.0, 750.0, 750.0]]),
np.array([0, 1]),
np.array(["cat", "dog"]).astype(np.dtype("U")),
),
), # multiple correct boxes, classes
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> cat ; "
"<loc0256><loc0256><loc0768> cat",
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
np.array([0]),
np.array(["cat"]).astype(str),
),
), # partial valid boxes
(
does_not_raise(),
"<loc0256><loc0256><loc0768><loc0768> cat ; "
"<loc0256><loc0256><loc0768><loc0768><loc0768> cat",
(1000, 1000),
["cat", "dog"],
(
np.array([[250.0, 250.0, 750.0, 750.0]]),
np.array([0]),
np.array(["cat"]).astype(str),
),
), # partial valid again
(
pytest.raises(ValueError),
"<loc0256><loc0256><loc0768><loc0768> cat",
(0, 1000),
None,
None,
), # zero width -> ValueError
(
pytest.raises(ValueError),
"<loc0256><loc0256><loc0768><loc0768> dog",
(1000, -200),
None,
None,
), # negative height -> ValueError
],
)
def test_from_paligemma(
exception,
result: str,
resolution_wh: Tuple[int, int],
classes: Optional[List[str]],
expected_results: Tuple[np.ndarray, Optional[np.ndarray], np.ndarray],
) -> None:
with exception:
result = from_paligemma(
result=result, resolution_wh=resolution_wh, classes=classes
)
np.testing.assert_array_equal(result[0], expected_results[0])
np.testing.assert_array_equal(result[1], expected_results[1])
np.testing.assert_array_equal(result[2], expected_results[2])
@pytest.mark.parametrize(
"exception, result, input_wh, resolution_wh, classes, expected_results",
[
(
does_not_raise(),
"some random text without triple backticks",
(640, 640),
(1280, 720),
None,
(np.empty((0, 4)), None, np.empty(0, dtype=str)),
), # no snippet
(
does_not_raise(),
"```json\nnot valid json\n```",
(640, 640),
(1280, 720),
None,
(np.empty((0, 4)), None, np.empty(0, dtype=str)),
), # invalid JSON
(
does_not_raise(),
"```json\n[]\n```",
(640, 640),
(1280, 720),
None,
(np.empty((0, 4)), None, np.empty(0, dtype=str)),
), # empty list
(
does_not_raise(),
"""```json
[
{"bbox_2d": [10, 10, 100, 100]},
{"label": "missing box"},
{"bbox_2d": [50, 60, 110, 120], "unused": "something"}
]
```""",
(640, 640),
(1280, 720),
None,
(np.empty((0, 4)), None, np.empty(0, dtype=str)),
), # missing keys
(
does_not_raise(),
"""```json
[
{"bbox_2d": [10, 20, 110, 120], "label": "cat"}
]
```""",
(640, 640),
(1280, 720),
None,
(
np.array([[20.0, 22.5, 220.0, 135.0]]),
None,
np.array(["cat"], dtype=str),
),
), # single box no classes
(
does_not_raise(),
"""```json
[
{"bbox_2d": [0, 0, 64, 64], "label": "dog"},
{"bbox_2d": [100, 200, 300, 400], "label": "cat"}
]
```""",
(640, 640),
(640, 640),
None,
(
np.array([[0, 0, 64, 64], [100, 200, 300, 400]], dtype=float),
None,
np.array(["dog", "cat"], dtype=str),
),
), # multiple no classes
(
does_not_raise(),
"""```json
[
{"bbox_2d": [10, 20, 110, 120], "label": "bird"}
]
```""",
(640, 640),
(1280, 720),
["cat", "dog"],
(np.empty((0, 4)), np.empty(0, dtype=int), np.empty(0, dtype=str)),
), # class mismatch
(
does_not_raise(),
"""```json
[
{"bbox_2d": [10, 20, 110, 120], "label": "cat"},
{"bbox_2d": [50, 100, 150, 200], "label": "dog"}
]
```""",
(640, 640),
(640, 480),
["cat", "dog"],
(
np.array([[10.0, 15.0, 110.0, 90.0], [50.0, 75.0, 150.0, 150.0]]),
np.array([0, 1], dtype=int),
np.array(["cat", "dog"], dtype=str),
),
), # partial filtering
(
does_not_raise(),
"""```json
[
{"bbox_2d": [-10, 0, 700, 700], "label": "dog"}
]
```""",
(640, 640),
(1280, 720),
None,
(
np.array([[-20.0, 0.0, 1400.0, 787.5]]),
None,
np.array(["dog"], dtype=str),
),
), # out-of-bounds box
(
pytest.raises(ValueError),
"""```json
[
{"bbox_2d": [10, 20, 110, 120], "label": "cat"}
]
```""",
(0, 640),
(1280, 720),
None,
None, # won't be compared because we expect an exception
), # zero input width -> ValueError
(
pytest.raises(ValueError),
"""```json
[
{"bbox_2d": [10, 20, 110, 120], "label": "dog"}
]
```""",
(640, 640),
(1280, -100),
None,
None,
), # negative resolution height -> ValueError
],
)
def test_from_qwen_2_5_vl(
exception,
result: str,
input_wh: Tuple[int, int],
resolution_wh: Tuple[int, int],
classes: Optional[List[str]],
expected_results,
) -> None:
with exception:
xyxy, class_id, class_name = from_qwen_2_5_vl(
result=result,
input_wh=input_wh,
resolution_wh=resolution_wh,
classes=classes,
)
if expected_results is not None:
np.testing.assert_array_equal(xyxy, expected_results[0])
np.testing.assert_array_equal(class_id, expected_results[1])
np.testing.assert_array_equal(class_name, expected_results[2])
@@ -4,7 +4,7 @@ from typing import Optional, Tuple
import numpy as np
import pytest
from supervision.detection.lmm import from_florence_2
from supervision.detection.vlm import from_florence_2
@pytest.mark.parametrize(
+19 -26
View File
@@ -22,33 +22,26 @@ def mock_callback():
@pytest.mark.parametrize(
"slice_wh, overlap_ratio_wh, overlap_wh, expected_overlap, exception",
[
# Valid case: overlap_ratio_wh provided, overlap calculated from the ratio
((128, 128), (0.2, 0.2), None, None, DoesNotRaise()),
# Valid case: overlap_wh in pixels, no ratio provided
# Valid case: explicit overlap_wh in pixels
((128, 128), None, (26, 26), (26, 26), DoesNotRaise()),
# Valid case: overlap_wh in pixels
((128, 128), None, (20, 20), (20, 20), DoesNotRaise()),
# Invalid case: overlap_ratio_wh greater than 1, should raise ValueError
((128, 128), (1.1, 0.5), None, None, pytest.raises(ValueError)),
# Invalid case: negative overlap_wh, should raise ValueError
((128, 128), None, (-10, 20), None, pytest.raises(ValueError)),
# Invalid case:
# overlap_ratio_wh and overlap_wh provided, should raise ValueError
((128, 128), (0.5, 0.5), (20, 20), (20, 20), pytest.raises(ValueError)),
# Valid case: no overlap_ratio_wh, overlap_wh = 50 pixels
((256, 256), None, (50, 50), (50, 50), DoesNotRaise()),
# Valid case: overlap_ratio_wh provided, overlap calculated from (0.3, 0.3)
((200, 200), (0.3, 0.3), None, None, DoesNotRaise()),
# Valid case: small overlap_ratio_wh values
((100, 100), (0.1, 0.1), None, None, DoesNotRaise()),
# Invalid case: negative overlap_ratio_wh value, should raise ValueError
((128, 128), (-0.1, 0.2), None, None, pytest.raises(ValueError)),
# Invalid case: negative overlap_ratio_wh with overlap_wh provided
((128, 128), (-0.1, 0.2), (30, 30), None, pytest.raises(ValueError)),
# Invalid case: overlap_wh greater than slice size, should raise ValueError
((128, 128), None, (150, 150), (150, 150), DoesNotRaise()),
# Valid case: overlap_ratio_wh is 0, no overlap
((128, 128), (0.0, 0.0), None, None, DoesNotRaise()),
# Invalid case: no overlaps defined, no overlap
# Invalid case: no overlaps defined
((128, 128), None, None, None, pytest.raises(ValueError)),
# Valid case: overlap_wh = 50 pixels
((256, 256), None, (50, 50), (50, 50), DoesNotRaise()),
# Valid case: overlap_wh = 60 pixels
((200, 200), None, (60, 60), (60, 60), DoesNotRaise()),
# Valid case: small overlap_wh values
((100, 100), None, (0.1, 0.1), (0.1, 0.1), DoesNotRaise()),
# Invalid case: negative overlap_wh values
((128, 128), None, (-10, -10), None, pytest.raises(ValueError)),
# Invalid case: overlap_wh greater than slice size
((128, 128), None, (150, 150), (150, 150), DoesNotRaise()),
# Valid case: zero overlap
((128, 128), None, (0, 0), (0, 0), DoesNotRaise()),
],
)
def test_inference_slicer_overlap(
@@ -187,6 +180,6 @@ def test_generate_offset(
)
# Verify that the generated offsets match the expected offsets
assert np.array_equal(
offsets, expected_offsets
), f"Expected {expected_offsets}, got {offsets}"
assert np.array_equal(offsets, expected_offsets), (
f"Expected {expected_offsets}, got {offsets}"
)
+36 -36
View File
@@ -22,15 +22,15 @@ def test_ensure_cv2_image_for_processing_when_pillow_image_submitted(
param_a: int,
param_b: str,
) -> np.ndarray:
assert np.allclose(
image, empty_cv2_image
), "Expected conversion to OpenCV image to happen"
assert (
param_a == param_a_value
), f"Parameter a expected to be {param_a_value} in target function"
assert (
param_b == param_b_value
), f"Parameter b expected to be {param_b_value} in target function"
assert np.allclose(image, empty_cv2_image), (
"Expected conversion to OpenCV image to happen"
)
assert param_a == param_a_value, (
f"Parameter a expected to be {param_a_value} in target function"
)
assert param_b == param_b_value, (
f"Parameter b expected to be {param_b_value} in target function"
)
return image
# when
@@ -61,15 +61,15 @@ def test_ensure_cv2_image_for_processing_when_cv2_image_submitted(
param_a: int,
param_b: str,
) -> np.ndarray:
assert np.allclose(
image, empty_cv2_image
), "Expected conversion to OpenCV image to happen"
assert (
param_a == param_a_value
), f"Parameter a expected to be {param_a_value} in target function"
assert (
param_b == param_b_value
), f"Parameter b expected to be {param_b_value} in target function"
assert np.allclose(image, empty_cv2_image), (
"Expected conversion to OpenCV image to happen"
)
assert param_a == param_a_value, (
f"Parameter a expected to be {param_a_value} in target function"
)
assert param_b == param_b_value, (
f"Parameter b expected to be {param_b_value} in target function"
)
return image
# when
@@ -91,9 +91,9 @@ def test_cv2_to_pillow(
# then
difference = ImageChops.difference(result, empty_pillow_image)
assert (
difference.getbbox() is None
), "Conversion to PIL.Image expected not to change the content of image"
assert difference.getbbox() is None, (
"Conversion to PIL.Image expected not to change the content of image"
)
def test_pillow_to_cv2(
@@ -103,9 +103,9 @@ def test_pillow_to_cv2(
result = pillow_to_cv2(image=empty_pillow_image)
# then
assert np.allclose(
result, empty_cv2_image
), "Conversion to OpenCV image expected not to change the content of image"
assert np.allclose(result, empty_cv2_image), (
"Conversion to OpenCV image expected not to change the content of image"
)
def test_images_to_cv2_when_empty_input_provided() -> None:
@@ -128,9 +128,9 @@ def test_images_to_cv2_when_only_cv2_images_provided(
# then
assert len(result) == 5, "Expected the same number of output element as input ones"
for result_element in result:
assert (
result_element is empty_cv2_image
), "Expected CV images not to be touched by conversion"
assert result_element is empty_cv2_image, (
"Expected CV images not to be touched by conversion"
)
def test_images_to_cv2_when_only_pillow_images_provided(
@@ -146,9 +146,9 @@ def test_images_to_cv2_when_only_pillow_images_provided(
# then
assert len(result) == 5, "Expected the same number of output element as input ones"
for result_element in result:
assert np.allclose(
result_element, empty_cv2_image
), "Output images expected to be equal to empty OpenCV image"
assert np.allclose(result_element, empty_cv2_image), (
"Output images expected to be equal to empty OpenCV image"
)
def test_images_to_cv2_when_mixed_input_provided(
@@ -163,9 +163,9 @@ def test_images_to_cv2_when_mixed_input_provided(
# then
assert len(result) == 2, "Expected the same number of output element as input ones"
assert np.allclose(
result[0], empty_cv2_image
), "PIL image should be converted to OpenCV one, equal to example empty image"
assert (
result[1] is empty_cv2_image
), "Expected CV images not to be touched by conversion"
assert np.allclose(result[0], empty_cv2_image), (
"PIL image should be converted to OpenCV one, equal to example empty image"
)
assert result[1] is empty_cv2_image, (
"Expected CV images not to be touched by conversion"
)
+9 -9
View File
@@ -21,9 +21,9 @@ def test_resize_image_for_opencv_image() -> None:
)
# then
assert np.allclose(
result, expected_result
), "Expected output shape to be (w, h): (1024, 768)"
assert np.allclose(result, expected_result), (
"Expected output shape to be (w, h): (1024, 768)"
)
def test_resize_image_for_pillow_image() -> None:
@@ -41,9 +41,9 @@ def test_resize_image_for_pillow_image() -> None:
# then
assert result.size == (1024, 768), "Expected output shape to be (w, h): (1024, 768)"
difference = ImageChops.difference(result, expected_result)
assert (
difference.getbbox() is None
), "Expected no difference in resized image content as the image is all zeros"
assert difference.getbbox() is None, (
"Expected no difference in resized image content as the image is all zeros"
)
def test_letterbox_image_for_opencv_image() -> None:
@@ -95,9 +95,9 @@ def test_letterbox_image_for_pillow_image() -> None:
1024,
), "Expected output shape to be (w, h): (1024, 1024)"
difference = ImageChops.difference(result, expected_result)
assert (
difference.getbbox() is None
), "Expected padding to be added top and bottom with padding added top and bottom"
assert difference.getbbox() is None, (
"Expected padding to be added top and bottom with padding added top and bottom"
)
def test_create_tiles_with_one_image(
Generated
+5414
View File
File diff suppressed because it is too large Load Diff