Compare commits

...
163 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
LinasKo 9975b63216 Enable kapa for 0.27.0 2024-12-13 12:22:45 +02:00
Zeel B Patel 512edd0c9b Merge branch 'roboflow:develop' into feat/expand-image-formats 2024-11-21 17:48:58 +05:30
patel-zeel f4850b870f replace cv2 with PIL for speed 2024-10-31 16:45:19 +05:30
50 changed files with 6875 additions and 6048 deletions
-42
View File
@@ -1,42 +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:
name: Poetry install and check
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: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: ${{ matrix.python-version }}
- name: 📜 Setup Poetry
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd # v3.0.1
- name: 🔍 Check the correctness of the project config
run: poetry check
- name: 🚀 Do Install the package Test
run: poetry install --with dev --extras metrics
- name: 🧪 Run the Import test
run: poetry run python -c "import supervision; from supervision import assets; from supervision import metrics; print(supervision.__version__)"
+16 -8
View File
@@ -33,17 +33,25 @@ jobs:
with:
fetch-depth: 0
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.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: 📜 Setup Poetry
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd # v3.0.1
- 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
- name: 🏗️ Install dependencies
run: |
poetry install --with=docs
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: |
@@ -53,10 +61,10 @@ jobs:
- 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 }} poetry run mike deploy --push develop
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 }} poetry run mike deploy --push --update-aliases $latest_tag latest
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
+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
-52
View File
@@ -1,52 +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:
permissions: {} # Explicitly remove all permissions by default
jobs:
build-and-publish-pre-release:
name: Publish Pre-releasePackage
runs-on: ubuntu-latest
environment: release
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: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: ${{ matrix.python-version }}
- name: 📜 Setup Poetry
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd # v3.0.1
- name: 🏗️ Build source and wheel distributions
run: |
poetry install --with=build
poetry build
twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
with:
attestations: true
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
with:
repository-url: https://test.pypi.org/legacy/
attestations: true
+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
-50
View File
@@ -1,50 +0,0 @@
name: Publish Supervision Releases to PyPI and TestPyPI
on:
push:
tags:
- "[0-9]+.[0-9]+[0-9]+.[0-9]"
workflow_dispatch:
permissions: {} # Explicitly remove all permissions by default
jobs:
build-and-publish-pre-release:
name: Publish Release Package
runs-on: ubuntu-latest
environment: release
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: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: ${{ matrix.python-version }}
- name: 📜 Setup Poetry
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd # v3.0.1
- name: 🏗️ Build source and wheel distributions
run: |
poetry install --with=build
poetry build
twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
with:
attestations: true
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
with:
repository-url: https://test.pypi.org/legacy/
attestations: true
+5 -8
View File
@@ -23,18 +23,15 @@ jobs:
with:
fetch-depth: 0
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.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: 📜 Setup Poetry
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd # v3.0.1
- name: 🏗️ Install dependencies
run: |
poetry install --with=docs
uv pip install -r pyproject.toml --extra docs
- name: 🧪 Test Docs Build
run: |
poetry run mkdocs build --verbose
run: uv run mkdocs build --verbose
-47
View File
@@ -1,47 +0,0 @@
name: Test WorkFlow
on:
pull_request:
branches: [main, develop]
permissions:
contents: read
checks: write # Required for test reporting
pull-requests: write # Required for PR comments
jobs:
pytest:
name: PyTest
timeout-minutes: 10
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
fail-fast: false # Continue with other versions if one fails
runs-on: ${{ matrix.os }}
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: ${{ matrix.python-version }}
- name: 📜 Setup Poetry
uses: abatilo/actions-poetry@e78f54a89cb052fff327414dd9ff010b5d2b4dbd # v3.0.1
- name: 📦 Install dependencies
run: |
poetry install --with=test
- name: Run pytest
uses: pavelzw/pytest-action@510c5e90c360a185039bea56ce8b3e7e51a16507 # v2.2.0
with:
verbose: true
emoji: true
job-summary: true
custom-arguments: "-q"
click-to-expand: true
report-title: "Test Report"
custom-pytest: "poetry run pytest"
+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.8.0'
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.8.2
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:
+6 -17
View File
@@ -155,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
@@ -192,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.
@@ -214,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/).
+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>
+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()
+5 -4
View File
@@ -113,18 +113,19 @@ You can install `supervision` in a
pip install -e "."
```
=== "poetry"
=== "uv"
```bash
# clone repository and navigate to root directory
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);
}}();
@@ -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.
@@ -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
@@ -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
@@ -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)
+8 -3
View File
@@ -149,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
@@ -176,11 +179,13 @@ markdown_extensions:
check_paths: true
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
extra_javascript:
# disabled until 0.27.0, so we can test its functionality
# - "javascripts/init_kapa_widget.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
-5084
View File
File diff suppressed because it is too large Load Diff
+55 -111
View File
@@ -1,18 +1,16 @@
[tool.poetry]
[project]
name = "supervision"
version = "0.26.0rc2"
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,116 +37,59 @@ classifiers = [
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS',
]
[tool.poetry.dependencies]
python = "^3.8"
# For Python versions 3.13 and above, use numpy versions 2.1.0,
# required for poetry to not fail.
numpy = [
{ version = ">=1.21.2", python = "<3.13" },
{ version = ">=2.1.0", 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"
]
scipy = [
{ version = "1.10.0", python = "<3.9" },
{ version = "^1.10.0", python = ">=3.9" },
{ version = ">=1.14.1", 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 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,<3.13" },
{ version = ">=1.3.0", python = ">=3.13" },
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"
]
matplotlib = [
{ version = ">=3.6.0,<3.8.0", python = "3.8" },
{ version = ">=3.6.0", python = ">=3.9" },
{ version = ">=3.7.3", python = ">=3.12" },
{ version = ">=3.9.2", python = ">=3.13" },
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"
]
pyyaml = ">=5.3"
defusedxml = "^0.7.1"
pillow = ">=9.4"
requests = ">=2.26.0"
tqdm = ">=4.62.3"
# pandas: picked lowest major version that supports Python 3.8
# pandas 2.2.3 has been released with support for Python 3.13
pandas = [
{ version = ">=2.0.0", python = "<3.13", optional = true },
{ version = ">=2.2.3", python = ">=3.13", optional = true },
build = [
"twine>=5.1.1,<7.0.0",
"wheel>=0.40,<0.46",
"build>=0.10,<1.3"
]
opencv-python = ">=4.5.5.64"
[tool.poetry.extras]
metrics = ["pandas"]
[tool.poetry.group.test.dependencies]
pytest = ">=7.2.2,<9.0.0"
pytest-md = "^0.2.0"
pytest-emoji = "^0.2.0"
[tool.poetry.group.dev.dependencies]
twine = ">=5.1.1,<7.0.0"
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" },
]
[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"
pandas-stubs = ">=2.0.0.230412"
[tool.poetry.group.build.dependencies]
twine = ">=5.1.1,<7.0.0"
[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"
@@ -273,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"
@@ -282,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"
+5 -3
View File
@@ -9,7 +9,6 @@ except importlib_metadata.PackageNotFoundError:
from supervision.annotators.core import (
BackgroundOverlayAnnotator,
BlurAnnotator,
BoundingBoxAnnotator,
BoxAnnotator,
BoxCornerAnnotator,
CircleAnnotator,
@@ -46,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,
@@ -78,7 +76,10 @@ from supervision.detection.utils import (
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,
@@ -126,7 +127,6 @@ __all__ = [
"BackgroundOverlayAnnotator",
"BaseDataset",
"BlurAnnotator",
"BoundingBoxAnnotator",
"BoxAnnotator",
"BoxCornerAnnotator",
"ByteTrack",
@@ -226,4 +226,6 @@ __all__ = [
"xcycwh_to_xyxy",
"xywh_to_xyxy",
"xyxy_to_polygons",
"xyxy_to_xyah",
"xyxy_to_xywh",
]
+5 -92
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,
@@ -36,7 +37,6 @@ from supervision.utils.image import (
overlay_image,
scale_image,
)
from supervision.utils.internal import deprecated
CV2_FONT = cv2.FONT_HERSHEY_SIMPLEX
@@ -124,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.
@@ -1217,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)):
@@ -1826,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)):
@@ -1944,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
+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)
-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
+67 -27
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
import numpy as np
@@ -9,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,
@@ -38,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
@@ -712,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 = [], [], []
@@ -799,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:
@@ -837,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()
@@ -859,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:
"""
@@ -1027,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],
@@ -1203,7 +1243,7 @@ class Detections:
if isinstance(index, str):
return self.data.get(index)
if self.is_empty():
return Detections.empty()
return self
if isinstance(index, int):
index = [index]
return Detections(
@@ -1306,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)))
@@ -1362,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
+87
View File
@@ -321,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)`
@@ -360,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.
+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")
+94 -7
View File
@@ -23,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
@@ -32,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)
@@ -61,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
@@ -91,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`
@@ -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()
+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:
+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])
+94 -18
View File
@@ -21,6 +21,8 @@ from supervision.detection.utils import (
scale_boxes,
xcycwh_to_xyxy,
xywh_to_xyxy,
xyxy_to_xcycarh,
xyxy_to_xywh,
)
TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool)
@@ -388,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(
@@ -1041,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(
@@ -1214,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(
@@ -1381,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(
@@ -180,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