Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f34993c202 | ||
|
|
f44a9376ec | ||
|
|
48752b432b | ||
|
|
c1797ffd81 | ||
|
|
a640e527d0 | ||
|
|
d894c7814b | ||
|
|
815c43beb8 | ||
|
|
2fed8ae0c3 | ||
|
|
586ff5b3fb | ||
|
|
8b73c12bcc | ||
|
|
67d1323daa | ||
|
|
0e6d69cf85 | ||
|
|
a5c211ff61 | ||
|
|
5f5b4da845 | ||
|
|
be16b84340 | ||
|
|
08af18ad41 | ||
|
|
caf504c0c9 | ||
|
|
893a19a991 | ||
|
|
b35a6161dd | ||
|
|
aabca4a4b5 | ||
|
|
57ba059f1c | ||
|
|
743f3281a5 | ||
|
|
737cb37249 | ||
|
|
78a4e73f25 | ||
|
|
dc9c1151c5 | ||
|
|
5f82aac9e6 | ||
|
|
d66a539779 | ||
|
|
a037a4a404 | ||
|
|
871968a916 | ||
|
|
5ca30f5898 | ||
|
|
54bb79c220 | ||
|
|
809d8946e6 | ||
|
|
e2305e6f57 | ||
|
|
e173510117 | ||
|
|
e209c7436c | ||
|
|
25a45aa423 | ||
|
|
b9d1d78f31 | ||
|
|
142fe02959 | ||
|
|
6815814aec | ||
|
|
1b6613d4a3 | ||
|
|
fabfe0783a | ||
|
|
2c4e7589b0 | ||
|
|
0abe36e079 | ||
|
|
61809f9329 | ||
|
|
69f3d10e42 | ||
|
|
62c45548a6 | ||
|
|
00e209744f | ||
|
|
8dcfb42751 | ||
|
|
a5a853e141 | ||
|
|
034c2bd9a8 | ||
|
|
a7449ba56f | ||
|
|
8f302c0646 | ||
|
|
1ddb78b662 | ||
|
|
db4afe9885 | ||
|
|
72a780b74a | ||
|
|
b2e9a668b2 | ||
|
|
57ef24bcb3 | ||
|
|
eda6909c91 | ||
|
|
9a9bf62fe0 | ||
|
|
1ab50cdb3c | ||
|
|
f78c282b7e | ||
|
|
3d843d710d | ||
|
|
75db51aa90 | ||
|
|
fe8a8260dc | ||
|
|
bf725ef0f7 | ||
|
|
338c589032 | ||
|
|
4baf570b28 | ||
|
|
1324cda600 | ||
|
|
9ded70c8da | ||
|
|
8cda753334 | ||
|
|
a14159c150 | ||
|
|
ae00a9d65f | ||
|
|
c51159b598 | ||
|
|
476a31fa79 | ||
|
|
e1e455f99c | ||
|
|
5b5e0eb88d | ||
|
|
d0139f5293 | ||
|
|
bed7ed45b4 | ||
|
|
35142fc755 | ||
|
|
f041f0ddcc | ||
|
|
253a136f43 | ||
|
|
9cefff8627 | ||
|
|
f2af290166 | ||
|
|
988da89bf4 | ||
|
|
b4a8859616 | ||
|
|
9fff77a6e3 | ||
|
|
e7f95e0761 | ||
|
|
2254e59bdf | ||
|
|
6a0482ded5 | ||
|
|
29532db02f | ||
|
|
8eb827b31d | ||
|
|
33e43bc92a | ||
|
|
8b5b0a8516 | ||
|
|
9a7b4c34ba | ||
|
|
b2ab398029 | ||
|
|
082f248159 | ||
|
|
72463f5f89 | ||
|
|
83342ca36f | ||
|
|
48851b5ae5 | ||
|
|
e5894df156 | ||
|
|
49d268c250 | ||
|
|
a7a7dac216 | ||
|
|
8bca45bde4 | ||
|
|
282db45c00 | ||
|
|
1c5006c013 | ||
|
|
9b4fa8cb89 | ||
|
|
333408d33a | ||
|
|
41a19ac4d6 | ||
|
|
fe0df11918 | ||
|
|
375b5f8f54 | ||
|
|
18fe2e99de | ||
|
|
a83d3b4c7e | ||
|
|
89c9f88aeb | ||
|
|
6ef7bf31d3 | ||
|
|
c3a025cc9d | ||
|
|
f489f944b8 | ||
|
|
cfb1e0ea60 | ||
|
|
5efd535e9d | ||
|
|
a550aaa812 | ||
|
|
30994243ae | ||
|
|
99707582bc | ||
|
|
c0b87a6f04 | ||
|
|
d30d80717c | ||
|
|
4733ebe27d | ||
|
|
44e4eeaa69 | ||
|
|
2952286f95 | ||
|
|
c5e1211c74 | ||
|
|
72dc07e332 | ||
|
|
c5c37e4a6c | ||
|
|
8000043bc8 | ||
|
|
14d0189e2c | ||
|
|
68a761349d | ||
|
|
d5d5725cea | ||
|
|
669c0658fe | ||
|
|
bed28e3db4 | ||
|
|
cc93bba741 | ||
|
|
e7c242bdd4 | ||
|
|
89c1b63979 | ||
|
|
456655c2a4 | ||
|
|
f67a67bd7c | ||
|
|
30687609f5 | ||
|
|
ab1b2d6d80 | ||
|
|
5c6323bed0 | ||
|
|
d2fe2ad40c | ||
|
|
5aaa15c894 | ||
|
|
4bb62f06ff | ||
|
|
4e7a763ad9 | ||
|
|
cb225b26e8 | ||
|
|
07ec604102 | ||
|
|
ef43640c88 | ||
|
|
77c126e421 | ||
|
|
edd8b8f41e | ||
|
|
cad38104e0 | ||
|
|
618fadae2a | ||
|
|
71d23619b0 | ||
|
|
1bddf26f34 | ||
|
|
73ed3f5f5e | ||
|
|
a57176568a | ||
|
|
88e7fb76d2 | ||
|
|
09c5407347 | ||
|
|
bfcf6469d4 | ||
|
|
ec72f81749 | ||
|
|
3a52f7b082 | ||
|
|
5efb3e2df1 | ||
|
|
6e33cb04ca | ||
|
|
2455ef7cfe | ||
|
|
1d0ffeba72 | ||
|
|
55d14cf33e | ||
|
|
a66d835a57 | ||
|
|
440be5d416 | ||
|
|
4fd1ba4dd3 | ||
|
|
61108048e1 | ||
|
|
a0b64342ca | ||
|
|
7cf593b55d | ||
|
|
1c1936e411 | ||
|
|
331ccb8dde | ||
|
|
7366ba9036 | ||
|
|
9d5e53030a | ||
|
|
d67fa3ae9b | ||
|
|
10ad27d726 | ||
|
|
8511bbb762 | ||
|
|
14388e56a2 | ||
|
|
1eb74ed88a | ||
|
|
87d830d3a1 | ||
|
|
d7a5624d60 | ||
|
|
d3c71c2119 | ||
|
|
9ad5e50ae9 | ||
|
|
67fa66c7e7 | ||
|
|
492f033882 | ||
|
|
e911516180 | ||
|
|
ed30ef68af | ||
|
|
fefd273a0f | ||
|
|
8746dd3d0e | ||
|
|
7cb57c9cc9 | ||
|
|
ddc036ea3a | ||
|
|
c83959bb16 | ||
|
|
4f513ce275 | ||
|
|
b482784403 | ||
|
|
3b00e04101 | ||
|
|
6f30c87612 | ||
|
|
93a8110908 | ||
|
|
410b7eb4fc | ||
|
|
e6e1397f56 | ||
|
|
0ca9a3ecdf | ||
|
|
c5016bbd8f | ||
|
|
9fbd0ddf62 | ||
|
|
a630870968 | ||
|
|
06416607cc | ||
|
|
c6b8fbed31 | ||
|
|
b9598f54b6 | ||
|
|
21e5d5139c | ||
|
|
8f583404ce | ||
|
|
bb42753e23 | ||
|
|
ea0ec29c81 | ||
|
|
227615b766 | ||
|
|
baff75332f | ||
|
|
434a695324 | ||
|
|
5d71ab2961 | ||
|
|
5c9e529743 | ||
|
|
3f7043e780 | ||
|
|
c746a7f9e4 | ||
|
|
04629cd7f4 |
@@ -0,0 +1,17 @@
|
||||
contourpy==1.1.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
cycler==0.11.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
fonttools==4.41.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
importlib-resources==6.0.0 ; python_version >= "3.8" and python_version < "3.10"
|
||||
kiwisolver==1.4.4 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
matplotlib==3.7.2 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
numpy==1.24.4 ; python_version >= "3.8" and python_version < "3.12"
|
||||
opencv-python-headless==4.8.0.74 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
packaging==23.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
pillow==10.0.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
pyparsing==3.0.9 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
python-dateutil==2.8.2 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
pyyaml==6.0.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
scipy==1.10.1 ; python_version >= "3.8" and python_version < "3.12"
|
||||
six==1.16.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
zipp==3.16.2 ; python_version >= "3.8" and python_version < "3.10"
|
||||
pytest==7.4.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
name: Clear cache
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 1 * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
clear-cache:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clear cache
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
console.log("About to clear")
|
||||
const caches = await github.rest.actions.getActionsCacheList({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
for (const cache of caches.data.actions_caches) {
|
||||
console.log(cache)
|
||||
github.rest.actions.deleteActionsCacheById({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
cache_id: cache.id,
|
||||
})
|
||||
}
|
||||
console.log("Clear completed")
|
||||
@@ -15,12 +15,18 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- name: 🐍 Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
# id based on python version
|
||||
id: python-setup
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: 🦾 Install dependencies
|
||||
check-latest: true
|
||||
cache: 'pip'
|
||||
cache-dependency-path: '**/requirements-dev.txt'
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .
|
||||
pip install pytest
|
||||
pip install -r .github/requirements-dev.txt
|
||||
|
||||
- name: 🧪 Test
|
||||
run: "python -m pytest ./test"
|
||||
|
||||
@@ -7,7 +7,7 @@ ci:
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
@@ -22,6 +22,7 @@ repos:
|
||||
- id: detect-private-key
|
||||
- id: forbid-new-submodules
|
||||
- id: pretty-format-json
|
||||
exclude: demo.ipynb
|
||||
args: ['--autofix', '--no-sort-keys', '--indent=4']
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
@@ -33,11 +34,6 @@ repos:
|
||||
# name: Upgrade code
|
||||
# args: [--py38-plus]
|
||||
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
name: Sort imports
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.1.0
|
||||
@@ -74,7 +70,7 @@ repos:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.0.291
|
||||
rev: v0.0.292
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* 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
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
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
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
community-reports@roboflow.com.
|
||||
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -1,22 +0,0 @@
|
||||
.PHONY: style check_code_quality
|
||||
|
||||
export PYTHONPATH = .
|
||||
check_dirs := supervision
|
||||
|
||||
style:
|
||||
black $(check_dirs)
|
||||
isort --profile black $(check_dirs)
|
||||
|
||||
check_code_quality:
|
||||
black --check $(check_dirs)
|
||||
isort --check-only --profile black $(check_dirs)
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 $(check_dirs) --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. E203 for black, E501 for docstring, W503 for line breaks before logical operators
|
||||
flake8 $(check_dirs) --count --max-line-length=88 --exit-zero --ignore=D --extend-ignore=E203,E501,W503 --statistics
|
||||
|
||||
publish:
|
||||
poetry build
|
||||
twine upload -r testpypi dist/* -u ${PYPI_USERNAME} -p ${PYPI_TEST_PASSWORD} --verbose
|
||||
twine check dist/*
|
||||
twine upload dist/* -u ${PYPI_USERNAME} -p ${PYPI_PASSWORD} --verbose
|
||||
@@ -19,9 +19,15 @@
|
||||
[](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
|
||||
[](https://badge.fury.io/py/supervision)
|
||||
[](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb)
|
||||
[](https://huggingface.co/spaces/Roboflow/Annotators)
|
||||
[](https://discord.gg/GbfgXGJ8Bk)
|
||||
|
||||
</div>
|
||||
|
||||
<a href="https://github.com/roboflow/supervision/issues?q=is%3Aissue+label%3Ahacktoberfest+">
|
||||
<img width="100%" src="https://media.roboflow.com/open-source/supervision/hacktoberfest-banner-3.png">
|
||||
</a>
|
||||
|
||||
## 👋 hello
|
||||
|
||||
**We write your reusable computer vision tools.** Whether you need to load your dataset from your hard drive, draw detections on an image or video, or count how many detections are in a zone. You can count on us! 🤝
|
||||
@@ -305,6 +311,12 @@ Visit our [documentation](https://roboflow.github.io/supervision) page to learn
|
||||
|
||||
We love your input! Please see our [contributing guide](https://github.com/roboflow/supervision/blob/main/CONTRIBUTING.md) to get started. Thank you 🙏 to all our contributors!
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/roboflow/supervision/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=roboflow/supervision" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
<div align="center">
|
||||
@@ -352,5 +364,4 @@ We love your input! Please see our [contributing guide](https://github.com/robof
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Vendored
+528
-528
File diff suppressed because one or more lines are too long
@@ -0,0 +1,321 @@
|
||||
=== "BoundingBox"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> bounding_box_annotator = sv.BoundingBoxAnnotator()
|
||||
>>> annotated_frame = bounding_box_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "BoxCorner"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> corner_annotator = sv.BoxCornerAnnotator()
|
||||
>>> annotated_frame = corner_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "BoxMask"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> box_mask_annotator = sv.BoxMaskAnnotator()
|
||||
>>> annotated_frame = box_mask_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Circle"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> circle_annotator = sv.CircleAnnotator()
|
||||
>>> annotated_frame = circle_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Dot"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> dot_annotator = sv.DotAnnotator()
|
||||
>>> annotated_frame = dot_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Ellipse"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> ellipse_annotator = sv.EllipseAnnotator()
|
||||
>>> annotated_frame = ellipse_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Halo"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> halo_annotator = sv.HaloAnnotator()
|
||||
>>> annotated_frame = halo_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Mask"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> mask_annotator = sv.MaskAnnotator()
|
||||
>>> annotated_frame = mask_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Label"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
|
||||
>>> annotated_frame = label_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Blur"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> blur_annotator = sv.BlurAnnotator()
|
||||
>>> annotated_frame = blur_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "Trace"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
>>> from ultralytics import YOLO
|
||||
|
||||
>>> model = YOLO('yolov8x.pt')
|
||||
|
||||
>>> trace_annotator = sv.TraceAnnotator()
|
||||
|
||||
>>> video_info = sv.VideoInfo.from_video_path(video_path='...')
|
||||
>>> frames_generator = get_video_frames_generator(source_path='...')
|
||||
>>> tracker = sv.ByteTrack()
|
||||
|
||||
>>> with sv.VideoSink(target_path='...', video_info=video_info) as sink:
|
||||
... for frame in frames_generator:
|
||||
... result = model(frame)[0]
|
||||
... detections = sv.Detections.from_ultralytics(result)
|
||||
... detections = tracker.update_with_detections(detections)
|
||||
... annotated_frame = trace_annotator.annotate(
|
||||
... scene=frame.copy(),
|
||||
... detections=detections)
|
||||
... sink.write_frame(frame=annotated_frame)
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
=== "HeatMap"
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
>>> from ultralytics import YOLO
|
||||
|
||||
>>> model = YOLO('yolov8x.pt')
|
||||
|
||||
>>> heat_map_annotator = sv.HeatMapAnnotator()
|
||||
|
||||
>>> video_info = sv.VideoInfo.from_video_path(video_path='...')
|
||||
>>> frames_generator = get_video_frames_generator(source_path='...')
|
||||
|
||||
>>> with sv.VideoSink(target_path='...', video_info=video_info) as sink:
|
||||
... for frame in frames_generator:
|
||||
... result = model(frame)[0]
|
||||
... detections = sv.Detections.from_ultralytics(result)
|
||||
... annotated_frame = heat_map_annotator.annotate(
|
||||
... scene=frame.copy(),
|
||||
... detections=detections)
|
||||
... sink.write_frame(frame=annotated_frame)
|
||||
```
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
## BoundingBoxAnnotator
|
||||
|
||||
:::supervision.annotators.core.BoundingBoxAnnotator
|
||||
|
||||
## BoxCornerAnnotator
|
||||
|
||||
:::supervision.annotators.core.BoxCornerAnnotator
|
||||
|
||||
## BoxMaskAnnotator
|
||||
|
||||
:::supervision.annotators.core.BoxMaskAnnotator
|
||||
|
||||
## CircleAnnotator
|
||||
|
||||
:::supervision.annotators.core.CircleAnnotator
|
||||
|
||||
## DotAnnotator
|
||||
|
||||
:::supervision.annotators.core.DotAnnotator
|
||||
|
||||
## EllipseAnnotator
|
||||
|
||||
:::supervision.annotators.core.EllipseAnnotator
|
||||
|
||||
## HaloAnnotator
|
||||
|
||||
:::supervision.annotators.core.HaloAnnotator
|
||||
|
||||
## HeatMapAnnotator
|
||||
|
||||
:::supervision.annotators.core.HeatMapAnnotator
|
||||
|
||||
## MaskAnnotator
|
||||
|
||||
:::supervision.annotators.core.MaskAnnotator
|
||||
|
||||
## LabelAnnotator
|
||||
|
||||
:::supervision.annotators.core.LabelAnnotator
|
||||
|
||||
## BlurAnnotator
|
||||
|
||||
:::supervision.annotators.core.BlurAnnotator
|
||||
|
||||
## TraceAnnotator
|
||||
|
||||
:::supervision.annotators.core.TraceAnnotator
|
||||
|
||||
## ColorLookup
|
||||
|
||||
:::supervision.annotators.utils.ColorLookup
|
||||
@@ -1,3 +1,86 @@
|
||||
### 0.16.0 <small>October 19, 2023</small>
|
||||
|
||||
- Added [#422](https://github.com/roboflow/supervision/pull/422): [`sv.BoxMaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoxMaskAnnotator) allowing to annotate images and videos with mox masks.
|
||||
|
||||
- Added [#433](https://github.com/roboflow/supervision/pull/433): [`sv.HaloAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.HaloAnnotator) allowing to annotate images and videos with halo effect.
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> halo_annotator = sv.HaloAnnotator()
|
||||
>>> annotated_frame = halo_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
- Added [#466](https://github.com/roboflow/supervision/pull/466): [`sv.HeatMapAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.HeatMapAnnotator) allowing to annotate videos with heat maps.
|
||||
|
||||
- Added [#492](https://github.com/roboflow/supervision/pull/492): [`sv.DotAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.DotAnnotator) allowing to annotate images and videos with dots.
|
||||
|
||||
- Added [#449](https://github.com/roboflow/supervision/pull/449): [`sv.draw_image`](https://supervision.roboflow.com/draw/utils/#supervision.draw.utils.draw_image) allowing to draw an image onto a given scene with specified opacity and dimensions.
|
||||
|
||||
- Added [#280](https://github.com/roboflow/supervision/pull/280): [`sv.FPSMonitor`](https://supervision.roboflow.com/utils/video/#supervision.utils.video.FPSMonitor) for monitoring frames per second (FPS) to benchmark latency.
|
||||
|
||||
- Added [#454](https://github.com/roboflow/supervision/pull/454): 🤗 Hugging Face Annotators [space](https://huggingface.co/spaces/Roboflow/Annotators).
|
||||
|
||||
- Changed [#482](https://github.com/roboflow/supervision/pull/482): [`sv.LineZone.tigger`](https://supervision.roboflow.com/detection/tools/line_zone/#supervision.detection.line_counter.LineZone.trigger) now return `Tuple[np.ndarray, np.ndarray]`. The first array indicates which detections have crossed the line from outside to inside. The second array indicates which detections have crossed the line from inside to outside.
|
||||
|
||||
- Changed [#465](https://github.com/roboflow/supervision/pull/465): Annotator argument name from `color_map: str` to `color_lookup: ColorLookup` enum to increase type safety.
|
||||
|
||||
- Changed [#426](https://github.com/roboflow/supervision/pull/426): [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) allowing 2x faster annotation.
|
||||
|
||||
- Fixed [#477](https://github.com/roboflow/supervision/pull/477): Poetry env definition allowing proper local installation.
|
||||
|
||||
- Fixed [#430](https://github.com/roboflow/supervision/pull/430): [`sv.ByteTrack`](https://supervision.roboflow.com/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) to return `np.array([], dtype=int)` when `svDetections` is empty.
|
||||
|
||||
!!! warning
|
||||
|
||||
`sv.Detections.from_yolov8` and `sv.Classifications.from_yolov8` as those are now replaced by [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_ultralytics) and [`sv.Classifications.from_ultralytics`](https://supervision.roboflow.com/classification/core/#supervision.classification.core.Classifications.from_ultralytics).
|
||||
|
||||
|
||||
### 0.15.0 <small>October 5, 2023</small>
|
||||
|
||||
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) allowing to annotate images and videos with bounding boxes.
|
||||
|
||||
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.BoxCornerAnnotator `](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoxCornerAnnotator) allowing to annotate images and videos with just bounding box corners.
|
||||
|
||||
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) allowing to annotate images and videos with segmentation masks.
|
||||
|
||||
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.EllipseAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.EllipseAnnotator) allowing to annotate images and videos with ellipses (sports game style).
|
||||
|
||||
- Added [#386](https://github.com/roboflow/supervision/pull/386): [`sv.CircleAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.CircleAnnotator) allowing to annotate images and videos with circles.
|
||||
|
||||
- Added [#354](https://github.com/roboflow/supervision/pull/354): [`sv.TraceAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.TraceAnnotator) allowing to draw path of moving objects on videos.
|
||||
|
||||
- Added [#405](https://github.com/roboflow/supervision/pull/405): [`sv.BlurAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BlurAnnotator) allowing to blur objects on images and videos.
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> bounding_box_annotator = sv.BoundingBoxAnnotator()
|
||||
>>> annotated_frame = bounding_box_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
- Added [#354](https://github.com/roboflow/supervision/pull/354): Supervision usage [example](https://github.com/roboflow/supervision/tree/develop/examples/traffic_analysis). You can now learn how to perform traffic flow analysis with Supervision.
|
||||
|
||||
- Changed [#399](https://github.com/roboflow/supervision/pull/399): [`sv.Detections.from_roboflow`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_roboflow) now does not require `class_list` to be specified. The `class_id` value can be extracted directly from the [inference](https://github.com/roboflow/inference) response.
|
||||
|
||||
- Changed [#381](https://github.com/roboflow/supervision/pull/381): [`sv.VideoSink`](https://supervision.roboflow.com/utils/video/#videosink) now allows to customize the output codec.
|
||||
|
||||
- Changed [#361](https://github.com/roboflow/supervision/pull/361): [`sv.InferenceSlicer`](https://supervision.roboflow.com/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer) can now operate in multithreading mode.
|
||||
|
||||
- Fixed [#348](https://github.com/roboflow/supervision/pull/348): [`sv.Detections.from_deepsparse`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_deepsparse) to allow processing empty [deepsparse](https://github.com/neuralmagic/deepsparse) result object.
|
||||
|
||||
### 0.14.0 <small>August 31, 2023</small>
|
||||
|
||||
- Added [#282](https://github.com/roboflow/supervision/pull/282): support for SAHI inference technique with [`sv.InferenceSlicer`](https://supervision.roboflow.com/detection/tools/inference_slicer).
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
## BoundingBoxAnnotator
|
||||
|
||||
:::supervision.annotators.core.BoundingBoxAnnotator
|
||||
|
||||
## MaskAnnotator
|
||||
|
||||
:::supervision.annotators.core.MaskAnnotator
|
||||
|
||||
## EllipseAnnotator
|
||||
|
||||
:::supervision.annotators.core.EllipseAnnotator
|
||||
|
||||
## BoxCornerAnnotator
|
||||
|
||||
:::supervision.annotators.core.BoxCornerAnnotator
|
||||
|
||||
## LabelAnnotator
|
||||
|
||||
:::supervision.annotators.core.LabelAnnotator
|
||||
@@ -0,0 +1,3 @@
|
||||
## LineZone
|
||||
|
||||
:::supervision.detection.line_counter.LineZone
|
||||
@@ -0,0 +1,7 @@
|
||||
## Color
|
||||
|
||||
:::supervision.draw.color.Color
|
||||
|
||||
## ColorPalette
|
||||
|
||||
:::supervision.draw.color.ColorPalette
|
||||
@@ -17,3 +17,7 @@
|
||||
## draw_text
|
||||
|
||||
:::supervision.draw.utils.draw_text
|
||||
|
||||
## draw_image
|
||||
|
||||
:::supervision.draw.utils.draw_image
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
With Supervision, you can easily [annotate](https://supervision.roboflow.com/annotators/) predictions obtained from a variety of object detection and segmentation models. This document outlines how to run inference using the [Ultralytics](https://github.com/ultralytics/ultralytics) YOLOv8 model, load these predictions into Supervision, and annotate the image.
|
||||
|
||||
## Run Inference
|
||||
|
||||
First, you'll need to obtain predictions from your object detection or segmentation model.
|
||||
```python
|
||||
import cv2
|
||||
from ultralytics import YOLO
|
||||
|
||||
model = YOLO("yolov8n.pt")
|
||||
image = cv2.imread("image.jpg")
|
||||
results = model(image)[0]
|
||||
```
|
||||
|
||||
## Load Predictions into Supervision
|
||||
|
||||
Now that we have predictions from a model, we can load them into Supervision. We can do so using the [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models.
|
||||
|
||||
```python
|
||||
import cv2
|
||||
from ultralytics import YOLO
|
||||
import supervision as sv
|
||||
|
||||
model = YOLO("yolov8n.pt")
|
||||
image = cv2.imread("image.jpg")
|
||||
results = model(image)[0]
|
||||
detections = sv.Detections.from_ultralytics(results)
|
||||
```
|
||||
|
||||
You can conveniently load predictions from other computer vision frameworks and libraries using:
|
||||
|
||||
- [`from_deepsparse`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_deepsparse) ([Deepsparse](https://github.com/neuralmagic/deepsparse))
|
||||
- [`from_detectron2`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_detectron2) ([Detectron2](https://github.com/facebookresearch/detectron2))
|
||||
- [`from_mmdetection`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_mmdetection) ([MMDetection](https://github.com/open-mmlab/mmdetection))
|
||||
- [`from_roboflow`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_roboflow) ([Roboflow Inference](https://github.com/roboflow/inference))
|
||||
- [`from_sam`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_sam) ([Segment Anything Model](https://github.com/facebookresearch/segment-anything))
|
||||
- [`from_transformers`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_transformers) ([HuggingFace Transformers](https://github.com/huggingface/transformers))
|
||||
- [`from_yolo_nas`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_yolo_nas) ([YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md))
|
||||
|
||||
|
||||
## Annotate Image
|
||||
|
||||
Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) and [`sv.LabelAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.LabelAnnotator) classes. If you are running the segmentation model [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) is a drop-in replacement for [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) that will allow you to draw masks instead of boxes.
|
||||
|
||||
```python
|
||||
import cv2
|
||||
from ultralytics import YOLO
|
||||
import supervision as sv
|
||||
|
||||
model = YOLO("yolov8n.pt")
|
||||
image = cv2.imread("image.jpg")
|
||||
results = model(image)[0]
|
||||
detections = sv.Detections.from_ultralytics(results)
|
||||
|
||||
bounding_box_annotator = sv.BoundingBoxAnnotator()
|
||||
label_annotator = sv.LabelAnnotator()
|
||||
|
||||
labels = [
|
||||
results.names[class_id]
|
||||
for class_id
|
||||
in detections.class_id
|
||||
]
|
||||
|
||||
annotated_image = bounding_box_annotator.annotate(
|
||||
scene=image, detections=detections)
|
||||
annotated_image = label_annotator.annotate(
|
||||
scene=annotated_image, detections=detections, labels=labels)
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Display Annotated Image
|
||||
|
||||
To display the annotated image in Jupyter Notebook or Google Colab, use the [`sv.plot_image`](https://supervision.roboflow.com/utils/notebook/#supervision.utils.notebook.plot_image) function.
|
||||
|
||||
```python
|
||||
sv.plot_image(annotated_image)
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
🚧 Page under construction.
|
||||
@@ -1,6 +1,3 @@
|
||||
## advanced filtering
|
||||
|
||||
|
||||
The advanced filtering capabilities of the `Detections` class offer users a versatile and efficient way to narrow down
|
||||
and refine object detections. This section outlines various filtering methods, including filtering by specific class
|
||||
or a set of classes, confidence, object area, bounding box area, relative area, box dimensions, and designated zones.
|
||||
@@ -0,0 +1 @@
|
||||
🚧 Page under construction.
|
||||
@@ -0,0 +1 @@
|
||||
🚧 Page under construction.
|
||||
+2
-1
@@ -46,6 +46,7 @@ You can install `supervision` with pip in a
|
||||
# setup python environment and activate it
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install --upgrade pip
|
||||
|
||||
# headless install
|
||||
pip install -e "."
|
||||
@@ -62,7 +63,7 @@ You can install `supervision` with pip in a
|
||||
cd supervision
|
||||
|
||||
# setup python environment and activate it
|
||||
poetry env use python 3.10
|
||||
poetry env use python3.10
|
||||
poetry shell
|
||||
|
||||
# headless install
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
:::supervision.utils.video.VideoSink
|
||||
|
||||
## FPSMonitor
|
||||
|
||||
:::supervision.utils.video.FPSMonitor
|
||||
|
||||
## get_video_frames_generator
|
||||
|
||||
:::supervision.utils.video.get_video_frames_generator
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
## Supervision Examples
|
||||
|
||||
This repository is packed with real-world use-cases, provided through Python scripts or
|
||||
interactive notebooks. Browse through to understand how the Supervision library
|
||||
interfaces with diverse applications.
|
||||
|
||||
| **Title** | **Contributor** |
|
||||
|:--------------------------------------:|:----------------------------------------:|
|
||||
| [Tracking](./tracking) | [@SkalskiP](https://github.com/SkalskiP) |
|
||||
| [Traffic Analysis](./traffic_analysis) | [@SkalskiP](https://github.com/SkalskiP) |
|
||||
|
||||
|
||||
## Guidelines for Contributing
|
||||
|
||||
We welcome contributions from the community in the form of examples, applications, and
|
||||
guides. To contribute, please follow these steps:
|
||||
|
||||
1. Create a pull request (PR) with the `[Example]` prefix in the title, adding your
|
||||
project folder to the `examples/` directory in the repository.
|
||||
2. Confirm your project aligns with the following standards:
|
||||
- Incorporates the `supervision` package.
|
||||
- Provides a `README.md` file, detailing the instructions to execute the project.
|
||||
- Showcases visual results, demonstrating the app's functionality.
|
||||
- Avoids adding large assets or dependencies unless absolutely necessary.
|
||||
- The contributor is expected to provide support for issues related to their
|
||||
examples.
|
||||
- In case the presented model has licensing complications, kindly specify them to
|
||||
circumvent potential misunderstandings.
|
||||
|
||||
For inquiries or concerns about these prerequisites, feel free to raise a PR. We are
|
||||
committed to assist and guide you.
|
||||
+17
-13
@@ -1,5 +1,5 @@
|
||||
site_name: Supervision
|
||||
site_url: https://roboflow.github.io/supervision
|
||||
site_url: https://supervision.roboflow.com/
|
||||
site_author: Roboflow
|
||||
site_description: A set of easy-to-use utils that will come in handy in any Computer Vision project
|
||||
repo_name: roboflow/supervision
|
||||
@@ -26,25 +26,29 @@ extra_css:
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Quickstart:
|
||||
- Detections: quickstart/detections.md
|
||||
- API reference:
|
||||
- How to:
|
||||
- Detect and Annotate: how_to/detect_and_annotate.md
|
||||
- Process Video: how_to/process_video.md
|
||||
- Track Objects: how_to/track_objects.md
|
||||
- Filter Detections: how_to/filter_detections.md
|
||||
- Evaluate Model: how_to/evaluate_model.md
|
||||
- API:
|
||||
- Classifications:
|
||||
- Core: classification/core.md
|
||||
- Detections:
|
||||
- Core: detection/core.md
|
||||
- Annotate: detection/annotate.md
|
||||
- Utils: detection/utils.md
|
||||
- Tools:
|
||||
- Polygon Zone: detection/tools/polygon_zone.md
|
||||
- Inference Slicer: detection/tools/inference_slicer.md
|
||||
- Trackers:
|
||||
- Core: tracker/core.md
|
||||
- Dataset:
|
||||
- Core: dataset/core.md
|
||||
- Tools:
|
||||
- Line Zone: detection/tools/line_zone.md
|
||||
- Polygon Zone: detection/tools/polygon_zone.md
|
||||
- Inference Slicer: detection/tools/inference_slicer.md
|
||||
- Annotators: annotators.md
|
||||
- Trackers: trackers.md
|
||||
- Datasets: datasets.md
|
||||
- Metrics:
|
||||
- Detection Models: metrics/detection.md
|
||||
- Object Detection: metrics/detection.md
|
||||
- Draw:
|
||||
- Color: draw/color.md
|
||||
- Utils: draw/utils.md
|
||||
- Utils:
|
||||
- Video: utils/video.md
|
||||
|
||||
Generated
+60
-72
@@ -2168,77 +2168,65 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "9.5.0"
|
||||
version = "10.0.1"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"},
|
||||
{file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"},
|
||||
{file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"},
|
||||
{file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"},
|
||||
{file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"},
|
||||
{file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"},
|
||||
{file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"},
|
||||
{file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"},
|
||||
{file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"},
|
||||
{file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"},
|
||||
{file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"},
|
||||
{file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"},
|
||||
{file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"},
|
||||
{file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"},
|
||||
{file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"},
|
||||
{file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"},
|
||||
{file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"},
|
||||
{file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"},
|
||||
{file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"},
|
||||
{file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"},
|
||||
{file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"},
|
||||
{file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"},
|
||||
{file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"},
|
||||
{file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"},
|
||||
{file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"},
|
||||
{file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"},
|
||||
{file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"},
|
||||
{file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"},
|
||||
{file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"},
|
||||
{file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"},
|
||||
{file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"},
|
||||
{file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"},
|
||||
{file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -3370,13 +3358,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.0.4"
|
||||
version = "2.0.7"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"},
|
||||
{file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"},
|
||||
{file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
|
||||
{file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -3532,4 +3520,4 @@ desktop = ["opencv-python"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.8,<3.12.0"
|
||||
content-hash = "fcefb11e72c40fa7defc13f4a9cb1fe24b9d3d171b64bb54fc263d8add9ef40f"
|
||||
content-hash = "beb692d89611ce85f8b2992f4e6da71fd14fa2ca75742ddae1a1c7b77df134f7"
|
||||
|
||||
+6
-5
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "supervision"
|
||||
version = "0.15.0rc2"
|
||||
version = "0.16.0"
|
||||
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
|
||||
authors = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
|
||||
maintainers = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
|
||||
@@ -13,7 +13,9 @@ documentation = "https://github.com/roboflow/supervision/blob/main/README.md"
|
||||
keywords = ["machine-learning", "deep-learning", "vision", "ML", "DL", "AI", "YOLOv5", "YOLOv8", "Roboflow"]
|
||||
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Education',
|
||||
'Intended Audience :: Science/Research',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 3',
|
||||
@@ -28,8 +30,7 @@ classifiers=[
|
||||
"Topic :: Scientific/Engineering :: Image Recognition",
|
||||
'Typing :: Typed',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: POSIX',
|
||||
'Operating System :: Unix',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Operating System :: MacOS'
|
||||
]
|
||||
|
||||
@@ -38,7 +39,7 @@ python = ">=3.8,<3.12.0"
|
||||
numpy = "^1.20.0"
|
||||
matplotlib = "^3.7.1"
|
||||
pyyaml = "^6.0"
|
||||
pillow = "^9.4.0"
|
||||
pillow = ">=9.4,<11.0"
|
||||
opencv-python = { version = "^4.8.0.74", optional = true }
|
||||
opencv-python-headless = "^4.8.0.74"
|
||||
scipy = "^1.9.0"
|
||||
@@ -175,7 +176,7 @@ max-args = 20
|
||||
include-package-data = false
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
exclude = ["docs*", "test*"]
|
||||
exclude = ["docs*", "test*","examples*"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
+18
-2
@@ -7,19 +7,27 @@ except importlib_metadata.PackageNotFoundError:
|
||||
__version__ = "development"
|
||||
|
||||
from supervision.annotators.core import (
|
||||
BlurAnnotator,
|
||||
BoundingBoxAnnotator,
|
||||
BoxCornerAnnotator,
|
||||
BoxMaskAnnotator,
|
||||
CircleAnnotator,
|
||||
DotAnnotator,
|
||||
EllipseAnnotator,
|
||||
HaloAnnotator,
|
||||
HeatMapAnnotator,
|
||||
LabelAnnotator,
|
||||
MaskAnnotator,
|
||||
TraceAnnotator,
|
||||
)
|
||||
from supervision.annotators.utils import ColorLookup
|
||||
from supervision.classification.core import Classifications
|
||||
from supervision.dataset.core import (
|
||||
BaseDataset,
|
||||
ClassificationDataset,
|
||||
DetectionDataset,
|
||||
)
|
||||
from supervision.detection.annotate import BoxAnnotator, TraceAnnotator
|
||||
from supervision.detection.annotate import BoxAnnotator
|
||||
from supervision.detection.core import Detections
|
||||
from supervision.detection.line_counter import LineZone, LineZoneAnnotator
|
||||
from supervision.detection.tools.inference_slicer import InferenceSlicer
|
||||
@@ -34,7 +42,14 @@ from supervision.detection.utils import (
|
||||
polygon_to_xyxy,
|
||||
)
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
from supervision.draw.utils import draw_filled_rectangle, draw_polygon, draw_text
|
||||
from supervision.draw.utils import (
|
||||
draw_filled_rectangle,
|
||||
draw_image,
|
||||
draw_line,
|
||||
draw_polygon,
|
||||
draw_rectangle,
|
||||
draw_text,
|
||||
)
|
||||
from supervision.geometry.core import Point, Position, Rect
|
||||
from supervision.geometry.utils import get_polygon_center
|
||||
from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision
|
||||
@@ -43,6 +58,7 @@ from supervision.utils.file import list_files_with_extensions
|
||||
from supervision.utils.image import ImageSink, crop_image
|
||||
from supervision.utils.notebook import plot_image, plot_images_grid
|
||||
from supervision.utils.video import (
|
||||
FPSMonitor,
|
||||
VideoInfo,
|
||||
VideoSink,
|
||||
get_video_frames_generator,
|
||||
|
||||
@@ -1,55 +1,8 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from supervision.detection.core import Detections
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
|
||||
|
||||
class ColorMap(Enum):
|
||||
"""
|
||||
Enum for annotator color mapping.
|
||||
"""
|
||||
|
||||
INDEX = "index"
|
||||
CLASS = "class"
|
||||
TRACK = "track"
|
||||
|
||||
|
||||
def resolve_color_idx(
|
||||
detections: Detections, detection_idx: int, color_map: ColorMap = ColorMap.CLASS
|
||||
) -> int:
|
||||
if detection_idx >= len(detections):
|
||||
raise ValueError(
|
||||
f"Detection index {detection_idx}"
|
||||
f"is out of bounds for detections of length {len(detections)}"
|
||||
)
|
||||
|
||||
if color_map == ColorMap.INDEX:
|
||||
return detection_idx
|
||||
elif color_map == ColorMap.CLASS:
|
||||
if detections.class_id is None:
|
||||
raise ValueError(
|
||||
"Could not resolve color by class because"
|
||||
"Detections do not have class_id"
|
||||
)
|
||||
return detections.class_id[detection_idx]
|
||||
elif color_map == ColorMap.TRACK:
|
||||
if detections.tracker_id is None:
|
||||
raise ValueError(
|
||||
"Could not resolve color by track because"
|
||||
"Detections do not have tracker_id"
|
||||
)
|
||||
return detections.tracker_id[detection_idx]
|
||||
|
||||
|
||||
def resolve_color(color: Union[Color, ColorPalette], idx: int) -> Color:
|
||||
if isinstance(color, ColorPalette):
|
||||
return color.by_idx(idx)
|
||||
else:
|
||||
return color
|
||||
|
||||
|
||||
class BaseAnnotator(ABC):
|
||||
|
||||
+681
-57
@@ -1,14 +1,11 @@
|
||||
from typing import List, Tuple, Union
|
||||
from math import sqrt
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision.annotators.base import (
|
||||
BaseAnnotator,
|
||||
ColorMap,
|
||||
resolve_color,
|
||||
resolve_color_idx,
|
||||
)
|
||||
from supervision.annotators.base import BaseAnnotator
|
||||
from supervision.annotators.utils import ColorLookup, Trace, resolve_color
|
||||
from supervision.detection.core import Detections
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
from supervision.geometry.core import Position
|
||||
@@ -23,27 +20,34 @@ class BoundingBoxAnnotator(BaseAnnotator):
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
thickness: int = 2,
|
||||
color_map: str = "class",
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
color (Union[Color, ColorPalette]): The color or color palette to use for
|
||||
annotating detections.
|
||||
thickness (int): Thickness of the bounding box lines.
|
||||
color_map (str): Strategy for mapping colors to annotations.
|
||||
Options are `index`, `class`, or `track`.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.thickness: int = thickness
|
||||
self.color_map: ColorMap = ColorMap(color_map)
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with bounding boxes based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where bounding boxes will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
@@ -63,16 +67,18 @@ class BoundingBoxAnnotator(BaseAnnotator):
|
||||
```
|
||||
|
||||

|
||||
supervision-annotator-examples/bounding-box-annotator-example-purple.png)
|
||||
"""
|
||||
for detection_idx in range(len(detections)):
|
||||
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
||||
idx = resolve_color_idx(
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_map=self.color_map,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
color = resolve_color(color=self.color, idx=idx)
|
||||
cv2.rectangle(
|
||||
img=scene,
|
||||
pt1=(x1, y1),
|
||||
@@ -92,27 +98,34 @@ class MaskAnnotator(BaseAnnotator):
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
opacity: float = 0.5,
|
||||
color_map: str = "class",
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
color (Union[Color, ColorPalette]): The color or color palette to use for
|
||||
annotating detections.
|
||||
opacity (float): Opacity of the overlay mask. Must be between `0` and `1`.
|
||||
color_map (str): Strategy for mapping colors to annotations.
|
||||
Options are `index`, `class`, or `track`.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.opacity = opacity
|
||||
self.color_map: ColorMap = ColorMap(color_map)
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with masks based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where masks will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
@@ -132,27 +145,201 @@ class MaskAnnotator(BaseAnnotator):
|
||||
```
|
||||
|
||||

|
||||
supervision-annotator-examples/mask-annotator-example-purple.png)
|
||||
"""
|
||||
if detections.mask is None:
|
||||
return scene
|
||||
|
||||
for detection_idx in np.flip(np.argsort(detections.area)):
|
||||
idx = resolve_color_idx(
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_map=self.color_map,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
color = resolve_color(color=self.color, idx=idx)
|
||||
mask = detections.mask[detection_idx]
|
||||
colored_mask = np.zeros_like(scene, dtype=np.uint8)
|
||||
colored_mask[:] = color.as_bgr()
|
||||
scene[mask] = cv2.addWeighted(
|
||||
colored_mask, self.opacity, scene, 1 - self.opacity, 0
|
||||
)[mask]
|
||||
|
||||
scene = np.where(
|
||||
np.expand_dims(mask, axis=-1),
|
||||
np.uint8(self.opacity * colored_mask + (1 - self.opacity) * scene),
|
||||
scene,
|
||||
return scene
|
||||
|
||||
|
||||
class BoxMaskAnnotator(BaseAnnotator):
|
||||
"""
|
||||
A class for drawing box masks on an image using provided detections.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
opacity: float = 0.5,
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
color (Union[Color, ColorPalette]): The color or color palette to use for
|
||||
annotating detections.
|
||||
opacity (float): Opacity of the overlay mask. Must be between `0` and `1`.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
self.opacity = opacity
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with box masks based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where bounding boxes will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> box_mask_annotator = sv.BoxMaskAnnotator()
|
||||
>>> annotated_frame = box_mask_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||

|
||||
"""
|
||||
mask_image = scene.copy()
|
||||
for detection_idx in range(len(detections)):
|
||||
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
cv2.rectangle(
|
||||
img=scene,
|
||||
pt1=(x1, y1),
|
||||
pt2=(x2, y2),
|
||||
color=color.as_bgr(),
|
||||
thickness=-1,
|
||||
)
|
||||
scene = cv2.addWeighted(
|
||||
scene, self.opacity, mask_image, 1 - self.opacity, gamma=0
|
||||
)
|
||||
return scene
|
||||
|
||||
|
||||
class HaloAnnotator(BaseAnnotator):
|
||||
"""
|
||||
A class for drawing Halos on an image using provided detections.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
opacity: float = 0.8,
|
||||
kernel_size: int = 40,
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
color (Union[Color, ColorPalette]): The color or color palette to use for
|
||||
annotating detections.
|
||||
opacity (float): Opacity of the overlay mask. Must be between `0` and `1`.
|
||||
kernel_size (int): The size of the average pooling kernel used for creating
|
||||
the halo.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.opacity = opacity
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
self.kernel_size: int = kernel_size
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with halos based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where masks will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> halo_annotator = sv.HaloAnnotator()
|
||||
>>> annotated_frame = halo_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||

|
||||
"""
|
||||
if detections.mask is None:
|
||||
return scene
|
||||
colored_mask = np.zeros_like(scene, dtype=np.uint8)
|
||||
fmask = np.array([False] * scene.shape[0] * scene.shape[1]).reshape(
|
||||
scene.shape[0], scene.shape[1]
|
||||
)
|
||||
|
||||
for detection_idx in np.flip(np.argsort(detections.area)):
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
mask = detections.mask[detection_idx]
|
||||
fmask = np.logical_or(fmask, mask)
|
||||
color_bgr = color.as_bgr()
|
||||
colored_mask[mask] = color_bgr
|
||||
|
||||
colored_mask = cv2.blur(colored_mask, (self.kernel_size, self.kernel_size))
|
||||
colored_mask[fmask] = [0, 0, 0]
|
||||
gray = cv2.cvtColor(colored_mask, cv2.COLOR_BGR2GRAY)
|
||||
alpha = self.opacity * gray / gray.max()
|
||||
alpha_mask = alpha[:, :, np.newaxis]
|
||||
scene = np.uint8(scene * (1 - alpha_mask) + colored_mask * self.opacity)
|
||||
return scene
|
||||
|
||||
|
||||
@@ -167,7 +354,7 @@ class EllipseAnnotator(BaseAnnotator):
|
||||
thickness: int = 2,
|
||||
start_angle: int = -45,
|
||||
end_angle: int = 235,
|
||||
color_map: str = "class",
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
@@ -176,22 +363,29 @@ class EllipseAnnotator(BaseAnnotator):
|
||||
thickness (int): Thickness of the ellipse lines.
|
||||
start_angle (int): Starting angle of the ellipse.
|
||||
end_angle (int): Ending angle of the ellipse.
|
||||
color_map (str): Strategy for mapping colors to annotations.
|
||||
Options are `index`, `class`, or `track`.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.thickness: int = thickness
|
||||
self.start_angle: int = start_angle
|
||||
self.end_angle: int = end_angle
|
||||
self.color_map: ColorMap = ColorMap(color_map)
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with ellipses based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where ellipses will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
@@ -211,17 +405,18 @@ class EllipseAnnotator(BaseAnnotator):
|
||||
```
|
||||
|
||||

|
||||
supervision-annotator-examples/ellipse-annotator-example-purple.png)
|
||||
"""
|
||||
for detection_idx in range(len(detections)):
|
||||
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
||||
idx = resolve_color_idx(
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_map=self.color_map,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
color = resolve_color(color=self.color, idx=idx)
|
||||
|
||||
center = (int((x1 + x2) / 2), y2)
|
||||
width = x2 - x1
|
||||
cv2.ellipse(
|
||||
@@ -247,8 +442,8 @@ class BoxCornerAnnotator(BaseAnnotator):
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
thickness: int = 4,
|
||||
corner_length: int = 25,
|
||||
color_map: str = "class",
|
||||
corner_length: int = 15,
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
@@ -256,21 +451,28 @@ class BoxCornerAnnotator(BaseAnnotator):
|
||||
annotating detections.
|
||||
thickness (int): Thickness of the corner lines.
|
||||
corner_length (int): Length of each corner line.
|
||||
color_map (str): Strategy for mapping colors to annotations.
|
||||
Options are `index`, `class`, or `track`.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.thickness: int = thickness
|
||||
self.corner_length: int = corner_length
|
||||
self.color_map: ColorMap = ColorMap(color_map)
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with box corners based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where box corners will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
@@ -290,16 +492,18 @@ class BoxCornerAnnotator(BaseAnnotator):
|
||||
```
|
||||
|
||||

|
||||
supervision-annotator-examples/box-corner-annotator-example-purple.png)
|
||||
"""
|
||||
for detection_idx in range(len(detections)):
|
||||
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
||||
idx = resolve_color_idx(
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_map=self.color_map,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
color = resolve_color(color=self.color, idx=idx)
|
||||
corners = [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]
|
||||
|
||||
for x, y in corners:
|
||||
@@ -315,6 +519,166 @@ class BoxCornerAnnotator(BaseAnnotator):
|
||||
return scene
|
||||
|
||||
|
||||
class CircleAnnotator(BaseAnnotator):
|
||||
"""
|
||||
A class for drawing circle on an image using provided detections.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
thickness: int = 2,
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
color (Union[Color, ColorPalette]): The color or color palette to use for
|
||||
annotating detections.
|
||||
thickness (int): Thickness of the circle line.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.thickness: int = thickness
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with circles based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where box corners will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> circle_annotator = sv.CircleAnnotator()
|
||||
>>> annotated_frame = circle_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||
|
||||

|
||||
"""
|
||||
for detection_idx in range(len(detections)):
|
||||
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
||||
center = ((x1 + x2) // 2, (y1 + y2) // 2)
|
||||
distance = sqrt((x1 - center[0]) ** 2 + (y1 - center[1]) ** 2)
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
cv2.circle(
|
||||
img=scene,
|
||||
center=center,
|
||||
radius=int(distance),
|
||||
color=color.as_bgr(),
|
||||
thickness=self.thickness,
|
||||
)
|
||||
|
||||
return scene
|
||||
|
||||
|
||||
class DotAnnotator(BaseAnnotator):
|
||||
"""
|
||||
A class for drawing dots on an image at specific coordinates based on provided
|
||||
detections.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
radius: int = 4,
|
||||
position: Position = Position.CENTER,
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
color (Union[Color, ColorPalette]): The color or color palette to use for
|
||||
annotating detections.
|
||||
radius (int): Radius of the drawn dots.
|
||||
position (Position): The anchor position for placing the dot.
|
||||
color_lookup (ColorLookup): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.radius: int = radius
|
||||
self.position: Position = position
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with dots based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where dots will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> dot_annotator = sv.DotAnnotator()
|
||||
>>> annotated_frame = dot_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||

|
||||
"""
|
||||
xy = detections.get_anchor_coordinates(anchor=self.position)
|
||||
for detection_idx in range(len(detections)):
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
center = (int(xy[detection_idx, 0]), int(xy[detection_idx, 1]))
|
||||
cv2.circle(scene, center, self.radius, color.as_bgr(), -1)
|
||||
return scene
|
||||
|
||||
|
||||
class LabelAnnotator:
|
||||
"""
|
||||
A class for annotating labels on an image using provided detections.
|
||||
@@ -328,7 +692,7 @@ class LabelAnnotator:
|
||||
text_thickness: int = 1,
|
||||
text_padding: int = 10,
|
||||
text_position: Position = Position.TOP_LEFT,
|
||||
color_map: str = "class",
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
@@ -340,8 +704,8 @@ class LabelAnnotator:
|
||||
text_padding (int): Padding around the text within its background box.
|
||||
text_position (Position): Position of the text relative to the detection.
|
||||
Possible values are defined in the `Position` enum.
|
||||
color_map (str): Strategy for mapping colors to annotations.
|
||||
Options are `index`, `class`, or `track`.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.text_color: Color = text_color
|
||||
@@ -349,7 +713,7 @@ class LabelAnnotator:
|
||||
self.text_thickness: int = text_thickness
|
||||
self.text_padding: int = text_padding
|
||||
self.text_position: Position = text_position
|
||||
self.color_map: ColorMap = ColorMap(color_map)
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
@staticmethod
|
||||
def resolve_text_background_xyxy(
|
||||
@@ -398,6 +762,7 @@ class LabelAnnotator:
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
labels: List[str] = None,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with labels based on the provided detections.
|
||||
@@ -406,6 +771,8 @@ class LabelAnnotator:
|
||||
scene (np.ndarray): The image where labels will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
labels (List[str]): Optional. Custom labels for each detection.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
@@ -425,17 +792,19 @@ class LabelAnnotator:
|
||||
```
|
||||
|
||||

|
||||
supervision-annotator-examples/label-annotator-example-purple.png)
|
||||
"""
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
for detection_idx in range(len(detections)):
|
||||
detection_xyxy = detections.xyxy[detection_idx].astype(int)
|
||||
idx = resolve_color_idx(
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_map=self.color_map,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
color = resolve_color(color=self.color, idx=idx)
|
||||
text = (
|
||||
f"{detections.class_id[detection_idx]}"
|
||||
if (labels is None or len(detections) != len(labels))
|
||||
@@ -476,3 +845,258 @@ class LabelAnnotator:
|
||||
lineType=cv2.LINE_AA,
|
||||
)
|
||||
return scene
|
||||
|
||||
|
||||
class BlurAnnotator(BaseAnnotator):
|
||||
"""
|
||||
A class for blurring regions in an image using provided detections.
|
||||
"""
|
||||
|
||||
def __init__(self, kernel_size: int = 15):
|
||||
"""
|
||||
Args:
|
||||
kernel_size (int): The size of the average pooling kernel used for blurring.
|
||||
"""
|
||||
self.kernel_size: int = kernel_size
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene by blurring regions based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where blurring will be applied.
|
||||
detections (Detections): Object detections to annotate.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The annotated image.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> blur_annotator = sv.BlurAnnotator()
|
||||
>>> annotated_frame = blur_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
|
||||

|
||||
"""
|
||||
for detection_idx in range(len(detections)):
|
||||
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
||||
roi = scene[y1:y2, x1:x2]
|
||||
|
||||
roi = cv2.blur(roi, (self.kernel_size, self.kernel_size))
|
||||
scene[y1:y2, x1:x2] = roi
|
||||
|
||||
return scene
|
||||
|
||||
|
||||
class TraceAnnotator:
|
||||
"""
|
||||
A class for drawing trace paths on an image based on detection coordinates.
|
||||
|
||||
!!! warning
|
||||
|
||||
This annotator utilizes the `tracker_id`. Read
|
||||
[here](https://supervision.roboflow.com/trackers/) to learn how to plug
|
||||
tracking into your inference pipeline.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
position: Position = Position.CENTER,
|
||||
trace_length: int = 30,
|
||||
thickness: int = 2,
|
||||
color_lookup: ColorLookup = ColorLookup.CLASS,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
color (Union[Color, ColorPalette]): The color to draw the trace, can be
|
||||
a single color or a color palette.
|
||||
position (Position): The position of the trace.
|
||||
Defaults to `CENTER`.
|
||||
trace_length (int): The maximum length of the trace in terms of historical
|
||||
points. Defaults to `30`.
|
||||
thickness (int): The thickness of the trace lines. Defaults to `2`.
|
||||
color_lookup (str): Strategy for mapping colors to annotations.
|
||||
Options are `INDEX`, `CLASS`, `TRACE`.
|
||||
"""
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.position = position
|
||||
self.trace = Trace(max_size=trace_length)
|
||||
self.thickness = thickness
|
||||
self.color_lookup: ColorLookup = color_lookup
|
||||
|
||||
def annotate(
|
||||
self,
|
||||
scene: np.ndarray,
|
||||
detections: Detections,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Draws trace paths on the frame based on the detection coordinates provided.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image on which the traces will be drawn.
|
||||
detections (Detections): The detections which include coordinates for
|
||||
which the traces will be drawn.
|
||||
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
|
||||
Allows to override the default color mapping strategy.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The image with the trace paths drawn on it.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
>>> from ultralytics import YOLO
|
||||
|
||||
>>> model = YOLO('yolov8x.pt')
|
||||
|
||||
>>> trace_annotator = sv.TraceAnnotator()
|
||||
|
||||
>>> video_info = sv.VideoInfo.from_video_path(video_path='...')
|
||||
>>> frames_generator = sv.get_video_frames_generator(source_path='...')
|
||||
>>> tracker = sv.ByteTrack()
|
||||
|
||||
>>> with sv.VideoSink(target_path='...', video_info=video_info) as sink:
|
||||
... for frame in frames_generator:
|
||||
... result = model(frame)[0]
|
||||
... detections = sv.Detections.from_ultralytics(result)
|
||||
... detections = tracker.update_with_detections(detections)
|
||||
... annotated_frame = trace_annotator.annotate(
|
||||
... scene=frame.copy(),
|
||||
... detections=detections)
|
||||
... sink.write_frame(frame=annotated_frame)
|
||||
```
|
||||
|
||||

|
||||
"""
|
||||
self.trace.put(detections)
|
||||
|
||||
for detection_idx in range(len(detections)):
|
||||
tracker_id = int(detections.tracker_id[detection_idx])
|
||||
color = resolve_color(
|
||||
color=self.color,
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_lookup=self.color_lookup
|
||||
if custom_color_lookup is None
|
||||
else custom_color_lookup,
|
||||
)
|
||||
xy = self.trace.get(tracker_id=tracker_id)
|
||||
if len(xy) > 1:
|
||||
scene = cv2.polylines(
|
||||
scene,
|
||||
[xy.astype(np.int32)],
|
||||
False,
|
||||
color=color.as_bgr(),
|
||||
thickness=self.thickness,
|
||||
)
|
||||
return scene
|
||||
|
||||
|
||||
class HeatMapAnnotator:
|
||||
"""
|
||||
A class for drawing heatmaps on an image based on provided detections.
|
||||
Heat accumulates over time and is drawn as a semi-transparent overlay
|
||||
of blurred circles.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
position: Position = Position.BOTTOM_CENTER,
|
||||
opacity: float = 0.2,
|
||||
radius: int = 40,
|
||||
kernel_size: int = 25,
|
||||
top_hue: int = 0,
|
||||
low_hue: int = 125,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
position (Position): The position of the heatmap. Defaults to
|
||||
`BOTTOM_CENTER`.
|
||||
opacity (float): Opacity of the overlay mask, between 0 and 1.
|
||||
radius (int): Radius of the heat circle.
|
||||
kernel_size (int): Kernel size for blurring the heatmap.
|
||||
top_hue (int): Hue at the top of the heatmap. Defaults to 0 (red).
|
||||
low_hue (int): Hue at the bottom of the heatmap. Defaults to 125 (blue).
|
||||
"""
|
||||
self.position = position
|
||||
self.opacity = opacity
|
||||
self.radius = radius
|
||||
self.kernel_size = kernel_size
|
||||
self.heat_mask = None
|
||||
self.top_hue = top_hue
|
||||
self.low_hue = low_hue
|
||||
|
||||
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
|
||||
"""
|
||||
Annotates the scene with a heatmap based on the provided detections.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image where the heatmap will be drawn.
|
||||
detections (Detections): Object detections to annotate.
|
||||
|
||||
Returns:
|
||||
np.ndarray: Annotated image.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
>>> from ultralytics import YOLO
|
||||
|
||||
>>> model = YOLO('yolov8x.pt')
|
||||
|
||||
>>> heat_map_annotator = sv.HeatMapAnnotator()
|
||||
|
||||
>>> video_info = sv.VideoInfo.from_video_path(video_path='...')
|
||||
>>> frames_generator = get_video_frames_generator(source_path='...')
|
||||
|
||||
>>> with sv.VideoSink(target_path='...', video_info=video_info) as sink:
|
||||
... for frame in frames_generator:
|
||||
... result = model(frame)[0]
|
||||
... detections = sv.Detections.from_ultralytics(result)
|
||||
... annotated_frame = heat_map_annotator.annotate(
|
||||
... scene=frame.copy(),
|
||||
... detections=detections)
|
||||
... sink.write_frame(frame=annotated_frame)
|
||||
```
|
||||
|
||||

|
||||
"""
|
||||
|
||||
if self.heat_mask is None:
|
||||
self.heat_mask = np.zeros(scene.shape[:2])
|
||||
mask = np.zeros(scene.shape[:2])
|
||||
for xy in detections.get_anchor_coordinates(self.position):
|
||||
cv2.circle(mask, (int(xy[0]), int(xy[1])), self.radius, 1, -1)
|
||||
self.heat_mask = mask + self.heat_mask
|
||||
temp = self.heat_mask.copy()
|
||||
temp = self.low_hue - temp / temp.max() * (self.low_hue - self.top_hue)
|
||||
temp = temp.astype(np.uint8)
|
||||
if self.kernel_size is not None:
|
||||
temp = cv2.blur(temp, (self.kernel_size, self.kernel_size))
|
||||
hsv = np.zeros(scene.shape)
|
||||
hsv[..., 0] = temp
|
||||
hsv[..., 1] = 255
|
||||
hsv[..., 2] = 255
|
||||
temp = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
|
||||
mask = cv2.cvtColor(self.heat_mask.astype(np.uint8), cv2.COLOR_GRAY2BGR) > 0
|
||||
scene[mask] = cv2.addWeighted(temp, self.opacity, scene, 1 - self.opacity, 0)[
|
||||
mask
|
||||
]
|
||||
return scene
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
from enum import Enum
|
||||
from typing import Optional, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
from supervision.detection.core import Detections
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
from supervision.geometry.core import Position
|
||||
|
||||
|
||||
class ColorLookup(Enum):
|
||||
"""
|
||||
Enumeration class to define strategies for mapping colors to annotations.
|
||||
|
||||
This enum supports three different lookup strategies:
|
||||
- `INDEX`: Colors are determined by the index of the detection within the scene.
|
||||
- `CLASS`: Colors are determined by the class label of the detected object.
|
||||
- `TRACK`: Colors are determined by the tracking identifier of the object.
|
||||
"""
|
||||
|
||||
INDEX = "index"
|
||||
CLASS = "class"
|
||||
TRACK = "track"
|
||||
|
||||
@classmethod
|
||||
def list(cls):
|
||||
return list(map(lambda c: c.value, cls))
|
||||
|
||||
|
||||
def resolve_color_idx(
|
||||
detections: Detections,
|
||||
detection_idx: int,
|
||||
color_lookup: Union[ColorLookup, np.ndarray] = ColorLookup.CLASS,
|
||||
) -> int:
|
||||
if detection_idx >= len(detections):
|
||||
raise ValueError(
|
||||
f"Detection index {detection_idx}"
|
||||
f"is out of bounds for detections of length {len(detections)}"
|
||||
)
|
||||
|
||||
if isinstance(color_lookup, np.ndarray):
|
||||
if len(color_lookup) != len(detections):
|
||||
raise ValueError(
|
||||
f"Length of color lookup {len(color_lookup)}"
|
||||
f"does not match length of detections {len(detections)}"
|
||||
)
|
||||
return color_lookup[detection_idx]
|
||||
elif color_lookup == ColorLookup.INDEX:
|
||||
return detection_idx
|
||||
elif color_lookup == ColorLookup.CLASS:
|
||||
if detections.class_id is None:
|
||||
raise ValueError(
|
||||
"Could not resolve color by class because"
|
||||
"Detections do not have class_id"
|
||||
)
|
||||
return detections.class_id[detection_idx]
|
||||
elif color_lookup == ColorLookup.TRACK:
|
||||
if detections.tracker_id is None:
|
||||
raise ValueError(
|
||||
"Could not resolve color by track because"
|
||||
"Detections do not have tracker_id"
|
||||
)
|
||||
return detections.tracker_id[detection_idx]
|
||||
|
||||
|
||||
def get_color_by_index(color: Union[Color, ColorPalette], idx: int) -> Color:
|
||||
if isinstance(color, ColorPalette):
|
||||
return color.by_idx(idx)
|
||||
return color
|
||||
|
||||
|
||||
def resolve_color(
|
||||
color: Union[Color, ColorPalette],
|
||||
detections: Detections,
|
||||
detection_idx: int,
|
||||
color_lookup: Union[ColorLookup, np.ndarray] = ColorLookup.CLASS,
|
||||
) -> Color:
|
||||
idx = resolve_color_idx(
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_lookup=color_lookup,
|
||||
)
|
||||
return get_color_by_index(color=color, idx=idx)
|
||||
|
||||
|
||||
class Trace:
|
||||
def __init__(
|
||||
self,
|
||||
max_size: Optional[int] = None,
|
||||
start_frame_id: int = 0,
|
||||
anchor: Position = Position.CENTER,
|
||||
) -> None:
|
||||
self.current_frame_id = start_frame_id
|
||||
self.max_size = max_size
|
||||
self.anchor = anchor
|
||||
|
||||
self.frame_id = np.array([], dtype=int)
|
||||
self.xy = np.empty((0, 2), dtype=np.float32)
|
||||
self.tracker_id = np.array([], dtype=int)
|
||||
|
||||
def put(self, detections: Detections) -> None:
|
||||
frame_id = np.full(len(detections), self.current_frame_id, dtype=int)
|
||||
self.frame_id = np.concatenate([self.frame_id, frame_id])
|
||||
self.xy = np.concatenate(
|
||||
[self.xy, detections.get_anchor_coordinates(self.anchor)]
|
||||
)
|
||||
self.tracker_id = np.concatenate([self.tracker_id, detections.tracker_id])
|
||||
|
||||
unique_frame_id = np.unique(self.frame_id)
|
||||
|
||||
if 0 < self.max_size < len(unique_frame_id):
|
||||
max_allowed_frame_id = self.current_frame_id - self.max_size + 1
|
||||
filtering_mask = self.frame_id >= max_allowed_frame_id
|
||||
self.frame_id = self.frame_id[filtering_mask]
|
||||
self.xy = self.xy[filtering_mask]
|
||||
self.tracker_id = self.tracker_id[filtering_mask]
|
||||
|
||||
self.current_frame_id += 1
|
||||
|
||||
def get(self, tracker_id: int) -> np.ndarray:
|
||||
return self.xy[self.tracker_id == tracker_id]
|
||||
@@ -5,8 +5,6 @@ from typing import Any, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from supervision.utils.internal import deprecated
|
||||
|
||||
|
||||
def _validate_class_ids(class_id: Any, n: int) -> None:
|
||||
"""
|
||||
@@ -41,41 +39,6 @@ class Classifications:
|
||||
_validate_class_ids(self.class_id, n)
|
||||
_validate_confidence(self.confidence, n)
|
||||
|
||||
@classmethod
|
||||
@deprecated(
|
||||
"""
|
||||
This method is deprecated and removed in 0.16.0 release.
|
||||
Use sv.Classifications.from_ultralytics() instead as it is more generic and
|
||||
can be used for detections from any ultralytics.engine.results.Results Object
|
||||
"""
|
||||
)
|
||||
def from_yolov8(cls, yolov8_results) -> Classifications:
|
||||
"""
|
||||
Creates a Classifications instance from a
|
||||
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
|
||||
|
||||
Args:
|
||||
yolov8_results (ultralytics.yolo.engine.results.Results):
|
||||
The output Results instance from YOLOv8
|
||||
|
||||
Returns:
|
||||
Classifications: A new Classifications object.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import cv2
|
||||
>>> from ultralytics import YOLO
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
|
||||
>>> model = YOLO('yolov8s-cls.pt')
|
||||
>>> result = model(image)[0]
|
||||
>>> classifications = sv.Classifications.from_yolov8(result)
|
||||
```
|
||||
"""
|
||||
confidence = yolov8_results.probs.data.cpu().numpy()
|
||||
return cls(class_id=np.arange(confidence.shape[0]), confidence=confidence)
|
||||
|
||||
@classmethod
|
||||
def from_ultralytics(cls, ultralytics_results) -> Classifications:
|
||||
"""
|
||||
@@ -84,7 +47,7 @@ class Classifications:
|
||||
|
||||
Args:
|
||||
ultralytics_results (ultralytics.engine.results.Results):
|
||||
The output Results instance from ultralytics model
|
||||
The output Results instance from ultralytics model
|
||||
|
||||
Returns:
|
||||
Classifications: A new Classifications object.
|
||||
|
||||
@@ -165,9 +165,8 @@ class DetectionDataset(BaseDataset):
|
||||
where the images should be saved.
|
||||
If not provided, images will not be saved.
|
||||
annotations_directory_path (Optional[str]): The path to
|
||||
the directory where the annotations in
|
||||
PASCAL VOC format should be saved. If not provided,
|
||||
annotations will not be saved.
|
||||
the directory where the annotations in PASCAL VOC format should be
|
||||
saved. If not provided, annotations will not be saved.
|
||||
min_image_area_percentage (float): The minimum percentage of
|
||||
detection area relative to
|
||||
the image area for a detection to be included.
|
||||
@@ -178,7 +177,7 @@ class DetectionDataset(BaseDataset):
|
||||
Argument is used only for segmentation datasets.
|
||||
approximation_percentage (float): The percentage of
|
||||
polygon points to be removed from the input polygon,
|
||||
in the range [0, 1). Argument is used only for segmentation datasets.
|
||||
in the range [0, 1). Argument is used only for segmentation datasets.
|
||||
"""
|
||||
if images_directory_path:
|
||||
save_dataset_images(
|
||||
@@ -563,8 +562,7 @@ class ClassificationDataset(BaseDataset):
|
||||
split_ratio (float, optional): The ratio of the training
|
||||
set to the entire dataset.
|
||||
random_state (int, optional): The seed for the
|
||||
random number generator.
|
||||
This is used for reproducibility.
|
||||
random number generator. This is used for reproducibility.
|
||||
shuffle (bool, optional): Whether to shuffle the data before splitting.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -5,7 +5,6 @@ import numpy as np
|
||||
|
||||
from supervision.detection.core import Detections
|
||||
from supervision.draw.color import Color, ColorPalette
|
||||
from supervision.geometry.core import Position
|
||||
|
||||
|
||||
class BoxAnnotator:
|
||||
@@ -145,116 +144,3 @@ class BoxAnnotator:
|
||||
lineType=cv2.LINE_AA,
|
||||
)
|
||||
return scene
|
||||
|
||||
|
||||
class Trace:
|
||||
def __init__(
|
||||
self,
|
||||
max_size: Optional[int] = None,
|
||||
start_frame_id: int = 0,
|
||||
anchor: Position = Position.CENTER,
|
||||
) -> None:
|
||||
self.current_frame_id = start_frame_id
|
||||
self.max_size = max_size
|
||||
self.anchor = anchor
|
||||
|
||||
self.frame_id = np.array([], dtype=int)
|
||||
self.xy = np.empty((0, 2), dtype=np.float32)
|
||||
self.tracker_id = np.array([], dtype=int)
|
||||
|
||||
def put(self, detections: Detections) -> None:
|
||||
frame_id = np.full(len(detections), self.current_frame_id, dtype=int)
|
||||
self.frame_id = np.concatenate([self.frame_id, frame_id])
|
||||
self.xy = np.concatenate(
|
||||
[self.xy, detections.get_anchor_coordinates(self.anchor)]
|
||||
)
|
||||
self.tracker_id = np.concatenate([self.tracker_id, detections.tracker_id])
|
||||
|
||||
unique_frame_id = np.unique(self.frame_id)
|
||||
|
||||
if 0 < self.max_size < len(unique_frame_id):
|
||||
max_allowed_frame_id = self.current_frame_id - self.max_size + 1
|
||||
filtering_mask = self.frame_id >= max_allowed_frame_id
|
||||
self.frame_id = self.frame_id[filtering_mask]
|
||||
self.xy = self.xy[filtering_mask]
|
||||
self.tracker_id = self.tracker_id[filtering_mask]
|
||||
|
||||
self.current_frame_id += 1
|
||||
|
||||
def get(self, tracker_id: int) -> np.ndarray:
|
||||
return self.xy[self.tracker_id == tracker_id]
|
||||
|
||||
|
||||
class TraceAnnotator:
|
||||
"""
|
||||
A class for drawing trace paths on an image based on detection coordinates.
|
||||
|
||||
Attributes:
|
||||
color (Union[Color, ColorPalette]): The color to draw the trace, can be
|
||||
a single color or a color palette.
|
||||
position (Optional[Position]): The position of the trace. Defaults to `CENTER`.
|
||||
trace_length (int): The maximum length of the trace in terms of historical
|
||||
points. Defaults to `30`.
|
||||
thickness (int): The thickness of the trace lines. Defaults to `2`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
position: Optional[Position] = Position.CENTER,
|
||||
trace_length: int = 30,
|
||||
thickness: int = 2,
|
||||
):
|
||||
self.color: Union[Color, ColorPalette] = color
|
||||
self.position = position
|
||||
self.trace = Trace(max_size=trace_length)
|
||||
self.thickness = thickness
|
||||
|
||||
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
|
||||
"""
|
||||
Draws trace paths on the frame based on the detection coordinates provided.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): The image on which the traces will be drawn.
|
||||
detections (Detections): The detections which include coordinates for
|
||||
which the traces will be drawn.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The image with the trace paths drawn on it.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
|
||||
>>> trace_annotator = sv.TraceAnnotator()
|
||||
>>> annotated_frame = trace_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
```
|
||||
"""
|
||||
self.trace.put(detections)
|
||||
|
||||
for i, (xyxy, mask, confidence, class_id, tracker_id) in enumerate(detections):
|
||||
class_id = detections.class_id[i] if class_id is not None else None
|
||||
idx = class_id if class_id is not None else i
|
||||
color = (
|
||||
self.color.by_idx(idx)
|
||||
if isinstance(self.color, ColorPalette)
|
||||
else self.color
|
||||
)
|
||||
|
||||
xy = self.trace.get(tracker_id=tracker_id)
|
||||
if len(xy) > 1:
|
||||
scene = cv2.polylines(
|
||||
scene,
|
||||
[xy.astype(np.int32)],
|
||||
False,
|
||||
color=color.as_bgr(),
|
||||
thickness=self.thickness,
|
||||
)
|
||||
return scene
|
||||
|
||||
@@ -12,7 +12,6 @@ from supervision.detection.utils import (
|
||||
xywh_to_xyxy,
|
||||
)
|
||||
from supervision.geometry.core import Position
|
||||
from supervision.utils.internal import deprecated
|
||||
|
||||
|
||||
def _validate_xyxy(xyxy: Any, n: int) -> None:
|
||||
@@ -185,46 +184,6 @@ class Detections:
|
||||
class_id=yolov5_detections_predictions[:, 5].astype(int),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@deprecated(
|
||||
"""
|
||||
This method is deprecated and removed in 0.15.0 release.
|
||||
Use sv.Detections.from_ultralytics() instead as it is more generic and
|
||||
can be used for detections from any ultralytics.engine.results.Results Object
|
||||
"""
|
||||
)
|
||||
def from_yolov8(cls, yolov8_results) -> Detections:
|
||||
"""
|
||||
Creates a Detections instance from a
|
||||
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
|
||||
|
||||
Args:
|
||||
yolov8_results (ultralytics.yolo.engine.results.Results):
|
||||
The output Results instance from YOLOv8
|
||||
|
||||
Returns:
|
||||
Detections: A new Detections object.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> import cv2
|
||||
>>> from ultralytics import YOLO
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
|
||||
>>> model = YOLO('yolov8s.pt')
|
||||
>>> result = model(image)[0]
|
||||
>>> detections = sv.Detections.from_yolov8(result)
|
||||
```
|
||||
"""
|
||||
|
||||
return cls(
|
||||
xyxy=yolov8_results.boxes.xyxy.cpu().numpy(),
|
||||
confidence=yolov8_results.boxes.conf.cpu().numpy(),
|
||||
class_id=yolov8_results.boxes.cls.cpu().numpy().astype(int),
|
||||
mask=extract_ultralytics_masks(yolov8_results),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_ultralytics(cls, ultralytics_results) -> Detections:
|
||||
"""
|
||||
@@ -296,7 +255,7 @@ class Detections:
|
||||
>>> detections = sv.Detections.from_yolo_nas(result)
|
||||
```
|
||||
"""
|
||||
if np.asarray(yolo_nas_results.bboxes_xyxy).shape[0] == 0:
|
||||
if np.asarray(yolo_nas_results.prediction.bboxes_xyxy).shape[0] == 0:
|
||||
return cls.empty()
|
||||
|
||||
return cls(
|
||||
@@ -432,7 +391,7 @@ class Detections:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_roboflow(cls, roboflow_result: dict, class_list: List[str]) -> Detections:
|
||||
def from_roboflow(cls, roboflow_result: dict) -> Detections:
|
||||
"""
|
||||
Create a Detections object from the [Roboflow](https://roboflow.com/)
|
||||
API inference result.
|
||||
@@ -440,8 +399,6 @@ class Detections:
|
||||
Args:
|
||||
roboflow_result (dict): The result from the
|
||||
Roboflow API containing predictions.
|
||||
class_list (List[str]): A list of class names
|
||||
corresponding to the class IDs in the API result.
|
||||
|
||||
Returns:
|
||||
(Detections): A Detections object containing the bounding boxes, class IDs,
|
||||
@@ -458,19 +415,19 @@ class Detections:
|
||||
... "y": 0.5,
|
||||
... "width": 0.2,
|
||||
... "height": 0.3,
|
||||
... "class_id": 0,
|
||||
... "class": "person",
|
||||
... "confidence": 0.9
|
||||
... },
|
||||
... # ... more predictions ...
|
||||
... ]
|
||||
... }
|
||||
>>> class_list = ["person", "car", "dog"]
|
||||
|
||||
>>> detections = sv.Detections.from_roboflow(roboflow_result, class_list)
|
||||
>>> detections = sv.Detections.from_roboflow(roboflow_result)
|
||||
```
|
||||
"""
|
||||
xyxy, confidence, class_id, masks = process_roboflow_result(
|
||||
roboflow_result=roboflow_result, class_list=class_list
|
||||
xyxy, confidence, class_id, masks, trackers = process_roboflow_result(
|
||||
roboflow_result=roboflow_result
|
||||
)
|
||||
|
||||
if np.asarray(xyxy).shape[0] == 0:
|
||||
@@ -481,6 +438,7 @@ class Detections:
|
||||
confidence=confidence,
|
||||
class_id=class_id,
|
||||
mask=masks,
|
||||
tracker_id=trackers,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -10,37 +10,54 @@ from supervision.geometry.core import Point, Rect, Vector
|
||||
|
||||
class LineZone:
|
||||
"""
|
||||
Count the number of objects that cross a line.
|
||||
This class is responsible for counting the number of objects that cross a
|
||||
predefined line.
|
||||
|
||||
!!! warning
|
||||
|
||||
LineZone utilizes the `tracker_id`. Read
|
||||
[here](https://supervision.roboflow.com/trackers/) to learn how to plug
|
||||
tracking into your inference pipeline.
|
||||
|
||||
Attributes:
|
||||
in_count (int): The number of objects that have crossed the line from outside
|
||||
to inside.
|
||||
out_count (int): The number of objects that have crossed the line from inside
|
||||
to outside.
|
||||
"""
|
||||
|
||||
def __init__(self, start: Point, end: Point):
|
||||
"""
|
||||
Initialize a LineCounter object.
|
||||
|
||||
Attributes:
|
||||
Args:
|
||||
start (Point): The starting point of the line.
|
||||
end (Point): The ending point of the line.
|
||||
|
||||
"""
|
||||
self.vector = Vector(start=start, end=end)
|
||||
self.tracker_state: Dict[str, bool] = {}
|
||||
self.in_count: int = 0
|
||||
self.out_count: int = 0
|
||||
|
||||
def trigger(self, detections: Detections):
|
||||
def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Update the in_count and out_count for the detections that cross the line.
|
||||
Update the `in_count` and `out_count` based on the objects that cross the line.
|
||||
|
||||
Attributes:
|
||||
detections (Detections): The detections for which to update the counts.
|
||||
Args:
|
||||
detections (Detections): A list of detections for which to update the
|
||||
counts.
|
||||
|
||||
Returns:
|
||||
A tuple of two boolean NumPy arrays. The first array indicates which
|
||||
detections have crossed the line from outside to inside. The second
|
||||
array indicates which detections have crossed the line from inside to
|
||||
outside.
|
||||
"""
|
||||
for xyxy, _, confidence, class_id, tracker_id in detections:
|
||||
# handle detections with no tracker_id
|
||||
crossed_in = np.full(len(detections), False)
|
||||
crossed_out = np.full(len(detections), False)
|
||||
|
||||
for i, (xyxy, _, confidence, class_id, tracker_id) in enumerate(detections):
|
||||
if tracker_id is None:
|
||||
continue
|
||||
|
||||
# we check if all four anchors of bbox are on the same side of vector
|
||||
x1, y1, x2, y2 = xyxy
|
||||
anchors = [
|
||||
Point(x=x1, y=y1),
|
||||
@@ -50,25 +67,27 @@ class LineZone:
|
||||
]
|
||||
triggers = [self.vector.is_in(point=anchor) for anchor in anchors]
|
||||
|
||||
# detection is partially in and partially out
|
||||
if len(set(triggers)) == 2:
|
||||
continue
|
||||
|
||||
tracker_state = triggers[0]
|
||||
# handle new detection
|
||||
|
||||
if tracker_id not in self.tracker_state:
|
||||
self.tracker_state[tracker_id] = tracker_state
|
||||
continue
|
||||
|
||||
# handle detection on the same side of the line
|
||||
if self.tracker_state.get(tracker_id) == tracker_state:
|
||||
continue
|
||||
|
||||
self.tracker_state[tracker_id] = tracker_state
|
||||
if tracker_state:
|
||||
self.in_count += 1
|
||||
crossed_in[i] = True
|
||||
else:
|
||||
self.out_count += 1
|
||||
crossed_out[i] = True
|
||||
|
||||
return crossed_in, crossed_out
|
||||
|
||||
|
||||
class LineZoneAnnotator:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from typing import Callable, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
@@ -28,15 +29,16 @@ class InferenceSlicer:
|
||||
involves dividing a larger image into smaller slices, performing inference on each
|
||||
slice, and then merging the detections.
|
||||
|
||||
Attributes:
|
||||
Args:
|
||||
slice_wh (Tuple[int, int]): Dimensions of each slice in the format
|
||||
`(width, height)`.
|
||||
overlap_ratio_wh (Tuple[float, float]): Overlap ratio between consecutive
|
||||
slices in the format `(width_ratio, height_ratio)`.
|
||||
iou_threshold (Optional[float]): Intersection over Union (IoU) threshold used
|
||||
for non-max suppression.
|
||||
callback (Callable): A function that performs inference on a given image slice
|
||||
and returns detections.
|
||||
iou_threshold (Optional[float]): Intersection over Union (IoU) threshold
|
||||
used for non-max suppression.
|
||||
callback (Callable): A function that performs inference on a given image
|
||||
slice and returns detections.
|
||||
thread_workers (int): Number of threads for parallel execution.
|
||||
|
||||
Note:
|
||||
The class ensures that slices do not exceed the boundaries of the original
|
||||
@@ -51,11 +53,13 @@ class InferenceSlicer:
|
||||
slice_wh: Tuple[int, int] = (320, 320),
|
||||
overlap_ratio_wh: Tuple[float, float] = (0.2, 0.2),
|
||||
iou_threshold: Optional[float] = 0.5,
|
||||
thread_workers: int = 1,
|
||||
):
|
||||
self.slice_wh = slice_wh
|
||||
self.overlap_ratio_wh = overlap_ratio_wh
|
||||
self.iou_threshold = iou_threshold
|
||||
self.callback = callback
|
||||
self.thread_workers = thread_workers
|
||||
validate_inference_callback(callback=callback)
|
||||
|
||||
def __call__(self, image: np.ndarray) -> Detections:
|
||||
@@ -98,15 +102,35 @@ class InferenceSlicer:
|
||||
overlap_ratio_wh=self.overlap_ratio_wh,
|
||||
)
|
||||
|
||||
for offset in offsets:
|
||||
image_slice = crop_image(image=image, xyxy=offset)
|
||||
detections = self.callback(image_slice)
|
||||
detections = move_detections(detections=detections, offset=offset[:2])
|
||||
detections_list.append(detections)
|
||||
with ThreadPoolExecutor(max_workers=self.thread_workers) as executor:
|
||||
futures = [
|
||||
executor.submit(self._run_callback, image, offset) for offset in offsets
|
||||
]
|
||||
for future in as_completed(futures):
|
||||
detections_list.append(future.result())
|
||||
|
||||
return Detections.merge(detections_list=detections_list).with_nms(
|
||||
threshold=self.iou_threshold
|
||||
)
|
||||
|
||||
def _run_callback(self, image, offset) -> Detections:
|
||||
"""
|
||||
Run the provided callback on a slice of an image.
|
||||
|
||||
Args:
|
||||
image (np.ndarray): The input image on which inference needs to run
|
||||
offset (np.ndarray): An array of shape `(4,)` containing coordinates
|
||||
for the slice.
|
||||
|
||||
Returns:
|
||||
Detections: A collection of detections for the slice.
|
||||
"""
|
||||
image_slice = crop_image(image=image, xyxy=offset)
|
||||
detections = self.callback(image_slice)
|
||||
detections = move_detections(detections=detections, offset=offset[:2])
|
||||
|
||||
return detections
|
||||
|
||||
@staticmethod
|
||||
def _generate_offset(
|
||||
resolution_wh: Tuple[int, int],
|
||||
|
||||
@@ -332,15 +332,16 @@ def extract_ultralytics_masks(yolov8_results) -> Optional[np.ndarray]:
|
||||
|
||||
|
||||
def process_roboflow_result(
|
||||
roboflow_result: dict, class_list: List[str]
|
||||
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]]:
|
||||
roboflow_result: dict,
|
||||
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], np.ndarray]:
|
||||
if not roboflow_result["predictions"]:
|
||||
return np.empty((0, 4)), np.empty(0), np.empty(0), None
|
||||
return np.empty((0, 4)), np.empty(0), np.empty(0), None, None
|
||||
|
||||
xyxy = []
|
||||
confidence = []
|
||||
class_id = []
|
||||
masks = []
|
||||
tracker_ids = []
|
||||
|
||||
image_width = int(roboflow_result["image"]["width"])
|
||||
image_height = int(roboflow_result["image"]["height"])
|
||||
@@ -357,24 +358,29 @@ def process_roboflow_result(
|
||||
|
||||
if "points" not in prediction:
|
||||
xyxy.append([x_min, y_min, x_max, y_max])
|
||||
class_id.append(class_list.index(prediction["class"]))
|
||||
class_id.append(prediction["class_id"])
|
||||
confidence.append(prediction["confidence"])
|
||||
if "tracker_id" in prediction:
|
||||
tracker_ids.append(prediction["tracker_id"])
|
||||
elif len(prediction["points"]) >= 3:
|
||||
polygon = np.array(
|
||||
[[point["x"], point["y"]] for point in prediction["points"]], dtype=int
|
||||
)
|
||||
mask = polygon_to_mask(polygon, resolution_wh=(image_width, image_height))
|
||||
xyxy.append([x_min, y_min, x_max, y_max])
|
||||
class_id.append(class_list.index(prediction["class"]))
|
||||
class_id.append(prediction["class_id"])
|
||||
confidence.append(prediction["confidence"])
|
||||
masks.append(mask)
|
||||
if "tracker_id" in prediction:
|
||||
tracker_ids.append(prediction["tracker_id"])
|
||||
|
||||
xyxy = np.array(xyxy) if len(xyxy) > 0 else np.empty((0, 4))
|
||||
confidence = np.array(confidence) if len(confidence) > 0 else np.empty(0)
|
||||
class_id = np.array(class_id).astype(int) if len(class_id) > 0 else np.empty(0)
|
||||
masks = np.array(masks, dtype=bool) if len(masks) > 0 else None
|
||||
tracker_id = np.array(tracker_ids).astype(int) if len(tracker_ids) > 0 else None
|
||||
|
||||
return xyxy, confidence, class_id, masks
|
||||
return xyxy, confidence, class_id, masks, tracker_id
|
||||
|
||||
|
||||
def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray:
|
||||
|
||||
+72
-21
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
||||
from typing import List, Tuple
|
||||
|
||||
DEFAULT_COLOR_PALETTE = [
|
||||
"#a351fb",
|
||||
"#e6194b",
|
||||
"#3cb44b",
|
||||
"#ffe119",
|
||||
@@ -33,21 +34,35 @@ def _validate_color_hex(color_hex: str):
|
||||
|
||||
@dataclass
|
||||
class Color:
|
||||
"""
|
||||
Represents a color in RGB format.
|
||||
|
||||
Attributes:
|
||||
r (int): Red channel.
|
||||
g (int): Green channel.
|
||||
b (int): Blue channel.
|
||||
"""
|
||||
|
||||
r: int
|
||||
g: int
|
||||
b: int
|
||||
|
||||
@classmethod
|
||||
def from_hex(cls, color_hex: str):
|
||||
def from_hex(cls, color_hex: str) -> Color:
|
||||
"""
|
||||
Creates a Color instance from a color hex string
|
||||
Create a Color instance from a hex string.
|
||||
|
||||
:param color_hex: str : The color hex string in the format
|
||||
of "fff", "ffffff", "#fff", or "#ffffff"
|
||||
:return: Color : A Color instance representing the color
|
||||
Args:
|
||||
color_hex (str): Hex string of the color.
|
||||
|
||||
Returns:
|
||||
Color: Instance representing the color.
|
||||
|
||||
Example:
|
||||
color = Color.from_hex('#ff00ff')
|
||||
```
|
||||
>>> Color.from_hex('#ff00ff')
|
||||
Color(r=255, g=0, b=255)
|
||||
```
|
||||
"""
|
||||
_validate_color_hex(color_hex)
|
||||
color_hex = color_hex.lstrip("#")
|
||||
@@ -58,17 +73,31 @@ class Color:
|
||||
|
||||
def as_rgb(self) -> Tuple[int, int, int]:
|
||||
"""
|
||||
Returns the color as a tuple of integers in the RGB format
|
||||
Returns the color as an RGB tuple.
|
||||
|
||||
:return: Tuple[int, int, int] : The color in the RGB format
|
||||
Returns:
|
||||
Tuple[int, int, int]: RGB tuple.
|
||||
|
||||
Example:
|
||||
```
|
||||
>>> color.as_rgb()
|
||||
(255, 0, 255)
|
||||
```
|
||||
"""
|
||||
return self.r, self.g, self.b
|
||||
|
||||
def as_bgr(self) -> Tuple[int, int, int]:
|
||||
"""
|
||||
Returns the color as a tuple of integers in the BGR format
|
||||
Returns the color as a BGR tuple.
|
||||
|
||||
:return: Tuple[int, int, int] : The color in the BGR format
|
||||
Returns:
|
||||
Tuple[int, int, int]: BGR tuple.
|
||||
|
||||
Example:
|
||||
```
|
||||
>>> color.as_bgr()
|
||||
(255, 0, 255)
|
||||
```
|
||||
"""
|
||||
return self.b, self.g, self.r
|
||||
|
||||
@@ -99,33 +128,55 @@ class ColorPalette:
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> ColorPalette:
|
||||
"""
|
||||
Returns a default color palette.
|
||||
|
||||
Returns:
|
||||
ColorPalette: A ColorPalette instance with default colors.
|
||||
|
||||
Example:
|
||||
```
|
||||
>>> ColorPalette.default()
|
||||
ColorPalette(colors=[Color(r=255, g=0, b=0), Color(r=0, g=255, b=0), ...])
|
||||
```
|
||||
"""
|
||||
return ColorPalette.from_hex(color_hex_list=DEFAULT_COLOR_PALETTE)
|
||||
|
||||
@classmethod
|
||||
def from_hex(cls, color_hex_list: List[str]):
|
||||
def from_hex(cls, color_hex_list: List[str]) -> ColorPalette:
|
||||
"""
|
||||
Creates a ColorPalette instance from a list of color hex strings
|
||||
Create a ColorPalette instance from a list of hex strings.
|
||||
|
||||
:param color_hex_list: List[str] : A list of color hex strings in the
|
||||
format of "fff", "ffffff", "#fff", or "#ffffff"
|
||||
:return: ColorPalette : A ColorPalette instance representing the color palette
|
||||
Args:
|
||||
color_hex_list (List[str]): List of color hex strings.
|
||||
|
||||
Returns:
|
||||
ColorPalette: A ColorPalette instance.
|
||||
|
||||
Example:
|
||||
color_palette = ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff'])
|
||||
```
|
||||
>>> ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff'])
|
||||
ColorPalette(colors=[Color(r=255, g=0, b=0), Color(r=0, g=255, b=0), ...])
|
||||
```
|
||||
"""
|
||||
colors = [Color.from_hex(color_hex) for color_hex in color_hex_list]
|
||||
return cls(colors)
|
||||
|
||||
def by_idx(self, idx: int) -> Color:
|
||||
"""
|
||||
Returns the color at a given index in the color palette.
|
||||
Return the color at a given index in the palette.
|
||||
|
||||
:param idx: int : The index of the color in the color palette
|
||||
:return: Color : The color at the given index
|
||||
Args:
|
||||
idx (int): Index of the color in the palette.
|
||||
|
||||
Returns:
|
||||
Color: Color at the given index.
|
||||
|
||||
Example:
|
||||
color_palette = ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff'])
|
||||
color = color_palette.by_idx(1)
|
||||
```
|
||||
>>> color_palette.by_idx(1)
|
||||
Color(r=0, g=255, b=0)
|
||||
```
|
||||
"""
|
||||
if idx < 0:
|
||||
raise ValueError("idx argument should not be negative")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Optional
|
||||
import os
|
||||
from typing import Optional, Union
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -168,3 +169,65 @@ def draw_text(
|
||||
lineType=cv2.LINE_AA,
|
||||
)
|
||||
return scene
|
||||
|
||||
|
||||
def draw_image(
|
||||
scene: np.ndarray, image: Union[str, np.ndarray], opacity: float, rect: Rect
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Draws an image onto a given scene with specified opacity and dimensions.
|
||||
|
||||
Args:
|
||||
scene (np.ndarray): Background image where the new image will be drawn.
|
||||
image (Union[str, np.ndarray]): Image to draw.
|
||||
opacity (float): Opacity of the image to be drawn.
|
||||
rect (Rect): Rectangle specifying where to draw the image.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The updated scene.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the image path does not exist.
|
||||
ValueError: For invalid opacity or rectangle dimensions.
|
||||
"""
|
||||
|
||||
# Validate and load image
|
||||
if isinstance(image, str):
|
||||
if not os.path.exists(image):
|
||||
raise FileNotFoundError(f"Image path ('{image}') does not exist.")
|
||||
image = cv2.imread(image, cv2.IMREAD_UNCHANGED)
|
||||
|
||||
# Validate opacity
|
||||
if not 0.0 <= opacity <= 1.0:
|
||||
raise ValueError("Opacity must be between 0.0 and 1.0.")
|
||||
|
||||
# Validate rectangle dimensions
|
||||
if (
|
||||
rect.x < 0
|
||||
or rect.y < 0
|
||||
or rect.x + rect.width > scene.shape[1]
|
||||
or rect.y + rect.height > scene.shape[0]
|
||||
):
|
||||
raise ValueError("Invalid rectangle dimensions.")
|
||||
|
||||
# Resize and isolate alpha channel
|
||||
image = cv2.resize(image, (rect.width, rect.height))
|
||||
alpha_channel = (
|
||||
image[:, :, 3]
|
||||
if image.shape[2] == 4
|
||||
else np.ones((rect.height, rect.width), dtype=image.dtype) * 255
|
||||
)
|
||||
alpha_scaled = cv2.convertScaleAbs(alpha_channel * opacity)
|
||||
|
||||
# Perform blending
|
||||
scene_roi = scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width]
|
||||
alpha_float = alpha_scaled.astype(np.float32) / 255.0
|
||||
blended_roi = cv2.convertScaleAbs(
|
||||
(1 - alpha_float[..., np.newaxis]) * scene_roi
|
||||
+ alpha_float[..., np.newaxis] * image[:, :, :3]
|
||||
)
|
||||
|
||||
# Update the scene
|
||||
scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width] = blended_roi
|
||||
|
||||
return scene
|
||||
|
||||
@@ -249,6 +249,8 @@ class ByteTrack:
|
||||
detections.confidence = np.array(
|
||||
[t.score for t in tracks], dtype=np.float32
|
||||
)
|
||||
else:
|
||||
detections.tracker_id = np.array([], dtype=int)
|
||||
|
||||
return detections
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Generator, Optional, Tuple
|
||||
|
||||
@@ -71,13 +73,11 @@ class VideoSink:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> video_info = sv.VideoInfo.from_video_path(video_path='source_video.mp4')
|
||||
>>> video_info = sv.VideoInfo.from_video_path('source.mp4')
|
||||
>>> frames_generator = get_video_frames_generator('source.mp4')
|
||||
|
||||
>>> with sv.VideoSink(target_path='target_video.mp4',
|
||||
... video_info=video_info,
|
||||
codec='H264') as sink:
|
||||
... for frame in get_video_frames_generator(source_path='source_video.mp4',
|
||||
... stride=2):
|
||||
>>> with sv.VideoSink(target_path='target.mp4', video_info=video_info) as sink:
|
||||
... for frame in frames_generator:
|
||||
... sink.write_frame(frame=frame)
|
||||
```
|
||||
"""
|
||||
@@ -202,3 +202,55 @@ def process_video(
|
||||
):
|
||||
result_frame = callback(frame, index)
|
||||
sink.write_frame(frame=result_frame)
|
||||
|
||||
|
||||
class FPSMonitor:
|
||||
"""
|
||||
A class for monitoring frames per second (FPS) to benchmark latency.
|
||||
"""
|
||||
|
||||
def __init__(self, sample_size: int = 30):
|
||||
"""
|
||||
Args:
|
||||
sample_size (int): The maximum number of observations for latency
|
||||
benchmarking.
|
||||
|
||||
Examples:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
|
||||
>>> frames_generator = sv.get_video_frames_generator('source.mp4')
|
||||
>>> fps_monitor = sv.FPSMonitor()
|
||||
|
||||
>>> for frame in frames_generator:
|
||||
... # your processing code here
|
||||
... fps_monitor.tick()
|
||||
... fps = fps_monitor()
|
||||
```
|
||||
"""
|
||||
self.all_timestamps = deque(maxlen=sample_size)
|
||||
|
||||
def __call__(self) -> float:
|
||||
"""
|
||||
Computes and returns the average FPS based on the stored time stamps.
|
||||
|
||||
Returns:
|
||||
float: The average FPS. Returns 0.0 if no time stamps are stored.
|
||||
"""
|
||||
|
||||
if not self.all_timestamps:
|
||||
return 0.0
|
||||
taken_time = self.all_timestamps[-1] - self.all_timestamps[0]
|
||||
return (len(self.all_timestamps)) / taken_time if taken_time != 0 else 0.0
|
||||
|
||||
def tick(self) -> None:
|
||||
"""
|
||||
Adds a new time stamp to the deque for FPS calculation.
|
||||
"""
|
||||
self.all_timestamps.append(time.monotonic())
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
Clears all the time stamps from the deque.
|
||||
"""
|
||||
self.all_timestamps.clear()
|
||||
|
||||
@@ -2,14 +2,15 @@ from contextlib import ExitStack as DoesNotRaise
|
||||
from test.utils import mock_detections
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from supervision.annotators.core import ColorMap, resolve_color_idx
|
||||
from supervision.annotators.utils import ColorLookup, resolve_color_idx
|
||||
from supervision.detection.core import Detections
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"detections, detection_idx, color_map, expected_result, exception",
|
||||
"detections, detection_idx, color_lookup, expected_result, exception",
|
||||
[
|
||||
(
|
||||
mock_detections(
|
||||
@@ -18,10 +19,10 @@ from supervision.detection.core import Detections
|
||||
tracker_id=[2, 6],
|
||||
),
|
||||
0,
|
||||
ColorMap.INDEX,
|
||||
ColorLookup.INDEX,
|
||||
0,
|
||||
DoesNotRaise(),
|
||||
), # multiple detections; index mapping
|
||||
), # multiple detections; index lookup
|
||||
(
|
||||
mock_detections(
|
||||
xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]],
|
||||
@@ -29,10 +30,10 @@ from supervision.detection.core import Detections
|
||||
tracker_id=[2, 6],
|
||||
),
|
||||
0,
|
||||
ColorMap.CLASS,
|
||||
ColorLookup.CLASS,
|
||||
5,
|
||||
DoesNotRaise(),
|
||||
), # multiple detections; class mapping
|
||||
), # multiple detections; class lookup
|
||||
(
|
||||
mock_detections(
|
||||
xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]],
|
||||
@@ -40,17 +41,17 @@ from supervision.detection.core import Detections
|
||||
tracker_id=[2, 6],
|
||||
),
|
||||
0,
|
||||
ColorMap.TRACK,
|
||||
ColorLookup.TRACK,
|
||||
2,
|
||||
DoesNotRaise(),
|
||||
), # multiple detections; track mapping
|
||||
), # multiple detections; track lookup
|
||||
(
|
||||
Detections.empty(),
|
||||
0,
|
||||
ColorMap.INDEX,
|
||||
ColorLookup.INDEX,
|
||||
None,
|
||||
pytest.raises(ValueError),
|
||||
), # no detections; index mapping; out of bounds
|
||||
), # no detections; index lookup; out of bounds
|
||||
(
|
||||
mock_detections(
|
||||
xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]],
|
||||
@@ -58,30 +59,44 @@ from supervision.detection.core import Detections
|
||||
tracker_id=[2, 6],
|
||||
),
|
||||
2,
|
||||
ColorMap.INDEX,
|
||||
ColorLookup.INDEX,
|
||||
None,
|
||||
pytest.raises(ValueError),
|
||||
), # multiple detections; index mapping; out of bounds
|
||||
), # multiple detections; index lookup; out of bounds
|
||||
(
|
||||
mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
||||
0,
|
||||
ColorMap.CLASS,
|
||||
ColorLookup.CLASS,
|
||||
None,
|
||||
pytest.raises(ValueError),
|
||||
), # multiple detections; class mapping; no class_id
|
||||
), # multiple detections; class lookup; no class_id
|
||||
(
|
||||
mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
||||
0,
|
||||
ColorMap.TRACK,
|
||||
ColorLookup.TRACK,
|
||||
None,
|
||||
pytest.raises(ValueError),
|
||||
), # multiple detections; class mapping; no track_id
|
||||
), # multiple detections; class lookup; no track_id
|
||||
(
|
||||
mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
||||
0,
|
||||
np.array([1, 0]),
|
||||
1,
|
||||
DoesNotRaise(),
|
||||
), # multiple detections; custom lookup; correct length
|
||||
(
|
||||
mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]),
|
||||
0,
|
||||
np.array([1]),
|
||||
None,
|
||||
pytest.raises(ValueError),
|
||||
), # multiple detections; custom lookup; wrong length
|
||||
],
|
||||
)
|
||||
def test_resolve_color_idx(
|
||||
detections: Detections,
|
||||
detection_idx: int,
|
||||
color_map: ColorMap,
|
||||
color_lookup: ColorLookup,
|
||||
expected_result: Optional[int],
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
@@ -89,6 +104,6 @@ def test_resolve_color_idx(
|
||||
result = resolve_color_idx(
|
||||
detections=detections,
|
||||
detection_idx=detection_idx,
|
||||
color_map=color_map,
|
||||
color_lookup=color_lookup,
|
||||
)
|
||||
assert result == expected_result
|
||||
@@ -260,12 +260,11 @@ def test_filter_polygons_by_area(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"roboflow_result, class_list, expected_result, exception",
|
||||
"roboflow_result, expected_result, exception",
|
||||
[
|
||||
(
|
||||
{"predictions": [], "image": {"width": 1000, "height": 1000}},
|
||||
["person", "car", "truck"],
|
||||
(np.empty((0, 4)), np.empty(0), np.empty(0), None),
|
||||
(np.empty((0, 4)), np.empty(0), np.empty(0), None, None),
|
||||
DoesNotRaise(),
|
||||
), # empty result
|
||||
(
|
||||
@@ -277,17 +276,18 @@ def test_filter_polygons_by_area(
|
||||
"width": 50.0,
|
||||
"height": 50.0,
|
||||
"confidence": 0.9,
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
}
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
["person", "car", "truck"],
|
||||
(
|
||||
np.array([[175.0, 275.0, 225.0, 325.0]]),
|
||||
np.array([0.9]),
|
||||
np.array([0]),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # single correct object detection result
|
||||
@@ -300,7 +300,9 @@ def test_filter_polygons_by_area(
|
||||
"width": 50.0,
|
||||
"height": 50.0,
|
||||
"confidence": 0.9,
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
"tracker_id": 1,
|
||||
},
|
||||
{
|
||||
"x": 500.0,
|
||||
@@ -308,17 +310,19 @@ def test_filter_polygons_by_area(
|
||||
"width": 100.0,
|
||||
"height": 100.0,
|
||||
"confidence": 0.8,
|
||||
"class_id": 7,
|
||||
"class": "truck",
|
||||
"tracker_id": 2,
|
||||
},
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
["person", "car", "truck"],
|
||||
(
|
||||
np.array([[175.0, 275.0, 225.0, 325.0], [450.0, 450.0, 550.0, 550.0]]),
|
||||
np.array([0.9, 0.8]),
|
||||
np.array([0, 2]),
|
||||
np.array([0, 7]),
|
||||
None,
|
||||
np.array([1, 2]),
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # two correct object detection result
|
||||
@@ -331,14 +335,15 @@ def test_filter_polygons_by_area(
|
||||
"width": 50.0,
|
||||
"height": 50.0,
|
||||
"confidence": 0.9,
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
"points": [],
|
||||
"tracker_id": None,
|
||||
}
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
["person", "car", "truck"],
|
||||
(np.empty((0, 4)), np.empty(0), np.empty(0), None),
|
||||
(np.empty((0, 4)), np.empty(0), np.empty(0), None, None),
|
||||
DoesNotRaise(),
|
||||
), # single incorrect instance segmentation result with no points
|
||||
(
|
||||
@@ -350,14 +355,14 @@ def test_filter_polygons_by_area(
|
||||
"width": 50.0,
|
||||
"height": 50.0,
|
||||
"confidence": 0.9,
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
"points": [{"x": 200.0, "y": 300.0}, {"x": 250.0, "y": 300.0}],
|
||||
}
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
["person", "car", "truck"],
|
||||
(np.empty((0, 4)), np.empty(0), np.empty(0), None),
|
||||
(np.empty((0, 4)), np.empty(0), np.empty(0), None, None),
|
||||
DoesNotRaise(),
|
||||
), # single incorrect instance segmentation result with no enough points
|
||||
(
|
||||
@@ -369,6 +374,7 @@ def test_filter_polygons_by_area(
|
||||
"width": 50.0,
|
||||
"height": 50.0,
|
||||
"confidence": 0.9,
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
"points": [
|
||||
{"x": 200.0, "y": 300.0},
|
||||
@@ -380,12 +386,12 @@ def test_filter_polygons_by_area(
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
["person", "car", "truck"],
|
||||
(
|
||||
np.array([[175.0, 275.0, 225.0, 325.0]]),
|
||||
np.array([0.9]),
|
||||
np.array([0]),
|
||||
TEST_MASK,
|
||||
None,
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # single incorrect instance segmentation result with no enough points
|
||||
@@ -398,6 +404,7 @@ def test_filter_polygons_by_area(
|
||||
"width": 50.0,
|
||||
"height": 50.0,
|
||||
"confidence": 0.9,
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
"points": [
|
||||
{"x": 200.0, "y": 300.0},
|
||||
@@ -412,18 +419,19 @@ def test_filter_polygons_by_area(
|
||||
"width": 100.0,
|
||||
"height": 100.0,
|
||||
"confidence": 0.8,
|
||||
"class_id": 7,
|
||||
"class": "truck",
|
||||
"points": [],
|
||||
},
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
["person", "car", "truck"],
|
||||
(
|
||||
np.array([[175.0, 275.0, 225.0, 325.0]]),
|
||||
np.array([0.9]),
|
||||
np.array([0]),
|
||||
TEST_MASK,
|
||||
None,
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # two instance segmentation results - one correct, one incorrect
|
||||
@@ -431,20 +439,22 @@ def test_filter_polygons_by_area(
|
||||
)
|
||||
def test_process_roboflow_result(
|
||||
roboflow_result: dict,
|
||||
class_list: List[str],
|
||||
expected_result: Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]],
|
||||
expected_result: Tuple[
|
||||
np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], np.ndarray
|
||||
],
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
with exception:
|
||||
result = process_roboflow_result(
|
||||
roboflow_result=roboflow_result, class_list=class_list
|
||||
)
|
||||
result = process_roboflow_result(roboflow_result=roboflow_result)
|
||||
assert np.array_equal(result[0], expected_result[0])
|
||||
assert np.array_equal(result[1], expected_result[1])
|
||||
assert np.array_equal(result[2], expected_result[2])
|
||||
assert (result[3] is None and expected_result[3] is None) or (
|
||||
np.array_equal(result[3], expected_result[3])
|
||||
)
|
||||
assert (result[4] is None and expected_result[4] is None) or (
|
||||
np.array_equal(result[4], expected_result[4])
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
Reference in New Issue
Block a user