Compare commits

...
88 Commits
Author SHA1 Message Date
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
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
35 changed files with 6402 additions and 5729 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@4db96194c378173c656ce18a155ffc14a9fc4355 # v5.2.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@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
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@4db96194c378173c656ce18a155ffc14a9fc4355 # v5.2.2
with:
python-version: ${{ matrix.python-version }}
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --extra build
uv build
uv run twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
attestations: true
@@ -1,4 +1,4 @@
name: Publish Supervision Releases to PyPI and TestPyPI
name: Publish Supervision Releases to PyPI
on:
push:
@@ -9,10 +9,12 @@ on:
permissions: {} # Explicitly remove all permissions by default
jobs:
build-and-publish-pre-release:
publish-release:
name: Publish Release Package
runs-on: ubuntu-latest
environment: release
environment:
name: release
url: https://pypi.org/project/supervision/
timeout-minutes: 10
permissions:
id-token: write # Required for PyPI publishing
@@ -24,27 +26,18 @@ jobs:
- 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
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@4db96194c378173c656ce18a155ffc14a9fc4355 # v5.2.2
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/*
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@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
-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@4db96194c378173c656ce18a155ffc14a9fc4355 # v5.2.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
+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@4db96194c378173c656ce18a155ffc14a9fc4355 # v5.2.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@4db96194c378173c656ce18a155ffc14a9fc4355 # v5.2.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
+1 -1
View File
@@ -32,7 +32,7 @@ repos:
additional_dependencies: ["bandit[toml]"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
rev: v0.8.6
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+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/).
+6
View File
@@ -89,6 +89,12 @@ 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.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
@@ -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)
+1 -2
View File
@@ -178,8 +178,7 @@ markdown_extensions:
anchor_linenums: 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"
- "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.8/purify.min.js"
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.0rc4"
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.27.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"
+3 -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,9 @@ from supervision.detection.utils import (
xcycwh_to_xyxy,
xywh_to_xyxy,
xyxy_to_polygons,
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 +126,6 @@ __all__ = [
"BackgroundOverlayAnnotator",
"BaseDataset",
"BlurAnnotator",
"BoundingBoxAnnotator",
"BoxAnnotator",
"BoxCornerAnnotator",
"ByteTrack",
@@ -226,4 +225,5 @@ __all__ = [
"xcycwh_to_xyxy",
"xywh_to_xyxy",
"xyxy_to_polygons",
"xyxy_to_xywh",
]
-87
View File
@@ -36,7 +36,6 @@ from supervision.utils.image import (
overlay_image,
scale_image,
)
from supervision.utils.internal import deprecated
CV2_FONT = cv2.FONT_HERSHEY_SIMPLEX
@@ -124,92 +123,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.
-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
+60 -20
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
@@ -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(
+37
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)`
@@ -1,3 +1,4 @@
import json
import re
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Union
@@ -5,23 +6,41 @@ 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"
RESULT_TYPES: Dict[LMM, type] = {LMM.PALIGEMMA: str, LMM.FLORENCE_2: dict}
class VLM(Enum):
PALIGEMMA = "paligemma"
FLORENCE_2 = "florence_2"
QWEN_2_5_VL = "qwen_2_5_vl"
REQUIRED_ARGUMENTS: Dict[LMM, List[str]] = {
LMM.PALIGEMMA: ["resolution_wh"],
LMM.FLORENCE_2: ["resolution_wh"],
RESULT_TYPES: Dict[VLM, type] = {
VLM.PALIGEMMA: str,
VLM.FLORENCE_2: dict,
VLM.QWEN_2_5_VL: str,
}
ALLOWED_ARGUMENTS: Dict[LMM, List[str]] = {
LMM.PALIGEMMA: ["resolution_wh", "classes"],
LMM.FLORENCE_2: ["resolution_wh"],
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 = [
@@ -38,58 +57,172 @@ SUPPORTED_TASKS_FLORENCE_2 = [
]
def validate_lmm_parameters(
lmm: Union[LMM, str], result: Any, kwargs: Dict[str, Any]
) -> LMM:
if isinstance(lmm, str):
def validate_vlm_parameters(
vlm: Union[VLM, str], result: Any, kwargs: Dict[str, Any]
) -> VLM:
if isinstance(vlm, str):
try:
lmm = LMM(lmm.lower())
vlm = VLM(vlm.lower())
except ValueError:
raise ValueError(
f"Invalid lmm value: {lmm}. Must be one of {[e.value for e in LMM]}"
f"Invalid vlm value: {vlm}. Must be one of {[e.value for e in VLM]}"
)
if not isinstance(result, RESULT_TYPES[lmm]):
if not isinstance(result, RESULT_TYPES[vlm]):
raise ValueError(
f"Invalid LMM result type: {type(result)}. Must be {RESULT_TYPES[lmm]}"
f"Invalid VLM result type: {type(result)}. Must be {RESULT_TYPES[vlm]}"
)
required_args = REQUIRED_ARGUMENTS.get(lmm, [])
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(lmm, [])
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 {lmm.name}")
raise ValueError(f"Argument {arg} is not allowed for {vlm.name}")
return lmm
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]).astype(bool)
xyxy, class_name = xyxy[mask], class_name[mask]
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[
+91 -4
View File
@@ -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()
+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]'`"
)
-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])
+24
View File
@@ -21,6 +21,7 @@ from supervision.detection.utils import (
scale_boxes,
xcycwh_to_xyxy,
xywh_to_xyxy,
xyxy_to_xywh,
)
TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool)
@@ -1381,6 +1382,29 @@ 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(
"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(
Generated
+5412
View File
File diff suppressed because it is too large Load Diff