Compare commits

...
39 Commits
Author SHA1 Message Date
Jirka Borovec a90a1c967b Debugging workflow permissions and add PR dry-run (#2083)
* Debugging workflow permissions and add PR dry-run

* ls all

* path: dist/
2026-01-19 14:23:14 +01:00
Jirka Borovec e2c380acbc Debugging workflow permissions and add PR dry-run (#2082) 2026-01-19 12:35:46 +01:00
SkalskiP 819d74f57c bump version from 0.27.0 to 0.28.0rc0 2026-01-19 12:10:26 +01:00
jirka 47e6a4bc88 Rename workflow file for documentation CI process 2026-01-16 18:51:30 +01:00
09de0f4f06 feat(ruff): add pytest linting rule (#2053)
* feat(ruff): add pytest linting rule
* --unsafe-fixes
* fix(pre_commit): 🎨 auto format pre-commit hooks


---------

Co-authored-by: jiri BOROVEC <jirka@Jiris-MacBook-Pro-J4N257Y6Q2.local>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-01-13 19:34:22 +01:00
f1e0c9cdd7 chore(pre_commit): add pyproject-fmt and validate-pyproject hooks (#2045)
* chore(pre_commit): add pyproject-fmt and validate-pyproject hooks
* fix(pre_commit): 🎨 auto format pre-commit hooks
* Apply suggestions from code review
* format pre-commit hooks for consistency

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: jiri BOROVEC <jirka@Jiris-MacBook-Pro-J4N257Y6Q2.local>
2026-01-13 18:48:06 +01:00
KshitijAucharmalandJirka Borovec 72d55a2938 docs: changed --extra to --group for dev (#1972)
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-01-13 18:33:09 +01:00
3abb6245c1 chore(pre_commit): ⬆ pre_commit autoupdate (#2075)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.14.10 → v0.14.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.10...v0.14.11)
- [github.com/executablebooks/mdformat: 0.7.17 → 1.0.0](https://github.com/executablebooks/mdformat/compare/0.7.17...1.0.0)

* fix(pre_commit): 🎨 auto format pre-commit hooks
* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: jirka <jirka.borovec@seznam.cz>
2026-01-12 20:15:27 +01:00
Jirka BorovecandCopilot a352e23d95 Switch PR Conflict Labeler workflow to eps1lon/actions-label-merge-conflict (#2076)
* Switch PR Conflict Labeler workflow to `eps1lon/actions-label-merge-conflict`
* Apply suggestions from code review

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 20:11:36 +01:00
Jirka BorovecandCopilot 75a2036ea3 Add GitHub Action for labeling PRs with conflicts (#2073)
* Add GitHub Action for labeling PRs with conflicts
* Apply suggestions from code review

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 16:06:55 +00:00
3d7642c09e chore(pre_commit): enable mdformat with additional dependencies (#2043)
* chore(pre_commit): enable mdformat with additional dependencies
* fix(pre_commit): 🎨 auto format pre-commit hooks
* Fix mdformat-introduced syntax errors in documentation (#2044)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
2026-01-12 16:43:17 +01:00
fabbff2b60 Remove security noqa comments by fixing underlying issues (#2064)
* Fix security issues and remove noqa comments
- Replace MD5 hash_new with md5() and add usedforsecurity=False flag
- Add documentation explaining MD5 is for file integrity, not security
- Add timeout parameter to requests.get call
- Add explicit shell=False and check=False to subprocess.run
- Remove all security-related noqa comments
- Add per-file ignores for S311 in tests and S603 in examples

---------

Co-authored-by: jirka <jirka.borovec@seznam.cz>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-12 15:25:41 +01:00
Jirka BorovecandCopilot 751eddc0ce Add Discord link to issues for community collaboration (#2074)
* Add Discord link to issue templates for community collaboration

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:11:23 +01:00
Jirka BorovecandCopilot 8df7aea949 Update CI workflows for documentation build and deployment (#2063)
Material for MkDocs Insiders – Now free for everyone
-> https://squidfunk.github.io/mkdocs-material/blog/2025/11/11/insiders-now-free-for-everyone/

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 15:06:08 +01:00
bbc96e14c9 docs: Update CONTRIBUTING.md for clarity and formatting improvements (#2060)
* docs: Update CONTRIBUTING.md for clarity and formatting improvements
* fix(pre_commit): 🎨 auto format pre-commit hooks
* Apply suggestions from code review
* Update command for installing project dependencies
* Update CONTRIBUTING.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 14:05:28 +01:00
Jirka Borovecandpre-commit-ci[bot] 5695b7f58f chore: Update issue templates for bug reports and feature requests (#2052)
* chore: Update issue templates for bug reports and feature requests
* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-12 12:32:14 +00:00
Jirka Borovec a1c58a92e2 Update classifiers in pyproject.toml to reflect production status and add multimedia topics (#2047) 2026-01-12 11:04:00 +00:00
Jirka Borovec 886a44b102 Update authors and maintainers in pyproject.toml (#2046) 2026-01-12 12:00:27 +01:00
dependabot[bot] 9b91c779e5 ⬆️ Update pytest-cov requirement (#2071)
Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-version: 7.0.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-12 11:59:52 +01:00
Jirka Borovecandpre-commit-ci[bot] 3697b2298c chore: add pull request template for improved PR process (#2042)
* chore: add pull request template for improved submission process
* fix(pre_commit): 🎨 auto format pre-commit hooks
* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-12 11:58:57 +01:00
Jirka BorovecandCopilot Autofix powered by AI e861878a22 chore: Add shared build-package workflow for (pre)publishing (#2050)
* chore: Add shared build-package workflow for (pre)publishing
* Potential fix for code scanning alert no. 4: Workflow does not contain permissions

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-01-12 10:10:04 +01:00
Jirka Borovec 834fd8db05 ⬆️ Update dependabot config to group GH Actions bumps (#2034) 2026-01-12 10:01:13 +01:00
Jirka Borovec fba1713944 ci: add UV cache pruning step (#2067) 2026-01-12 09:53:15 +01:00
b143121859 Add pytest-cov dependency and integrate Codecov in CI workflow (#2066)
* Add pytest-cov dependency and integrate Codecov in CI workflow
* fix(pre_commit): 🎨 auto format pre-commit hooks
* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 09:41:44 +01:00
Jirka Borovec 132cc056f6 chore(pre_commit): remove exclusion for trailing-whitespace hook (#2049) 2026-01-12 09:30:54 +01:00
dependabot[bot] f6e0896278 ⬆️ Bump astral-sh/setup-uv from 7.1.6 to 7.2.0 (#2070)
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.6 to 7.2.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/681c641aba71e4a1c380be3ab5e12ad51f415867...61cb8a9741eeb8a550a1b8544337180c0fc8476b)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-12 09:30:04 +01:00
dependabot[bot] de4f060d3b ⬆️ Update build requirement from <1.4,>=0.10 to >=0.10,<1.5 (#2069)
Updates the requirements on [build](https://github.com/pypa/build) to permit the latest version.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/0.10.0...1.4.0)

---
updated-dependencies:
- dependency-name: build
  dependency-version: 1.4.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-12 09:29:41 +01:00
Jirka Borovec 14b7f91c5c chore(pre_commit): remove duplicated pyupgrade hook (#2041) 2026-01-12 09:29:12 +01:00
Jirka BorovecandCopilot 5016ed3c8a chore(pre_commit): update Ruff rules with its references (#2040)
The `fixanle` they do not have any impact if they are not listed in selected.

In Ruff, the select list determines which rules are active (checked). The fixable list merely controls which of those active rules are allowed to automatically modify your code when running with --fix.

If a rule is listed in fixable but not in select, it is effectively disabled; Ruff won't look for it, so there will be no errors to fix. Listing extra items in fixable is harmless but redundant code clutter.

The default value for fixable in Ruff is ["ALL"].

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 08:23:49 +01:00
Jirka Borovec d570366fb4 chore(pre_commit): convert bandit into Ruff's S rule & add TODOs for security improvements (#2039) 2026-01-12 08:08:19 +01:00
95ef33a102 Add testing-guardian job to aggregate test results (#2037)
* Add testing-guardian job to aggregate test results
* Potential fix for code scanning alert no. 2: Workflow does not contain permissions
* Apply suggestions from code review

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-06 17:28:33 +01:00
Jirka BorovecandCopilot fc11beac78 Add push triggers for main and develop branches in workflow files (#2033)
* Add push triggers for `main` and `develop` branches in workflow files
* Add conditional concurrency cancellation for PR vs push events (#2035)

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-01-06 15:39:28 +01:00
pre-commit-ci[bot] cb1fcecaf2 chore(pre_commit): ⬆ pre_commit autoupdate (#2032)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.13.3 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.3...v0.14.10)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-01-05 20:29:04 +01:00
Christian ClaussandJirka Borovec afdbdc55f7 pre-commit autoupdate 2025-10-04 (#1976)
* pre-commit autoupdate 2025-10-04
* Rename ruff hook to ruff-check in pre-commit config

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
2026-01-05 17:27:28 +01:00
28dfc39c02 chore(pre_commit): ⬆ pre_commit autoupdate (#1928)
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
- [github.com/PyCQA/bandit: 1.8.6 → 1.9.2](https://github.com/PyCQA/bandit/compare/1.8.6...1.9.2)
- [github.com/astral-sh/ruff-pre-commit: v0.12.5 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.5...v0.14.10)
- [github.com/asottile/pyupgrade: v3.20.0 → v3.21.2](https://github.com/asottile/pyupgrade/compare/v3.20.0...v3.21.2)

* linting

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: jirka <jirka.borovec@seznam.cz>
2026-01-05 17:21:58 +01:00
dependabot[bot] 793f246ebc ⬆️ Bump actions/create-github-app-token from 2.0.6 to 2.2.1 (#2024)
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2.0.6 to 2.2.1.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/df432ceedc7162793a195dd1713ff69aefc7379e...29824e69f54612133e76f7eaac726eef6c875baf)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 16:48:24 +01:00
dependabot[bot] 9aa9ef5bbe ⬆️ Bump actions/checkout from 4.2.2 to 6.0.1 (#2025)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/11bd71901bbe5b1630ceea73d27597364c9af683...8e8c483db84b4bee98b60c0593521ed34d9990e8)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 16:48:15 +01:00
dependabot[bot] 6e18804c55 ⬆️ Bump astral-sh/setup-uv from 7.1.2 to 7.1.6 (#2029)
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.2 to 7.1.6.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41...681c641aba71e4a1c380be3ab5e12ad51f415867)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-05 16:48:02 +01:00
Jirka Borovec bca3c60bb3 resolve docs build / pin mkdocstrings-python<2.0.0 (#2031) 2026-01-05 15:48:18 +01:00
69 changed files with 988 additions and 760 deletions
+45
View File
@@ -0,0 +1,45 @@
# see https://docs.codecov.io/docs/codecov-yaml
# Validation check:
# $ curl --data-binary @.codecov.yml https://codecov.io/validate
# https://docs.codecov.io/docs/codecovyml-reference
codecov:
bot: "codecov-io"
strict_yaml_branch: "yaml-config"
require_ci_to_pass: yes
notify:
# after_n_builds: 2
wait_for_ci: yes
coverage:
precision: 0 # 2 = xx.xx%, 0 = xx%
round: nearest # how coverage is rounded: down/up/nearest
range: 40...100 # custom range of coverage colors from red -> yellow -> green
status:
# https://codecov.readme.io/v1.0/docs/commit-status
project:
default:
informational: true
target: 95% # specify the target coverage for each commit status
threshold: 10% # allow this little decrease on project
# https://github.com/codecov/support/wiki/Filtering-Branches
# branches: master
if_ci_failed: error
# https://github.com/codecov/support/wiki/Patch-Status
patch:
default:
informational: true
target: 95% # enforce same coverage target for new/changed code as the project
threshold: 10% # allow only a small decrease on patch coverage
changes: false
# https://docs.codecov.com/docs/github-checks#disabling-github-checks-patch-annotations
github_checks:
annotations: false
comment:
layout: header, diff
require_changes: false
behavior: default # update if exists else create new
# branches: *
+33 -30
View File
@@ -1,65 +1,68 @@
name: 🐞 Bug Report
# title: " "
description: Problems with Supervision
title: "[Bug]: "
description: Report a bug or unexpected behavior in Supervision
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thank you for submitting a Supervision 🐞 Bug Report!
- type: checkboxes
attributes:
label: Search before asking
description: >
Please search the [issues](https://github.com/roboflow/supervision/issues) to see if a similar bug report already exists.
description: Please search [issues](https://github.com/roboflow/supervision/issues) and [discussions](https://github.com/roboflow/supervision/discussions) first.
options:
- label: >
I have searched the Supervision [issues](https://github.com/roboflow/supervision/issues) and found no similar bug report.
- label: I have searched the issues and discussions and found no similar bug report.
required: true
- type: textarea
attributes:
label: Bug
description: Provide console output with error messages and/or screenshots of the bug.
description: Describe the bug, what you expected, and what actually happened.
placeholder: |
💡 ProTip! Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response.
**What's the bug?**
Brief description...
**Expected behavior:**
What should happen...
**Actual behavior:**
What actually happens...
**Error/Traceback (if any):**
```python
# Paste error messages here
```
validations:
required: true
- type: textarea
attributes:
label: Environment
description: Please specify the software and hardware you used to produce the bug.
description: Your setup details
placeholder: |
- Supervision: 0.1.0
- OS: Ubuntu 20.04
- Python: 3.8.10
- Supervision: 0.23.0
- Python: 3.10.12
- OS: Ubuntu 22.04
value: |
- Supervision:
- Python:
- OS:
validations:
required: false
required: true
- type: textarea
attributes:
label: Minimal Reproducible Example
description: >
When asking a question, people will be better able to provide help if you provide code that they can easily understand and use to **reproduce** the problem.
This is referred to by community members as creating a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example).
description: Code to reproduce the bug ([guide](https://stackoverflow.com/help/minimal-reproducible-example))
placeholder: |
```
# Code to reproduce your issue here
```python
import supervision as sv
# Your code here
```
validations:
required: false
- type: textarea
attributes:
label: Additional
description: Anything else you would like to share?
- type: checkboxes
attributes:
label: Are you willing to submit a PR?
description: >
(Optional) We encourage you to submit a [Pull Request](https://github.com/roboflow/supervision/pulls) (PR) to help improve Supervision for everyone, especially if you have a good understanding of how to implement a fix or feature.
description: (Optional) We encourage community contributions!
options:
- label: Yes I'd like to help by submitting a PR!
+17
View File
@@ -0,0 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: ❓ Ask a Question
url: https://github.com/roboflow/supervision/discussions/new?category=q-a
about: Ask questions about using Supervision in GitHub Discussions
- name: 📚 Documentation
url: https://supervision.roboflow.com/
about: Check the official Supervision documentation for guides and API references
- name: 💬 GitHub Discussions
url: https://github.com/roboflow/supervision/discussions
about: Join community discussions, share ideas, and get help
- name: 🌟 Show and Tell
url: https://github.com/roboflow/supervision/discussions/categories/show-and-tell
about: Share your projects and creations built with Supervision
- name: 🗣️ Roboflow Discord
url: https://discord.com/invite/GbfgXGJ8Bk
about: Join our Discord to collaborate with other community members
+20 -19
View File
@@ -1,6 +1,6 @@
name: 🤩 Feature Request
description: Suggest a Supervision idea
# title: " "
title: "[Feature]: "
description: Suggest a new feature or improvement
labels: [enhancement]
body:
- type: markdown
@@ -11,39 +11,40 @@ body:
- type: checkboxes
attributes:
label: Search before asking
description: >
Please search the [issues](https://github.com/roboflow/supervision/issues) to see if a similar feature request already exists.
description: Please search [issues](https://github.com/roboflow/supervision/issues) and [discussions](https://github.com/roboflow/supervision/discussions) first.
options:
- label: >
I have searched the Supervision [issues](https://github.com/roboflow/supervision/issues) and found no similar feature requests.
- label: I have searched the issues and discussions and found no similar feature request.
required: true
- type: textarea
attributes:
label: Description
description: A short description of your feature.
label: Feature Description
description: What feature would you like and why?
placeholder: |
What new feature would you like to see in Supervision?
**What:** Describe the feature...
**Why:** What problem does it solve? Who would benefit?
**How (optional):** Any ideas on implementation?
validations:
required: true
- type: textarea
attributes:
label: Use case
description: |
Describe the use case of your feature request. It will help us understand and prioritize the feature request.
label: Example Usage
description: (Optional) Show how you'd like to use this feature
placeholder: |
How would this feature be used, and who would use it?
```python
import supervision as sv
- type: textarea
attributes:
label: Additional
description: Anything else you would like to share?
# Your example usage here
```
validations:
required: false
- type: checkboxes
attributes:
label: Are you willing to submit a PR?
description: >
(Optional) We encourage you to submit a [Pull Request](https://github.com/roboflow/supervision/pulls) (PR) to help improve Supervision for everyone, especially if you have a good understanding of how to implement a fix or feature.
description: (Optional) We encourage community contributions!
options:
- label: Yes I'd like to help by submitting a PR!
-33
View File
@@ -1,33 +0,0 @@
name: ❓ Question
description: Ask a Supervision question
# title: " "
labels: [question]
body:
- type: markdown
attributes:
value: |
Thank you for asking a Supervision ❓ Question!
- type: checkboxes
attributes:
label: Search before asking
description: >
Please search the [issues](https://github.com/roboflow/supervision/issues) to see if a similar feature request already exists.
options:
- label: >
I have searched the Supervision [issues](https://github.com/roboflow/supervision/issues) and found no similar feature requests.
required: true
- type: textarea
attributes:
label: Question
description: What is your question?
placeholder: |
💡 ProTip! Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response.
validations:
required: true
- type: textarea
attributes:
label: Additional
description: Anything else you would like to share?
+67
View File
@@ -0,0 +1,67 @@
<details>
<summary>Before submitting</summary>
- [ ] Self-reviewed the code
- [ ] Updated documentation, follow [Google-style](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods)
- [ ] Added docs entry for autogeneration (if new functions/classes)
- [ ] Added/updated tests
- [ ] All tests pass locally
</details>
## Description
<!-- Provide a clear and concise description of your changes -->
## Type of Change
<!-- Mark the relevant option with an "x" and delete the others -->
- 🐛 Bug fix (non-breaking change which fixes an issue)
- ✨ New feature (non-breaking change which adds functionality)
- 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
- 📝 Documentation update
- 🧪 Test update
- 🔨 Refactoring (no functional changes)
- ⚡ Performance improvement
- 🔧 Chore (dependencies, configs, etc.)
## Motivation and Context
<!-- Why is this change required? What problem does it solve? -->
<!-- If it fixes an open issue, please link to the issue here -->
Closes #(issue)
## Changes Made
<!-- List the main changes made in this PR -->
-
-
-
## Testing
<!-- Describe the tests you ran and how to reproduce them -->
- [ ] I have tested this code locally
- [ ] I have added unit tests that prove my fix is effective or that my feature works
- [ ] All new and existing tests pass
## Google Colab (optional)
<!-- If applicable, provide a link to a Google Colab notebook demonstrating the feature/fix -->
<!-- Ensure the notebook is publicly accessible -->
Colab link:
## Screenshots/Videos (optional)
<!-- If applicable, add screenshots or videos to demonstrate the changes -->
## Additional Notes
<!-- Any additional information that reviewers should know -->
+4
View File
@@ -8,6 +8,10 @@ updates:
commit-message:
prefix: ⬆️
target-branch: "develop"
groups:
github-actions:
patterns:
- "*"
# Python
- package-ecosystem: "pip"
directory: "/"
+37
View File
@@ -0,0 +1,37 @@
name: Build Package
permissions:
contents: read
on:
workflow_call:
inputs:
python-version:
required: false
type: string
default: "3.10"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: 🐍 Install uv and set Python version ${{ inputs.python-version }}
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
with:
python-version: ${{ inputs.python-version }}
activate-environment: true
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --group build
uv build
uv run twine check --strict dist/*
- name: 📤 Upload distribution artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
@@ -1,37 +1,39 @@
name: 🧪 Docs Test WorkFlow 📚
name: Test Docs Build
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
concurrency:
group: docs-test-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# Restrict permissions by default
permissions:
contents: read # Required for checkout
checks: write # Required for test reporting
jobs:
docs-build-test:
docs-build:
name: Test docs build
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: 🐍 Install uv and set Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: 🐍 Install uv and set Python
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
python-version: ${{ matrix.python-version }}
python-version: "3.10"
activate-environment: true
- name: 🏗️ Install dependencies
run: |
uv pip install -r pyproject.toml --group docs
run: uv pip install -r pyproject.toml --group docs
- name: 🧪 Test Docs Build
run: uv run mkdocs build --verbose
+87
View File
@@ -0,0 +1,87 @@
name: 🔧 Pytest/Test Workflow
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
permissions:
contents: read
concurrency:
group: pytest-test-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build-pkg:
uses: ./.github/workflows/build-package.yml
run-tests:
name: Import Test and Pytest Run
# needs: build # todo: consider using this build package for testing
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.os }}
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: 🐍 Install uv and set Python version ${{ matrix.python-version }}
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
- name: 🚀 Install Packages
run: uv pip install -r pyproject.toml --group dev --group 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 --cov=supervision --cov-report=xml
- name: Generate Coverage Report
run: |
coverage xml
coverage report
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: "coverage.xml"
flags: cpu,${{ runner.os }},python${{ matrix.python-version }}
env_vars: OS,PYTHON
name: codecov-umbrella
fail_ci_if_error: false
- name: Minimize uv cache
run: uv cache prune --ci
testing-guardian:
runs-on: ubuntu-latest
needs: run-tests
if: always()
steps:
- name: 📋 Display test result
run: echo "${{ needs.run-tests.result }}"
- name: ❌ Fail guardian on test failure
if: needs.run-tests.result == 'failure'
run: exit 1
# Ensure that cancelled or skipped test runs still cause this guardian job to fail,
# using an explicit exit code instead of relying on timeout behavior.
- name: ⚠️ cancelled or skipped...
if: contains(fromJSON('["cancelled", "skipped"]'), needs.run-tests.result)
run: |
echo "run-tests job result is '${{ needs.run-tests.result }}'; failing explicitly."
exit 1
- name: ✅ tests succeeded
if: needs.run-tests.result == 'success'
run: echo "All tests completed successfully in job 'run-tests'."
@@ -1,22 +0,0 @@
name: Combine Dependabot PRs
on:
schedule:
- cron: "0 1 * * 3" # Wednesday at 01:00
workflow_dispatch: # allows you to manually trigger the workflow
permissions:
contents: write
pull-requests: write
checks: read
jobs:
combine-prs:
name: Combine
runs-on: ubuntu-latest
steps:
- name: combine-prs
id: combine-prs
uses: github/combine-prs@2909f404763c3177a456e052bdb7f2e85d3a7cb3 # v5.2.0
with:
labels: combined-pr
+27
View File
@@ -0,0 +1,27 @@
name: PR Conflict Labeler
on:
# So that PRs touching the same files as the push are updated
push:
branches:
- main
- develop
# So that the `dirtyLabel` is removed if conflicts are resolved
# We recommend `pull_request_target` so that github secrets are available.
# In `pull_request` we wouldn't be able to change labels of fork PRs
pull_request_target:
types: [synchronize]
permissions:
pull-requests: write
issues: write
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: check if prs are dirty
uses: eps1lon/actions-label-merge-conflict@v3
with:
dirtyLabel: "has conflicts"
removeOnDirtyLabel: "has conflicts"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
+14 -26
View File
@@ -20,53 +20,41 @@ permissions:
pull-requests: write # Required for PR comments
jobs:
deploy:
docs-build-deploy:
name: Publish Docs
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.10"]
steps:
- name: 📥 Checkout the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: 🐍 Install uv and set Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: 🐍 Install uv and set Python
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
python-version: ${{ matrix.python-version }}
python-version: "3.10"
activate-environment: true
- name: 🔑 Create GitHub App token (mkdocs)
id: mkdocs_token
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
with:
app-id: ${{ secrets.MKDOCS_APP_ID }}
private-key: ${{ secrets.MKDOCS_PEM }}
owner: roboflow
repositories: mkdocs-material-insiders
- name: 🏗️ Install dependencies
run: |
uv pip install -r pyproject.toml --group 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]"
run: uv pip install -r pyproject.toml --group docs
- name: ⚙️ Configure git for github-actions
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
- name: 🚀 Deploy Development Docs
if: (github.event_name == 'push' && github.ref == 'refs/heads/develop') || github.event_name == 'workflow_dispatch'
env:
MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.GITHUB_TOKEN }}
run: |
MKDOCS_GIT_COMMITTERS_APIKEY=${{ secrets.GITHUB_TOKEN }} uv run mike deploy --push develop
uv run mike deploy --push develop
- name: 🚀 Deploy Release Docs
if: github.event_name == 'release' && github.event.action == 'published'
env:
MKDOCS_GIT_COMMITTERS_APIKEY: ${{ secrets.GITHUB_TOKEN }}
run: |
latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
MKDOCS_GIT_COMMITTERS_APIKEY=${{ secrets.GITHUB_TOKEN }} uv run mike deploy --push --update-aliases $latest_tag latest
uv run mike deploy --push --update-aliases $latest_tag latest
+18 -17
View File
@@ -7,12 +7,23 @@ on:
- "[0-9]+.[0-9]+[0-9]+.[0-9]+b[0-9]"
- "[0-9]+.[0-9]+[0-9]+.[0-9]+rc[0-9]"
workflow_dispatch:
pull_request:
branches: [main, develop]
paths:
- '.github/workflows/build-package.yml'
- '.github/workflows/publish-pre-release.yml'
permissions: {} # Explicitly remove all permissions by default
jobs:
build:
permissions:
contents: read
uses: ./.github/workflows/build-package.yml
publish-pre-release:
name: Publish Pre-release Package
needs: build
runs-on: ubuntu-latest
environment:
name: test
@@ -21,27 +32,17 @@ jobs:
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@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: 📥 Download distribution artifacts
uses: actions/download-artifact@v4
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --group build
uv build
uv run twine check --strict dist/*
name: dist
path: dist/
- name: List distribution artifacts
run: ls -lh dist/
- name: 🚀 Publish to PyPi
if: github.event_name != 'pull_request'
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
attestations: true
+18 -17
View File
@@ -5,12 +5,23 @@ on:
tags:
- "[0-9]+.[0-9]+[0-9]+.[0-9]"
workflow_dispatch:
pull_request:
branches: [main, develop]
paths:
- '.github/workflows/build-package.yml'
- '.github/workflows/publish-release.yml'
permissions: {} # Explicitly remove all permissions by default
jobs:
build:
permissions:
contents: read
uses: ./.github/workflows/build-package.yml
publish-release:
name: Publish Release Package
needs: build
runs-on: ubuntu-latest
environment:
name: release
@@ -19,27 +30,17 @@ jobs:
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@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: 📥 Download distribution artifacts
uses: actions/download-artifact@v4
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --group build
uv build
uv run twine check --strict dist/*
name: dist
path: dist/
- name: List distribution artifacts
run: ls -lh dist/
- name: 🚀 Publish to PyPi
if: github.event_name != 'pull_request'
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
attestations: true
+18 -17
View File
@@ -2,12 +2,23 @@ name: Publish Supervision Releases to TestPyPI
on:
workflow_dispatch:
pull_request:
branches: [main, develop]
paths:
- '.github/workflows/build-package.yml'
- '.github/workflows/publish-testpypi.yml'
permissions: {} # Explicitly remove all permissions by default
jobs:
build:
permissions:
contents: read
uses: ./.github/workflows/build-package.yml
publish-testpypi:
name: Publish Release Package
needs: build
runs-on: ubuntu-latest
environment:
name: release
@@ -16,27 +27,17 @@ jobs:
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@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: 📥 Download distribution artifacts
uses: actions/download-artifact@v4
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
- name: 🏗️ Build source and wheel distributions
run: |
uv pip install -r pyproject.toml --group build
uv build
uv run twine check --strict dist/*
name: dist
path: dist/
- name: List distribution artifacts
run: ls -lh dist/
- name: 🚀 Publish to Test-PyPi
if: github.event_name != 'pull_request'
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
repository-url: https://test.pypi.org/legacy/
-35
View File
@@ -1,35 +0,0 @@
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.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@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
python-version: ${{ matrix.python-version }}
activate-environment: true
- name: 🚀 Install Packages
run: uv pip install -r pyproject.toml --group dev --group 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
+20 -26
View File
@@ -1,4 +1,3 @@
ci:
autofix_prs: true
autoupdate_schedule: weekly
@@ -7,10 +6,9 @@ ci:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
exclude: test/.*\.py
- id: check-yaml
exclude: mkdocs.yml
- id: check-executables-have-shebangs
@@ -24,28 +22,32 @@ repos:
- id: end-of-file-fixer
- id: mixed-line-ending
- repo: https://github.com/PyCQA/bandit
rev: '1.8.6'
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.6.0
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.24.1
hooks:
- id: validate-pyproject
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.5
rev: v0.14.11
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-check
args: [--fix]
- id: ruff-format
types_or: [ python, pyi, jupyter ]
# - repo: https://github.com/executablebooks/mdformat
# rev: 0.7.17
# hooks:
# - id: mdformat
# additional_dependencies:
# - "mdformat-mkdocs[recommended]>=2.1.0"
# args: ["--number"]
- repo: https://github.com/executablebooks/mdformat
rev: 1.0.0
hooks:
- id: mdformat
additional_dependencies:
- "mdformat-mkdocs[recommended]>=2.1.0"
args: ["--number"]
exclude: ^docs/
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
@@ -53,11 +55,3 @@ repos:
- id: codespell
additional_dependencies:
- tomli
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
args: ["--py310-plus"]
additional_dependencies:
- tomli
+5 -5
View File
@@ -21,20 +21,20 @@ community include:
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
professional setting
## Enforcement Responsibilities
+88 -27
View File
@@ -83,18 +83,20 @@ git push -u origin <your_branch_name>
Use conventional commit messages to clearly describe your changes. The format is:
<type>\[optional scope\]: <description>
```
<type>[optional scope]: <description>
```
Common types include:
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- chore: Changes to the build process or auxiliary tools and libraries
- `feat`: A new feature
- `fix`: A bug fix
- `docs`: Documentation only changes
- `style`: Changes that do not affect the meaning of the code (white-space, formatting, etc)
- `refactor`: A code change that neither fixes a bug nor adds a feature
- `perf`: A code change that improves performance
- `test`: Adding missing tests or correcting existing tests
- `chore`: Changes to the build process or auxiliary tools and libraries
Then, go back to your fork of the `supervision` repository, click "Pull Requests", and click "New Pull Request".
@@ -116,7 +118,7 @@ When creating new functions, please ensure you have the following:
2. Unit tests for the function.
3. Examples in the documentation for the function.
4. Created an entry in our docs to autogenerate the documentation for the function.
5. Please share a Google Colab with minimal code to test new feature or reproduce PR whenever it is possible. Please ensure that Google Colab can be accessed without any issue.
5. Please share a Google Colab with minimal code to test a new feature or reproduce the issue whenever possible. Please ensure that Google Colab can be accessed without any restrictions.
When you submit your Pull Request, you will be asked to sign a Contributor License Agreement (CLA) by the `cla-assistant` GitHub bot. We can only respond to PRs from contributors who have signed the project CLA.
@@ -128,7 +130,7 @@ PRs must pass all tests and linting requirements before they can be merged.
Before starting your work on the project, set up your development environment:
1. Clone your fork of the project (recommended to use shallow clone of develop branch):
1. **Clone your fork of the project:**
**Option A: Recommended for most contributors (shallow clone of develop branch):**
@@ -139,33 +141,50 @@ Before starting your work on the project, set up your development environment:
Replace `YOUR_USERNAME` with your GitHub username.
> Note: Using `--depth 1` creates a shallow clone with minimal history and `-b develop` ensures you start with the development branch. This significantly reduces download size while providing everything needed to contribute.
> **Note**: Using `--depth 1` creates a shallow clone with minimal history and `-b develop` ensures you start with the development branch. This significantly reduces download size while providing everything needed to contribute.
**Option B: Full repository clone (if you need complete history):**
```bash
git clone https://github.com/YOUR_USERNAME/supervision.git
cd supervision
git checkout develop
```
2. Create and activate a virtual environment:
2. **Set up the upstream remote:**
```bash
git remote add upstream https://github.com/roboflow/supervision.git
git fetch upstream
```
3. **Create and activate a virtual environment:**
**On Linux/macOS:**
```bash
python3 -m venv .venv
source .venv/bin/activate
```
3. Install `uv`:
**On Windows:**
```cmd
python -m venv .venv
.venv\Scripts\activate
```
4. **Install `uv`:**
Follow the instructions on the [uv installation page](https://docs.astral.sh/uv/getting-started/installation/).
4. Install project dependencies:
5. **Install project dependencies:**
```bash
uv pip install -r pyproject.toml --extra dev --extra docs --extra metrics
uv pip install -r pyproject.toml --group dev --group docs --extra metrics
```
5. Run pytest to verify the setup:
6. **Verify the setup:**
```bash
uv run pytest
@@ -181,13 +200,29 @@ 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: `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
1. **Install pre-commit** (already included if you followed the installation steps above):
2. Once pre-commit is installed, navigate to the project's root directory.
```bash
uv sync --group dev
```
3. Run the command `pre-commit run --all-files`. This will execute the pre-commit hooks configured for this project against the modified files. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the pre-commit command until all issues are resolved.
2. **Navigate to the project's root directory** (if not already there).
4. You can also install pre-commit as a git hook by executing `pre-commit install`. Every time you do a `git commit` pre-commit run automatically for you.
3. **Run pre-commit checks**:
```bash
uv run pre-commit run --all-files
```
This will execute the pre-commit hooks configured for this project. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the command until all issues are resolved.
4. **Install pre-commit as a git hook** (optional but recommended):
```bash
uv run pre-commit install
```
This will automatically run pre-commit checks every time you make a `git commit`.
### Docstrings
@@ -197,13 +232,27 @@ All new functions and classes in `supervision` should include docstrings. This i
### Type checking
So far, **there is no type checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4).
Currently, there is no systematic type checking with mypy implemented in the project. This is a known limitation that may be addressed in future updates.
## 📝 Documentation
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 `uv pip install -r pyproject.toml --extra dev --extra docs`. Then, run `mkdocs serve` to start the documentation server.
To run the documentation locally:
1. **Install documentation dependencies** (if not already installed):
```bash
uv sync --group docs
```
2. **Start the documentation server**:
```bash
uv run mkdocs serve
```
3. **Access the documentation** at `http://127.0.0.1:8000` in your browser.
You can learn more about mkdocs on the [mkdocs website](https://www.mkdocs.org/).
@@ -216,14 +265,26 @@ submit a PR with your example. Here are some guidelines for submitting a new exa
- Create a new notebook in the [`docs/notebooks`](https://github.com/roboflow/supervision/tree/develop/docs/notebooks) folder.
- Add a link to the new notebook in [`docs/theme/cookbooks.html`](https://github.com/roboflow/supervision/blob/develop/docs/theme/cookbooks.html). Make sure to add the path to the new notebook, as well as a title, labels, author and supervision version.
- Use the [Count Objects Crossing the Line](https://supervision.roboflow.com/develop/notebooks/count-objects-crossing-the-line/) example as a template for your new example.
- Freeze the version of `supervision` you are using.
- Place an appropriate Open in Colab button at the top of the notebook. You can find an example of such a button in the aforementioned `Count Objects Crossing the Line` cookbook.
- Notebook should be self-contained. If you rely on external data ( videos, images, etc.) or libraries, include download and installation commands in the notebook.
- Pin the version of `supervision` you are using in the notebook.
- Place an appropriate "Open in Colab" button at the top of the notebook. You can find an example of such a button in the aforementioned `Count Objects Crossing the Line` cookbook.
- **Notebook should be self-contained**. If you rely on external data (videos, images, etc.) or libraries, include download and installation commands in the notebook.
- Annotate the code with appropriate comments, including links to the documentation describing each of the tools you have used.
## 🧪 Tests
[`pytests`](https://docs.pytest.org/en/7.1.x/) is used to run our tests.
[`pytest`](https://docs.pytest.org/en/7.1.x/) is used to run our tests.
To run tests:
```bash
uv run pytest
```
To run tests with coverage:
```bash
uv run pytest --cov=supervision
```
## 📄 License
+81 -82
View File
@@ -24,7 +24,7 @@
[![discord](https://img.shields.io/discord/1159501506232451173?logo=discord&label=discord&labelColor=fff&color=5865f2&link=https%3A%2F%2Fdiscord.gg%2FGbfgXGJ8Bk)](https://discord.gg/GbfgXGJ8Bk)
[![built-with-material-for-mkdocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?logo=MaterialForMkDocs&logoColor=white)](https://squidfunk.github.io/mkdocs-material/)
<div align="center">
<div align="center">
<a href="https://trendshift.io/repositories/124" target="_blank"><img src="https://trendshift.io/api/badge/repositories/124" alt="roboflow%2Fsupervision | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>
@@ -70,21 +70,21 @@ len(detections)
- inference
Running with [Inference](https://github.com/roboflow/inference) requires a [Roboflow API KEY](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key).
Running with [Inference](https://github.com/roboflow/inference) requires a [Roboflow API KEY](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key).
```python
import cv2
import supervision as sv
from inference import get_model
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(...)
model = get_model(model_id="yolov8s-640", api_key=<ROBOFLOW API KEY>)
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
image = cv2.imread(...)
model = get_model(model_id="yolov8s-640", api_key="ROBOFLOW_API_KEY")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
len(detections)
# 5
```
len(detections)
# 5
```
</details>
@@ -100,9 +100,7 @@ image = cv2.imread(...)
detections = sv.Detections(...)
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections)
annotated_frame = box_annotator.annotate(scene=image.copy(), detections=detections)
```
https://github.com/roboflow/supervision/assets/26109316/691e219c-0565-4403-9218-ab5644f39bce
@@ -115,8 +113,8 @@ Supervision provides a set of [utils](https://supervision.roboflow.com/latest/da
import supervision as sv
from roboflow import Roboflow
project = Roboflow().workspace(<WORKSPACE_ID>).project(<PROJECT_ID>)
dataset = project.version(<PROJECT_VERSION>).download("coco")
project = Roboflow().workspace("WORKSPACE_ID").project("PROJECT_ID")
dataset = project.version("PROJECT_VERSION").download("coco")
ds = sv.DetectionDataset.from_coco(
images_directory_path=f"{dataset.location}/train",
@@ -124,10 +122,11 @@ ds = sv.DetectionDataset.from_coco(
)
path, image, annotation = ds[0]
# loads image on demand
# loads image on demand
for path, image, annotation in ds:
# loads image on demand
pass
```
<details close>
@@ -135,88 +134,88 @@ for path, image, annotation in ds:
- load
```python
dataset = sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...
)
```python
dataset = sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
)
dataset = sv.DetectionDataset.from_pascal_voc(
images_directory_path=...,
annotations_directory_path=...
)
dataset = sv.DetectionDataset.from_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
dataset = sv.DetectionDataset.from_coco(
images_directory_path=...,
annotations_path=...
)
```
dataset = sv.DetectionDataset.from_coco(
images_directory_path=...,
annotations_path=...,
)
```
- split
```python
train_dataset, test_dataset = dataset.split(split_ratio=0.7)
test_dataset, valid_dataset = test_dataset.split(split_ratio=0.5)
```python
train_dataset, test_dataset = dataset.split(split_ratio=0.7)
test_dataset, valid_dataset = test_dataset.split(split_ratio=0.5)
len(train_dataset), len(test_dataset), len(valid_dataset)
# (700, 150, 150)
```
len(train_dataset), len(test_dataset), len(valid_dataset)
# (700, 150, 150)
```
- merge
```python
ds_1 = sv.DetectionDataset(...)
len(ds_1)
# 100
ds_1.classes
# ['dog', 'person']
```python
ds_1 = sv.DetectionDataset(...)
len(ds_1)
# 100
ds_1.classes
# ['dog', 'person']
ds_2 = sv.DetectionDataset(...)
len(ds_2)
# 200
ds_2.classes
# ['cat']
ds_2 = sv.DetectionDataset(...)
len(ds_2)
# 200
ds_2.classes
# ['cat']
ds_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
ds_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
- save
```python
dataset.as_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...
)
```python
dataset.as_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
)
dataset.as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...
)
dataset.as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
dataset.as_coco(
images_directory_path=...,
annotations_path=...
)
```
dataset.as_coco(
images_directory_path=...,
annotations_path=...,
)
```
- convert
```python
sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...
).as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...
)
```
```python
sv.DetectionDataset.from_yolo(
images_directory_path=...,
annotations_directory_path=...,
data_yaml_path=...,
).as_pascal_voc(
images_directory_path=...,
annotations_directory_path=...,
)
```
</details>
+2 -2
View File
@@ -21,7 +21,7 @@ https://github.com/roboflow/supervision/assets/26109316/f84db7b5-79e2-4142-a1da-
cd supervision/examples/count_people_in_zone
```
- setup python environment and activate it \[optional\]
- setup python environment and activate it [optional]
```bash
python3 -m venv venv
@@ -110,7 +110,7 @@ https://github.com/roboflow/supervision/assets/26109316/f84db7b5-79e2-4142-a1da-
```bash
python inference_example.py \
--roboflow_api_key <ROBOFLOW API KEY> \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--zone_configuration_path data/multi-zone-config.json \
--source_video_path data/market-square.mp4 \
--confidence_threshold 0.3 \
+1 -1
View File
@@ -15,7 +15,7 @@ supervision package for multiple tasks such as drawing heatmap annotations, trac
cd supervision/examples/heatmap_and_track
```
- setup python environment and activate it \[optional\]
- setup python environment and activate it [optional]
```bash
python3 -m venv venv
+3 -3
View File
@@ -11,7 +11,7 @@ supervision package for multiple tasks such as tracking, annotations, etc.
https://github.com/roboflow/supervision/assets/26109316/d50118c1-2ae4-458d-915a-5d860fd36f71
> \[!IMPORTANT\]
> [!IMPORTANT]
> Adjust the [`SOURCE`](https://github.com/roboflow/supervision/blob/e32b05a636dab2ea1f39299e529c4b22b8baa8da/examples/speed_estimation/ultralytics_example.py#L10)
> and [`TARGET`](https://github.com/roboflow/supervision/blob/e32b05a636dab2ea1f39299e529c4b22b8baa8da/examples/speed_estimation/ultralytics_example.py#L15)
> configuration if you plan to run a speed estimation script on your video file. Those must be adjusted separately for each camera view. You can learn more
@@ -26,7 +26,7 @@ https://github.com/roboflow/supervision/assets/26109316/d50118c1-2ae4-458d-915a-
cd supervision/examples/speed_estimation
```
- setup python environment and activate it \[optional\]
- setup python environment and activate it [optional]
```bash
python3.10 -m venv venv
@@ -90,7 +90,7 @@ https://github.com/roboflow/supervision/assets/26109316/d50118c1-2ae4-458d-915a-
```bash
python inference_example.py \
--roboflow_api_key <ROBOFLOW API KEY> \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path data/vehicles.mp4 \
--target_video_path data/vehicles-result.mp4 \
--confidence_threshold 0.3 \
+1 -1
View File
@@ -20,7 +20,7 @@ https://github.com/roboflow/supervision/assets/26109316/d051cc8a-dd15-41d4-aa36-
cd supervision/examples/time_in_zone
```
- setup python environment and activate it \[optional\]
- setup python environment and activate it [optional]
```bash
python3 -m venv venv
@@ -77,7 +77,7 @@ def run_command_in_thread(command: list) -> Thread:
def run_command(command: list) -> int:
process = subprocess.run(command)
process = subprocess.run(command) # noqa: S603 # TODO: Validate command input to prevent execution of untrusted input
return process.returncode
+2 -2
View File
@@ -14,7 +14,7 @@ detection and Supervision for tracking and annotation.
cd supervision/examples/tracking
```
- setup python environment and activate it \[optional\]
- setup python environment and activate it [optional]
```bash
python3 -m venv venv
@@ -79,7 +79,7 @@ detection and Supervision for tracking and annotation.
```bash
python inference_example.py \
--roboflow_api_key <ROBOFLOW API KEY> \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path input.mp4 \
--target_video_path tracking_result.mp4
```
+2 -2
View File
@@ -17,7 +17,7 @@ https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-
cd supervision/examples/traffic_analysis
```
- setup python environment and activate it \[optional\]
- setup python environment and activate it [optional]
```bash
python3 -m venv venv
@@ -101,7 +101,7 @@ https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-
```bash
python inference_example.py \
--roboflow_api_key <ROBOFLOW API KEY> \
--roboflow_api_key "ROBOFLOW_API_KEY" \
--source_video_path data/traffic_analysis.mov \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
+151 -196
View File
@@ -1,219 +1,184 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = [ "setuptools>=61" ]
[project]
name = "supervision"
version = "0.28.0rc0"
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
license = { text = "MIT" }
version = "0.27.0"
readme = "README.md"
requires-python = ">=3.9"
authors = [
{ name = "Piotr Skalski", email = "piotr.skalski92@gmail.com" }
]
maintainers = [
{ name = "Piotr Skalski", email = "piotr.skalski92@gmail.com" },
]
keywords = [
"machine-learning",
"deep-learning",
"vision",
"ML",
"DL",
"AI",
"Roboflow",
"AI",
"deep-learning",
"DL",
"machine-learning",
"ML",
"Roboflow",
"vision",
]
license = { text = "MIT" }
maintainers = [
{ name = "Piotr Skalski", email = "piotr@roboflow.com" },
]
authors = [
{ name = "Roboflow et al.", email = "develop@roboflow.com" },
]
requires-python = ">=3.9"
classifiers = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development',
'Topic :: Scientific/Engineering',
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
'Typing :: Typed',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS',
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Multimedia :: Graphics",
"Topic :: Multimedia :: Video",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
"Topic :: Software Development",
"Typing :: Typed",
]
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"
"defusedxml>=0.7.1",
"matplotlib>=3.6",
"numpy>=1.21.2",
"opencv-python>=4.5.5.64",
"pillow>=9.4",
"pyyaml>=5.3",
"requests>=2.26",
"scipy>=1.10",
"tqdm>=4.62.3",
]
[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",
optional-dependencies.metrics = [
"pandas>=2",
]
urls.Documentation = "https://supervision.roboflow.com/latest/"
urls.Homepage = "https://github.com/roboflow/supervision"
urls.Repository = "https://github.com/roboflow/supervision"
[dependency-groups]
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",
"pre-commit>=3.8.0"
"docutils!=0.21",
"ipywidgets>=8.1.1",
"jupytext>=1.16.1",
"nbconvert>=7.14.2",
"notebook>=6.5.3,<8",
"pre-commit>=3.8",
"pytest>=7.2.2,<9",
"pytest-cov>=4,<8",
"tox>=4.11.4",
]
docs = [
"mkdocs-material[imaging]>=9.5.5",
"mkdocstrings>=0.25.2,<0.31.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.9' and python_version < '4'",
"mkdocs-git-revision-date-localized-plugin>=1.2.4"
"mike>=2",
"mkdocs-git-committers-plugin-2>=2.4.1; python_version>='3.9' and python_version<'4'",
"mkdocs-git-revision-date-localized-plugin>=1.2.4",
"mkdocs-jupyter>=0.24.3",
"mkdocs-material[imaging]>=9.7",
"mkdocstrings>=0.25.2,<0.31",
"mkdocstrings-python>=1.10.9,<2", # todo: breaking changes in 2.x
]
build = [
"twine>=5.1.1,<7.0.0",
"wheel>=0.40,<0.46",
"build>=0.10,<1.4"
"build>=0.10,<1.5",
"twine>=5.1.1,<7",
"wheel>=0.40,<0.46",
]
[tool.bandit]
target = ["test", "supervision"]
tests = ["B201", "B301", "B318", "B314", "B303", "B413", "B412"]
[tool.setuptools]
include-package-data = false
[tool.autoflake]
check = true
imports = ["cv2", "supervision"]
[tool.setuptools.packages.find]
include = [ "supervision*" ]
exclude = [ "docs*", "test*", "examples*" ]
[tool.setuptools.package-data]
supervision = [ "py.typed" ]
[tool.ruff]
target-version = "py39"
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
"yarn-error.log",
"yarn.lock",
"docs",
]
line-length = 88
indent-width = 4
[tool.ruff.lint]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F", "I", "A", "Q", "W", "RUF", "UP"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"I",
"N",
"Q",
"S",
"T",
"W",
"ANN",
"ARG",
"BLE",
"COM",
"DJ",
"DTZ",
"EM",
"ERA",
"EXE",
"FBT",
"ICN",
"INP",
"ISC",
"NPY",
"PD",
"PGH",
"PIE",
"PL",
"PT",
"PTH",
"PYI",
"RET",
"RSE",
"RUF",
"SIM",
"SLF",
"TCH",
"TID",
"TRY",
"UP",
"YTT",
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"docs",
"node_modules",
"venv",
"yarn-error.log",
"yarn.lock",
]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
pylint.max-args = 20
[tool.ruff.lint.flake8-quotes]
inline-quotes = "double"
multiline-quotes = "double"
docstring-quotes = "double"
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402", "F401"]
[tool.ruff.lint.mccabe]
# Flag errors (`C901`) whenever the complexity level exceeds 5.
max-complexity = 20
[tool.ruff.lint.isort]
order-by-type = true
no-sections = false
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
format.indent-style = "space"
# Like Black, use double quotes for strings.
format.quote-style = "double"
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
format.line-ending = "auto"
# Like Black, respect magic trailing commas.
format.skip-magic-trailing-comma = false
# Enable linting rules for code style, imports, and best practices.
lint.select = [
"A", # flake8-builtins - https://docs.astral.sh/ruff/rules/#flake8-builtins-a
"E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e
"F", # Pyflakes - https://docs.astral.sh/ruff/rules/#pyflakes-f
"I", # isort - https://docs.astral.sh/ruff/rules/#isort-i
"PT", # pytest - https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
"Q", # flake8-quotes - https://docs.astral.sh/ruff/rules/#flake8-quotes-q
"RUF", # Ruff-specific rules - https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
"S", # bandit - https://docs.astral.sh/ruff/rules/#flake8-bandit-s
"UP", # pyupgrade - https://docs.astral.sh/ruff/rules/#pyupgrade-up
"W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#pycodestyle-w
]
lint.ignore = [ ]
lint.per-file-ignores."__init__.py" = [ "E402", "F401" ]
lint.per-file-ignores."supervision/**" = [
"S101", # TODO: Replace asserts with proper error handling
]
lint.per-file-ignores."test/**" = [
"PT011", # TODO: `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception
"S101", # Use of `assert` detected
]
lint.unfixable = [ ]
# Allow unused variables when underscore-prefixed.
lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
lint.flake8-quotes.docstring-quotes = "double"
lint.flake8-quotes.inline-quotes = "double"
lint.flake8-quotes.multiline-quotes = "double"
lint.isort.no-sections = false
lint.isort.order-by-type = true
# Flag errors (`C901`) whenever the complexity level exceeds 5.
lint.mccabe.max-complexity = 20
lint.pydocstyle.convention = "google"
lint.pylint.max-args = 20
[tool.codespell]
skip = "*.ipynb"
@@ -221,16 +186,6 @@ count = true
quiet-level = 3
ignore-words-list = "STrack,sTrack,strack"
[tool.setuptools]
include-package-data = false
[tool.setuptools.packages.find]
include = ["supervision*"]
exclude = ["docs*", "test*", "examples*"]
[tool.setuptools.package-data]
supervision = ["py.typed"]
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[tool.autoflake]
check = true
imports = [ "cv2", "supervision" ]
+3 -3
View File
@@ -764,7 +764,7 @@ class EllipseAnnotator(BaseAnnotator):
"""
assert isinstance(scene, np.ndarray)
for detection_idx in range(len(detections)):
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
x1, _y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
color = resolve_color(
color=self.color,
detections=detections,
@@ -1621,7 +1621,7 @@ class RichLabelAnnotator(_BaseLabelAnnotator):
)
# Move to the next line position
left, top, right, bottom = draw.textbbox((0, 0), line, font=self.font)
_left, top, _right, bottom = draw.textbbox((0, 0), line, font=self.font)
line_height = bottom - top
y_position += line_height + self.text_padding
@@ -1917,7 +1917,7 @@ class TraceAnnotator(BaseAnnotator):
if len(xy) > 3 and self.smooth:
x, y = xy[:, 0], xy[:, 1]
tck, u = splprep([x, y], s=20)
tck, _u = splprep([x, y], s=20)
x_new, y_new = splev(np.linspace(0, 1, 100), tck)
spline_points = np.stack([x_new, y_new], axis=1).astype(np.int32)
+8 -4
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
import os
from hashlib import new as hash_new
from hashlib import md5
from pathlib import Path
from shutil import copyfileobj
@@ -15,6 +15,9 @@ def is_md5_hash_matching(filename: str, original_md5_hash: str) -> bool:
"""
Check if the MD5 hash of a file matches the original hash.
Note: MD5 is used here for file integrity checking (detecting corruption),
not for cryptographic security purposes.
Parameters:
filename (str): The path to the file to be checked as a string.
original_md5_hash (str): The original MD5 hash to compare against.
@@ -27,8 +30,7 @@ def is_md5_hash_matching(filename: str, original_md5_hash: str) -> bool:
with open(filename, "rb") as file:
file_contents = file.read()
computed_md5_hash = hash_new(name="MD5")
computed_md5_hash.update(file_contents)
computed_md5_hash = md5(file_contents, usedforsecurity=False)
return computed_md5_hash.hexdigest() == original_md5_hash
@@ -57,7 +59,9 @@ def download_assets(asset_name: VideoAssets | str) -> str:
if not Path(filename).exists() and filename in VIDEO_ASSETS:
print(f"Downloading {filename} assets \n")
response = get(VIDEO_ASSETS[filename][0], stream=True, allow_redirects=True)
response = get(
VIDEO_ASSETS[filename][0], stream=True, allow_redirects=True, timeout=30
)
response.raise_for_status()
file_size = int(response.headers.get("Content-Length", 0))
+1 -1
View File
@@ -100,7 +100,7 @@ def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray:
A 2D NumPy array of shape (num_masks, 2), where each row contains the x and y
coordinates (in that order) of the centroid of the corresponding mask.
"""
num_masks, height, width = masks.shape
_num_masks, height, width = masks.shape
total_pixels = masks.sum(axis=(1, 2))
# offset for 1-based indexing
+1 -1
View File
@@ -575,7 +575,7 @@ def from_florence_2(
labels = np.array([result_string])
return xyxy, labels, None, None
assert False, f"Unimplemented task: {task}"
raise RuntimeError(f"Unimplemented task: {task}")
def from_google_gemini_2_0(
+1 -1
View File
@@ -247,7 +247,7 @@ class ByteTrack:
if strack_pool[i].state == TrackState.Tracked
]
dists = matching.iou_distance(r_tracked_stracks, detections_second)
matches, u_track, u_detection_second = matching.linear_assignment(
matches, u_track, _u_detection_second = matching.linear_assignment(
dists, thresh=0.5
)
for itracked, idet in matches:
+1 -1
View File
@@ -98,7 +98,7 @@ def plot_images_grid(
" or reduce the number of images."
)
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=size)
_fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=size)
for idx, ax in enumerate(axes.flat):
if idx < len(images):
+2 -2
View File
@@ -11,7 +11,7 @@ from test.test_utils import mock_detections
@pytest.mark.parametrize(
"detections, detection_idx, color_lookup, expected_result, exception",
("detections", "detection_idx", "color_lookup", "expected_result", "exception"),
[
(
mock_detections(
@@ -111,7 +111,7 @@ def test_resolve_color_idx(
@pytest.mark.parametrize(
"text, max_line_length, expected_result, exception",
("text", "max_line_length", "expected_result", "exception"),
[
(None, None, [""], DoesNotRaise()), # text is None
("", None, [""], DoesNotRaise()), # empty string
+1 -1
View File
@@ -9,7 +9,7 @@ from supervision.classification.core import Classifications
@pytest.mark.parametrize(
"class_id, confidence, k, expected_result, exception",
("class_id", "confidence", "k", "expected_result", "exception"),
[
(
np.array([0, 1, 2, 3, 4]),
+13 -7
View File
@@ -39,7 +39,7 @@ def mock_coco_annotation(
@pytest.mark.parametrize(
"coco_categories, expected_result, exception",
("coco_categories", "expected_result", "exception"),
[
([], [], DoesNotRaise()), # empty coco categories
(
@@ -87,7 +87,7 @@ def test_coco_categories_to_classes(
@pytest.mark.parametrize(
"classes, exception",
("classes", "exception"),
[
([], DoesNotRaise()), # empty classes
(["baseball cap"], DoesNotRaise()), # single class
@@ -104,7 +104,7 @@ def test_classes_to_coco_categories_and_back_to_classes(
@pytest.mark.parametrize(
"coco_annotations, expected_result, exception",
("coco_annotations", "expected_result", "exception"),
[
([], {}, DoesNotRaise()), # empty coco annotations
(
@@ -163,8 +163,14 @@ def test_group_coco_annotations_by_image_id(
@pytest.mark.parametrize(
"image_annotations, resolution_wh, with_masks, use_iscrowd, "
"expected_result, exception",
(
"image_annotations",
"resolution_wh",
"with_masks",
"use_iscrowd",
"expected_result",
"exception",
),
[
(
[],
@@ -601,7 +607,7 @@ def test_coco_annotations_to_detections(
@pytest.mark.parametrize(
"coco_categories, target_classes, expected_result, exception",
("coco_categories", "target_classes", "expected_result", "exception"),
[
([], [], {}, DoesNotRaise()), # empty coco categories
(
@@ -660,7 +666,7 @@ def test_build_coco_class_index_mapping(
@pytest.mark.parametrize(
"detections, image_id, annotation_id, expected_result, exception",
("detections", "image_id", "annotation_id", "expected_result", "exception"),
[
(
Detections(
+10 -3
View File
@@ -31,7 +31,7 @@ def are_xml_elements_equal(elem1, elem2):
@pytest.mark.parametrize(
"xyxy, name, polygon, expected_result, exception",
("xyxy", "name", "polygon", "expected_result", "exception"),
[
(
np.array([0, 0, 10, 10]),
@@ -71,7 +71,7 @@ def test_object_to_pascal_voc(
@pytest.mark.parametrize(
"polygon_element, expected_result, exception",
("polygon_element", "expected_result", "exception"),
[
(
ElementTree.fromstring(
@@ -116,7 +116,14 @@ NO_DETECTIONS = """<annotation></annotation>"""
@pytest.mark.parametrize(
"xml_string, classes, resolution_wh, force_masks, expected_result, exception",
(
"xml_string",
"classes",
"resolution_wh",
"force_masks",
"expected_result",
"exception",
),
[
(
ONE_CLASS_ONE_BBOX,
+4 -5
View File
@@ -31,7 +31,7 @@ def _arrays_almost_equal(
@pytest.mark.parametrize(
"lines, expected_result, exception",
("lines", "expected_result", "exception"),
[
([], False, DoesNotRaise()), # empty yolo annotation file
(
@@ -44,7 +44,6 @@ def _arrays_almost_equal(
False,
DoesNotRaise(),
), # yolo annotation file with two lines with box
(["0 0.5 0.5 0.2 0.2"], False, DoesNotRaise()),
(
["0 0.4 0.4 0.6 0.4 0.6 0.6 0.4 0.6"],
True,
@@ -66,7 +65,7 @@ def test_with_mask(
@pytest.mark.parametrize(
"lines, resolution_wh, with_masks, expected_result, exception",
("lines", "resolution_wh", "with_masks", "expected_result", "exception"),
[
(
[],
@@ -190,7 +189,7 @@ def test_yolo_annotations_to_detections(
@pytest.mark.parametrize(
"image_name, expected_result, exception",
("image_name", "expected_result", "exception"),
[
("image.png", "image.txt", DoesNotRaise()), # simple png image
("image.jpeg", "image.txt", DoesNotRaise()), # simple jpeg image
@@ -211,7 +210,7 @@ def test_image_name_to_annotation_name(
@pytest.mark.parametrize(
"xyxy, class_id, image_shape, polygon, expected_result, exception",
("xyxy", "class_id", "image_shape", "polygon", "expected_result", "exception"),
[
(
np.array([100, 100, 200, 200], dtype=np.float32),
+1 -1
View File
@@ -9,7 +9,7 @@ from test.test_utils import mock_detections
@pytest.mark.parametrize(
"dataset_list, expected_result, exception",
("dataset_list", "expected_result", "exception"),
[
(
[],
+6 -6
View File
@@ -22,7 +22,7 @@ T = TypeVar("T")
@pytest.mark.parametrize(
"data, train_ratio, random_state, shuffle, expected_result, exception",
("data", "train_ratio", "random_state", "shuffle", "expected_result", "exception"),
[
([], 0.5, None, False, ([], []), DoesNotRaise()), # empty data
(
@@ -94,7 +94,7 @@ def test_train_test_split(
@pytest.mark.parametrize(
"class_lists, expected_result, exception",
("class_lists", "expected_result", "exception"),
[
([], [], DoesNotRaise()), # empty class lists
(
@@ -128,7 +128,7 @@ def test_merge_class_maps(
@pytest.mark.parametrize(
"source_classes, target_classes, expected_result, exception",
("source_classes", "target_classes", "expected_result", "exception"),
[
([], [], {}, DoesNotRaise()), # empty class lists
([], ["dog", "person"], {}, DoesNotRaise()), # empty source class list
@@ -178,7 +178,7 @@ def test_build_class_index_mapping(
@pytest.mark.parametrize(
"source_to_target_mapping, detections, expected_result, exception",
("source_to_target_mapping", "detections", "expected_result", "exception"),
[
(
{},
@@ -238,7 +238,7 @@ def test_map_detections_class_id(
@pytest.mark.parametrize(
"mask, expected_rle, exception",
("mask", "expected_rle", "exception"),
[
(
np.zeros((3, 3)).astype(bool),
@@ -297,7 +297,7 @@ def test_mask_to_rle(
@pytest.mark.parametrize(
"rle, resolution_wh, expected_mask, exception",
("rle", "resolution_wh", "expected_mask", "exception"),
[
(
np.array([9]),
+5 -5
View File
@@ -130,7 +130,7 @@ TEST_DET_DIFFERENT_METADATA = Detections(
@pytest.mark.parametrize(
"detections, index, expected_result, exception",
("detections", "index", "expected_result", "exception"),
[
(
DETECTIONS,
@@ -244,7 +244,7 @@ def test_getitem(
@pytest.mark.parametrize(
"detections_list, expected_result, exception",
("detections_list", "expected_result", "exception"),
[
([], Detections.empty(), DoesNotRaise()), # empty detections list
(
@@ -516,7 +516,7 @@ def test_merge(
@pytest.mark.parametrize(
"detections, anchor, expected_result, exception",
("detections", "anchor", "expected_result", "exception"),
[
(
Detections.empty(),
@@ -598,7 +598,7 @@ def test_get_anchor_coordinates(
@pytest.mark.parametrize(
"detections_a, detections_b, expected_result",
("detections_a", "detections_b", "expected_result"),
[
(
Detections.empty(),
@@ -649,7 +649,7 @@ def test_equal(
@pytest.mark.parametrize(
"detection_1, detection_2, expected_result, exception",
("detection_1", "detection_2", "expected_result", "exception"),
[
(
mock_detections(
+16 -6
View File
@@ -9,9 +9,14 @@ from test.test_utils import mock_detections
@pytest.mark.parametrize(
"detections, custom_data, "
"second_detections, second_custom_data, "
"file_name, expected_result",
(
"detections",
"custom_data",
"second_detections",
"second_custom_data",
"file_name",
"expected_result",
),
[
(
mock_detections(
@@ -206,9 +211,14 @@ def test_csv_sink(
@pytest.mark.parametrize(
"detections, custom_data, "
"second_detections, second_custom_data, "
"file_name, expected_result",
(
"detections",
"custom_data",
"second_detections",
"second_custom_data",
"file_name",
"expected_result",
),
[
(
mock_detections(
+8 -3
View File
@@ -9,9 +9,14 @@ from test.test_utils import mock_detections
@pytest.mark.parametrize(
"detections, custom_data, "
"second_detections, second_custom_data, "
"file_name, expected_result",
(
"detections",
"custom_data",
"second_detections",
"second_custom_data",
"file_name",
"expected_result",
),
[
(
mock_detections(
+36 -11
View File
@@ -10,7 +10,7 @@ from test.test_utils import mock_detections
@pytest.mark.parametrize(
"vector, expected_result, exception",
("vector", "expected_result", "exception"),
[
(
Vector(start=Point(x=0.0, y=0.0), end=Point(x=0.0, y=0.0)),
@@ -75,7 +75,7 @@ def test_calculate_region_of_interest_limits(
@pytest.mark.parametrize(
"vector, xyxy_sequence, expected_crossed_in, expected_crossed_out",
("vector", "xyxy_sequence", "expected_crossed_in", "expected_crossed_out"),
[
( # Vertical line, simple crossing
Vector(Point(0, 0), Point(0, 10)),
@@ -260,8 +260,13 @@ def test_line_zone_one_detection_default_anchors(
@pytest.mark.parametrize(
"vector, xyxy_sequence, triggering_anchors, expected_crossed_in, "
"expected_crossed_out",
(
"vector",
"xyxy_sequence",
"triggering_anchors",
"expected_crossed_in",
"expected_crossed_out",
),
[
( # Scrape line, left side, corner anchors
Vector(Point(0, 0), Point(10, 0)),
@@ -425,8 +430,14 @@ def test_line_zone_one_detection(
@pytest.mark.parametrize(
"vector, xyxy_sequence, anchors, expected_crossed_in, "
"expected_crossed_out, exception",
(
"vector",
"xyxy_sequence",
"anchors",
"expected_crossed_in",
"expected_crossed_out",
"exception",
),
[
( # One stays, one crosses
Vector(Point(0, 0), Point(10, 0)),
@@ -494,8 +505,14 @@ def test_line_zone_multiple_detections(
@pytest.mark.parametrize(
"vector, xyxy_sequence, triggering_anchors, minimum_crossing_threshold, "
"expected_crossed_in, expected_crossed_out",
(
"vector",
"xyxy_sequence",
"triggering_anchors",
"minimum_crossing_threshold",
"expected_crossed_in",
"expected_crossed_out",
),
[
( # Detection lingers around line, all crosses counted
Vector(Point(0, 0), Point(10, 0)),
@@ -610,9 +627,17 @@ def test_line_zone_one_detection_long_horizon(
@pytest.mark.parametrize(
"vector, xyxy_sequence, anchors, minimum_crossing_threshold, "
"expected_crossed_in, expected_crossed_out, expected_count_in, "
"expected_count_out, exception",
(
"vector",
"xyxy_sequence",
"anchors",
"minimum_crossing_threshold",
"expected_crossed_in",
"expected_crossed_out",
"expected_count_in",
"expected_count_out",
"exception",
),
[
( # One stays, one crosses, one disappears before crossing
Vector(Point(0, 0), Point(10, 0)),
@@ -22,7 +22,7 @@ ANNOTATED_SCENE_0_5_OPACITY = sv.draw_filled_polygon(
@pytest.mark.parametrize(
"scene, polygon_zone_annotator, expected_results",
("scene", "polygon_zone_annotator", "expected_results"),
[
(
SCENE,
+2 -2
View File
@@ -29,7 +29,7 @@ POLYGON = np.array([[100, 100], [200, 100], [200, 200], [100, 200]])
@pytest.mark.parametrize(
"detections, polygon_zone, expected_results, exception",
("detections", "polygon_zone", "expected_results", "exception"),
[
(
DETECTIONS,
@@ -90,7 +90,7 @@ def test_polygon_zone_trigger(
@pytest.mark.parametrize(
"polygon, triggering_anchors, exception",
("polygon", "triggering_anchors", "exception"),
[
(POLYGON, [sv.Position.CENTER], DoesNotRaise()),
(
+7 -39
View File
@@ -20,7 +20,7 @@ from supervision.detection.vlm import (
@pytest.mark.parametrize(
"exception, result, resolution_wh, classes, expected_results",
("exception", "result", "resolution_wh", "classes", "expected_results"),
[
(
does_not_raise(),
@@ -202,7 +202,7 @@ def test_from_paligemma(
@pytest.mark.parametrize(
"exception, result, input_wh, resolution_wh, classes, expected_results",
("exception", "result", "input_wh", "resolution_wh", "classes", "expected_results"),
[
(
does_not_raise(),
@@ -405,7 +405,7 @@ def test_from_qwen_2_5_vl(
@pytest.mark.parametrize(
"exception, result, resolution_wh, classes, expected_results",
("exception", "result", "resolution_wh", "classes", "expected_results"),
[
(
does_not_raise(),
@@ -544,7 +544,7 @@ def test_from_google_gemini(
@pytest.mark.parametrize(
"exception, result, resolution_wh, expected_results",
("exception", "result", "resolution_wh", "expected_results"),
[
(
does_not_raise(),
@@ -644,7 +644,7 @@ def test_from_moondream(
@pytest.mark.parametrize(
"florence_result, resolution_wh, expected_results, exception",
("florence_result", "resolution_wh", "expected_results", "exception"),
[
( # Object detection: empty
{"<OD>": {"bboxes": [], "labels": []}},
@@ -792,38 +792,6 @@ def test_from_moondream(
),
DoesNotRaise(),
),
( # Referring Expression Segmentation
{
"<REFERRING_EXPRESSION_SEGMENTATION>": {
"polygons": [[[1, 1, 2, 1, 2, 2, 1, 2]]],
"labels": [""],
}
},
(10, 10),
(
np.array([[1.0, 1.0, 2.0, 2.0]], dtype=np.float32),
None,
np.array(
[
[
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]
],
dtype=bool,
),
None,
),
DoesNotRaise(),
),
( # OCR: unsupported
{"<OCR>": "A"},
(10, 10),
@@ -928,7 +896,7 @@ def test_florence_2(
@pytest.mark.parametrize(
"exception, result, resolution_wh, classes, expected_results",
("exception", "result", "resolution_wh", "classes", "expected_results"),
[
(
does_not_raise(),
@@ -1165,7 +1133,7 @@ def test_from_google_gemini_2_5(
@pytest.mark.parametrize(
"exception, result, resolution_wh, classes, expected_detections",
("exception", "result", "resolution_wh", "classes", "expected_detections"),
[
(
pytest.raises(ValueError),
@@ -18,7 +18,7 @@ def mock_callback():
@pytest.mark.parametrize(
"resolution_wh, slice_wh, overlap_wh, expected_offsets",
("resolution_wh", "slice_wh", "overlap_wh", "expected_offsets"),
[
# Case 1: Square image, square slices, no overlap
(
+4 -4
View File
@@ -14,7 +14,7 @@ from supervision.detection.utils.boxes import (
@pytest.mark.parametrize(
"xyxy, resolution_wh, expected_result",
("xyxy", "resolution_wh", "expected_result"),
[
(
np.empty(shape=(0, 4)),
@@ -58,7 +58,7 @@ def test_clip_boxes(
@pytest.mark.parametrize(
"xyxy, offset, expected_result, exception",
("xyxy", "offset", "expected_result", "exception"),
[
(
np.empty(shape=(0, 4)),
@@ -104,7 +104,7 @@ def test_move_boxes(
@pytest.mark.parametrize(
"xyxy, factor, expected_result, exception",
("xyxy", "factor", "expected_result", "exception"),
[
(
np.empty(shape=(0, 4)),
@@ -150,7 +150,7 @@ def test_scale_boxes(
@pytest.mark.parametrize(
"xyxy, resolution_wh, normalization_factor, expected_result, exception",
("xyxy", "resolution_wh", "normalization_factor", "expected_result", "exception"),
[
(
np.empty(shape=(0, 4)),
+5 -5
View File
@@ -13,7 +13,7 @@ from supervision.detection.utils.converters import (
@pytest.mark.parametrize(
"xywh, expected_result",
("xywh", "expected_result"),
[
(np.array([[10, 20, 30, 40]]), np.array([[10, 20, 40, 60]])), # standard case
(np.array([[0, 0, 0, 0]]), np.array([[0, 0, 0, 0]])), # zero size bounding box
@@ -36,7 +36,7 @@ def test_xywh_to_xyxy(xywh: np.ndarray, expected_result: np.ndarray) -> None:
@pytest.mark.parametrize(
"xyxy, expected_result",
("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
@@ -59,7 +59,7 @@ def test_xyxy_to_xywh(xyxy: np.ndarray, expected_result: np.ndarray) -> None:
@pytest.mark.parametrize(
"xyxy, expected_result",
("xyxy", "expected_result"),
[
# Empty and zero cases
(np.array([]).reshape(0, 4), np.array([]).reshape(0, 4)), # empty array
@@ -110,7 +110,7 @@ def test_xyxy_to_xcycarh(xyxy: np.ndarray, expected_result: np.ndarray) -> None:
@pytest.mark.parametrize(
"xcycwh, expected_result",
("xcycwh", "expected_result"),
[
(np.array([[50, 50, 20, 30]]), np.array([[40, 35, 60, 65]])), # standard case
(np.array([[0, 0, 0, 0]]), np.array([[0, 0, 0, 0]])), # zero size bounding box
@@ -133,7 +133,7 @@ def test_xcycwh_to_xyxy(xcycwh: np.ndarray, expected_result: np.ndarray) -> None
@pytest.mark.parametrize(
"boxes,resolution_wh,expected",
("boxes", "resolution_wh", "expected"),
[
# 0) Empty input
(
+5 -5
View File
@@ -19,7 +19,7 @@ TEST_MASK[:, 300:351, 200:251] = True
@pytest.mark.parametrize(
"roboflow_result, expected_result, exception",
("roboflow_result", "expected_result", "exception"),
[
(
{"predictions": [], "image": {"width": 1000, "height": 1000}},
@@ -251,7 +251,7 @@ def test_process_roboflow_result(
@pytest.mark.parametrize(
"data_list, expected_result, exception",
("data_list", "expected_result", "exception"),
[
(
[],
@@ -445,7 +445,7 @@ def test_merge_data(
with exception:
result = merge_data(data_list=data_list)
if expected_result is None:
assert False, f"Expected an error, but got result {result}"
pytest.fail(f"Expected an error, but got result {result}")
for key in result:
if isinstance(result[key], np.ndarray):
@@ -459,7 +459,7 @@ def test_merge_data(
@pytest.mark.parametrize(
"data, index, expected_result, exception",
("data", "index", "expected_result", "exception"),
[
({}, 0, {}, DoesNotRaise()), # empty data dict
(
@@ -632,7 +632,7 @@ def test_get_data_item(
@pytest.mark.parametrize(
"metadata_list, expected_result, exception",
("metadata_list", "expected_result", "exception"),
[
# Identical metadata with a single key
([{"key1": "value1"}, {"key1": "value1"}], {"key1": "value1"}, DoesNotRaise()),
+13 -14
View File
@@ -18,7 +18,7 @@ from test.test_utils import random_boxes
@pytest.mark.parametrize(
"predictions, iou_threshold, expected_result, exception",
("predictions", "iou_threshold", "expected_result", "exception"),
[
(
np.empty(shape=(0, 5), dtype=float),
@@ -145,7 +145,7 @@ def test_group_overlapping_boxes(
@pytest.mark.parametrize(
"predictions, iou_threshold, expected_result, exception",
("predictions", "iou_threshold", "expected_result", "exception"),
[
(
np.empty(shape=(0, 5)),
@@ -250,7 +250,7 @@ def test_box_non_max_suppression(
@pytest.mark.parametrize(
"predictions, masks, iou_threshold, expected_result, exception",
("predictions", "masks", "iou_threshold", "expected_result", "exception"),
[
(
np.empty((0, 6)),
@@ -456,7 +456,7 @@ def test_mask_non_max_suppression(
@pytest.mark.parametrize(
"predictions, masks, iou_threshold, expected_result, exception",
("predictions", "masks", "iou_threshold", "expected_result", "exception"),
[
(
np.empty((0, 6)),
@@ -638,7 +638,7 @@ def test_mask_non_max_merge(
@pytest.mark.parametrize(
"box_true, box_detection, overlap_metric, expected_overlap, exception",
("box_true", "box_detection", "overlap_metric", "expected_overlap", "exception"),
[
(
[100.0, 100.0, 200.0, 200.0],
@@ -689,13 +689,6 @@ def test_mask_non_max_merge(
1.0,
DoesNotRaise(),
), # identical boxes, both boxes are arrays, IOU as uppercase string
(
[0.0, 0.0, 10.0, 10.0],
[0.0, 0.0, 10.0, 10.0],
"IOU",
1.0,
DoesNotRaise(),
), # identical boxes, both boxes are arrays, IOS as uppercase string
(
[0.0, 0.0, 10.0, 10.0],
[20.0, 20.0, 30.0, 30.0],
@@ -813,7 +806,13 @@ def test_box_iou(
@pytest.mark.parametrize(
"boxes_true, boxes_detection, overlap_metric, expected_overlap, exception",
(
"boxes_true",
"boxes_detection",
"overlap_metric",
"expected_overlap",
"exception",
),
[
# both inputs empty
(
@@ -1086,7 +1085,7 @@ def test_box_iou_batch(
@pytest.mark.parametrize(
"num_true, num_det",
("num_true", "num_det"),
[
(5, 5),
(5, 10),
+13 -5
View File
@@ -16,7 +16,7 @@ from supervision.detection.utils.masks import (
@pytest.mark.parametrize(
"masks, offset, resolution_wh, expected_result, exception",
("masks", "offset", "resolution_wh", "expected_result", "exception"),
[
(
np.array(
@@ -278,7 +278,7 @@ def test_move_masks(
@pytest.mark.parametrize(
"masks, expected_result, exception",
("masks", "expected_result", "exception"),
[
(
np.array(
@@ -369,7 +369,7 @@ def test_calculate_masks_centroids(
@pytest.mark.parametrize(
"mask, expected_result, exception",
("mask", "expected_result", "exception"),
[
(
np.array([[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 0, 0], [0, 1, 1, 0]]).astype(
@@ -424,7 +424,7 @@ def test_contains_holes(
@pytest.mark.parametrize(
"mask, connectivity, expected_result, exception",
("mask", "connectivity", "expected_result", "exception"),
[
(
np.array([[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 0, 0], [0, 1, 1, 0]]).astype(
@@ -504,7 +504,15 @@ def test_contains_multiple_segments(
@pytest.mark.parametrize(
"mask, connectivity, mode, absolute_distance, relative_distance, expected_result, exception", # noqa: E501
(
"mask",
"connectivity",
"mode",
"absolute_distance",
"relative_distance",
"expected_result",
"exception",
),
[
# single component, unchanged
(
+1 -1
View File
@@ -9,7 +9,7 @@ from supervision.detection.utils.polygons import filter_polygons_by_area
@pytest.mark.parametrize(
"polygons, min_area, max_area, expected_result, exception",
("polygons", "min_area", "max_area", "expected_result", "exception"),
[
(
[np.array([[0, 0], [0, 10], [10, 10], [10, 0]])],
+2 -2
View File
@@ -4,7 +4,7 @@ from supervision.detection.utils.vlms import edit_distance, fuzzy_match_index
@pytest.mark.parametrize(
"string_1, string_2, case_sensitive, expected_result",
("string_1", "string_2", "case_sensitive", "expected_result"),
[
# identical strings, various cases
("hello", "hello", True, 0),
@@ -68,7 +68,7 @@ def test_edit_distance(string_1, string_2, case_sensitive, expected_result):
@pytest.mark.parametrize(
"candidates, query, threshold, case_sensitive, expected_result",
("candidates", "query", "threshold", "case_sensitive", "expected_result"),
[
# exact match at index 0
(["cat", "dog", "rat"], "cat", 0, True, 0),
+2 -2
View File
@@ -8,7 +8,7 @@ from supervision.draw.color import Color
@pytest.mark.parametrize(
"color_hex, expected_result, exception",
("color_hex", "expected_result", "exception"),
[
("fff", Color.WHITE, DoesNotRaise()),
("#fff", Color.WHITE, DoesNotRaise()),
@@ -34,7 +34,7 @@ def test_color_from_hex(
@pytest.mark.parametrize(
"color, expected_result, exception",
("color", "expected_result", "exception"),
[
(Color.WHITE, "#ffffff", DoesNotRaise()),
(Color.BLACK, "#000000", DoesNotRaise()),
+2 -2
View File
@@ -4,7 +4,7 @@ from supervision.geometry.core import Point, Vector
@pytest.mark.parametrize(
"vector, point, expected_result",
("vector", "point", "expected_result"),
[
(Vector(start=Point(x=0, y=0), end=Point(x=5, y=5)), Point(x=-1, y=1), 10.0),
(Vector(start=Point(x=0, y=0), end=Point(x=5, y=5)), Point(x=6, y=6), 0.0),
@@ -34,7 +34,7 @@ def test_vector_cross_product(
@pytest.mark.parametrize(
"vector, expected_result",
("vector", "expected_result"),
[
(Vector(start=Point(x=0, y=0), end=Point(x=0, y=0)), 0.0),
(Vector(start=Point(x=1, y=0), end=Point(x=0, y=0)), 1.0),
+1 -1
View File
@@ -35,7 +35,7 @@ def generate_test_polygon(n: int) -> np.ndarray:
@pytest.mark.parametrize(
"polygon, expected_result",
("polygon", "expected_result"),
[
(generate_test_polygon(10), Point(x=5.0, y=12.0)),
(generate_test_polygon(50), Point(x=25.0, y=61.0)),
+1 -1
View File
@@ -22,7 +22,7 @@ KEY_POINTS = mock_key_points(
@pytest.mark.parametrize(
"key_points, index, expected_result, exception",
("key_points", "index", "expected_result", "exception"),
[
(
KeyPoints.empty(),
+21 -7
View File
@@ -123,7 +123,7 @@ BAD_CONF_MATRIX = worsen_ideal_conf_matrix(
@pytest.mark.parametrize(
"detections, with_confidence, expected_result, exception",
("detections", "with_confidence", "expected_result", "exception"),
[
(
Detections.empty(),
@@ -187,8 +187,15 @@ def test_detections_to_tensor(
@pytest.mark.parametrize(
"predictions, targets, classes, conf_threshold, iou_threshold, expected_result,"
" exception",
(
"predictions",
"targets",
"classes",
"conf_threshold",
"iou_threshold",
"expected_result",
"exception",
),
[
(
DETECTION_TENSORS,
@@ -359,8 +366,15 @@ def test_from_tensors(
@pytest.mark.parametrize(
"predictions, targets, num_classes, conf_threshold, iou_threshold, expected_result,"
" exception",
(
"predictions",
"targets",
"num_classes",
"conf_threshold",
"iou_threshold",
"expected_result",
"exception",
),
[
(
DETECTION_TENSORS[0],
@@ -396,7 +410,7 @@ def test_evaluate_detection_batch(
@pytest.mark.parametrize(
"matches, expected_result, exception",
("matches", "expected_result", "exception"),
[
(
IDEAL_MATCHES,
@@ -417,7 +431,7 @@ def test_drop_extra_matches(
@pytest.mark.parametrize(
"recall, precision, expected_result, exception",
("recall", "precision", "expected_result", "exception"),
[
(
np.array([1.0]),
@@ -11,7 +11,7 @@ class TestMeanAveragePrecisionArea:
"""Test area calculation in MeanAveragePrecision."""
@pytest.mark.parametrize(
"xyxy, expected_areas, expected_size_maps",
("xyxy", "expected_areas", "expected_size_maps"),
[
(
np.array(
+5 -7
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import random
from typing import Any
import numpy as np
@@ -77,18 +76,17 @@ def random_boxes(
(`numpy.ndarray`): Array of shape `(count, 4)` with bounding boxes as
`(x_min, y_min, x_max, y_max)`.
"""
if seed is not None:
random.seed(seed)
rng = np.random.default_rng(seed)
img_w, img_h = image_size
out = np.zeros((count, 4), dtype=np.float32)
for i in range(count):
w = random.uniform(min_box_size, max_box_size)
h = random.uniform(min_box_size, max_box_size)
w = rng.uniform(min_box_size, max_box_size)
h = rng.uniform(min_box_size, max_box_size)
x_min = random.uniform(0, img_w - w)
y_min = random.uniform(0, img_h - h)
x_min = rng.uniform(0, img_w - w)
y_min = rng.uniform(0, img_h - h)
x_max = x_min + w
y_max = y_min + h
+1 -1
View File
@@ -5,7 +5,7 @@ import supervision as sv
@pytest.mark.parametrize(
"detections, expected_results",
("detections", "expected_results"),
[
(
[
+3 -4
View File
@@ -12,12 +12,11 @@ Line 2
Line 3
"""
FILE_2_CONTENT = """
Line 2
FILE_2_CONTENT = """ \nLine 2
Line 4
""" # noqa
"""
FILE_3_CONTENT = """
Line 2
@@ -44,7 +43,7 @@ def setup_and_teardown_files():
@pytest.mark.parametrize(
"file_name, skip_empty, expected_result, exception",
("file_name", "skip_empty", "expected_result", "exception"),
[
("file_1.txt", False, ["Line 1", "Line 2", "Line 3"], DoesNotRaise()),
("file_2.txt", True, ["Line 2", "Line 4"], DoesNotRaise()),
+2 -2
View File
@@ -103,7 +103,7 @@ def test_letterbox_image_for_pillow_image() -> None:
@pytest.mark.parametrize(
"image, xyxy, expected_size",
("image", "xyxy", "expected_size"),
[
# NumPy RGB
(
@@ -143,7 +143,7 @@ def test_crop_image(image, xyxy, expected_size):
@pytest.mark.parametrize(
"image, expected",
("image", "expected"),
[
# NumPy RGB
(np.zeros((4, 6, 3), dtype=np.uint8), (6, 4)),
+1 -15
View File
@@ -74,7 +74,7 @@ class MockDataclass:
@pytest.mark.parametrize(
"input_instance, include_properties, expected, exception",
("input_instance", "include_properties", "expected", "exception"),
[
(
MockClass,
@@ -184,20 +184,6 @@ class MockDataclass:
},
DoesNotRaise(),
),
(
Detections.empty(),
False,
{
"xyxy",
"class_id",
"confidence",
"mask",
"tracker_id",
"data",
"metadata",
},
DoesNotRaise(),
),
],
)
def test_get_instance_variables(
+2 -2
View File
@@ -4,7 +4,7 @@ from supervision.utils.iterables import create_batches, fill
@pytest.mark.parametrize(
"sequence, batch_size, expected",
("sequence", "batch_size", "expected"),
[
# Empty sequence, non-zero batch size. Expect empty list.
([], 4, []),
@@ -24,7 +24,7 @@ def test_create_batches(sequence, batch_size, expected) -> None:
@pytest.mark.parametrize(
"sequence, desired_size, content, expected",
("sequence", "desired_size", "content", "expected"),
[
# Empty sequence, desired size 0. Expect empty list.
([], 0, 1, []),