Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9dbbbc0ec | ||
|
|
337abe1e71 | ||
|
|
9ece846f8e | ||
|
|
6837ef1d36 | ||
|
|
26990513ba | ||
|
|
bdf527809d | ||
|
|
23a71e6f51 | ||
|
|
e61f1718a0 | ||
|
|
bb79eebdae | ||
|
|
cdb74c1b19 | ||
|
|
b1ceb1debf | ||
|
|
9fe343942f | ||
|
|
b08d40cad4 | ||
|
|
46a50e9595 | ||
|
|
7001721872 | ||
|
|
8c2d18d752 | ||
|
|
fc8e2e2889 | ||
|
|
14dfb32f44 | ||
|
|
8aee70872f | ||
|
|
671c8b3683 | ||
|
|
edaef0b165 | ||
|
|
28fb656bca | ||
|
|
e9866c2f25 | ||
|
|
e59d1bc9f3 | ||
|
|
b6001e389e | ||
|
|
c529402068 | ||
|
|
9d68ee6a1e | ||
|
|
999c12162a | ||
|
|
7d3082c93b | ||
|
|
4cb7400c75 | ||
|
|
3f5f5fb070 | ||
|
|
8689c294bf | ||
|
|
3391eec078 | ||
|
|
6887b0b412 | ||
|
|
a36abd4297 | ||
|
|
7715ef2a17 | ||
|
|
9b3dc37e28 | ||
|
|
24aefe6bcd | ||
|
|
52452c019c | ||
|
|
dca6d1248f | ||
|
|
03fd015f39 | ||
|
|
7faddb52ca | ||
|
|
f987e4f786 | ||
|
|
a00a752d44 | ||
|
|
8bf721559f | ||
|
|
de45e9d67f | ||
|
|
b815c0e7fc | ||
|
|
c158f96b75 | ||
|
|
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 | ||
|
|
f3b92163ad | ||
|
|
2ab5941cbc | ||
|
|
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 | ||
|
|
a57176568a | ||
|
|
b482784403 | ||
|
|
3b00e04101 | ||
|
|
227615b766 | ||
|
|
baff75332f | ||
|
|
434a695324 | ||
|
|
5d71ab2961 | ||
|
|
5c9e529743 | ||
|
|
3f7043e780 | ||
|
|
c746a7f9e4 | ||
|
|
04629cd7f4 |
@@ -0,0 +1,16 @@
|
||||
version: 2
|
||||
updates:
|
||||
# GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: ⬆️
|
||||
# Python
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: ⬆️
|
||||
@@ -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")
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🐍 Set up Python 3.8 environment for build
|
||||
uses: actions/setup-python@v4
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
python-version: [3.8]
|
||||
steps:
|
||||
- name: 🛎️ Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: 🐍 Set up Python ${{ matrix.python-version }}
|
||||
|
||||
@@ -12,15 +12,21 @@ jobs:
|
||||
python-version: ["3.8", "3.9", "3.10","3.11"]
|
||||
steps:
|
||||
- name: 🛎️ Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- 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"
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: 👋 Welcome
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1.1.1
|
||||
- uses: actions/first-interaction@v1.2.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: "Hello there, thank you for opening an Issue ! 🙏🏻 The team was notified and they will get back to you asap."
|
||||
|
||||
@@ -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
|
||||
@@ -69,12 +65,12 @@ repos:
|
||||
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.9.1
|
||||
rev: 23.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.0.292
|
||||
rev: v0.1.1
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -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
+186
-64
@@ -15,49 +15,7 @@
|
||||
|
||||
<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>
|
||||
|
||||
=== "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" }
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
@@ -78,7 +36,28 @@
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
{ 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>
|
||||
|
||||
@@ -99,7 +78,91 @@
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
{ 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>
|
||||
|
||||
@@ -120,7 +183,7 @@
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
@@ -141,7 +204,7 @@
|
||||
|
||||
<div class="result" markdown>
|
||||
|
||||
{ align=center width="800" }
|
||||
{ align=center width="800" }
|
||||
|
||||
</div>
|
||||
|
||||
@@ -149,20 +212,59 @@
|
||||
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
>>> from ultralytics import YOLO
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
>>> model = YOLO('yolov8x.pt')
|
||||
|
||||
>>> trace_annotator = sv.TraceAnnotator()
|
||||
>>> annotated_frame = trace_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
|
||||
>>> 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" }
|
||||
{ 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>
|
||||
|
||||
@@ -170,30 +272,50 @@
|
||||
|
||||
:::supervision.annotators.core.BoundingBoxAnnotator
|
||||
|
||||
## MaskAnnotator
|
||||
|
||||
:::supervision.annotators.core.MaskAnnotator
|
||||
|
||||
## EllipseAnnotator
|
||||
|
||||
:::supervision.annotators.core.EllipseAnnotator
|
||||
|
||||
## 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
|
||||
|
||||
## BlurAnnotator
|
||||
## ColorLookup
|
||||
|
||||
:::supervision.annotators.core.BlurAnnotator
|
||||
:::supervision.annotators.utils.ColorLookup
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
Supervision offers an assets download utility that allows you to download video files
|
||||
that you can use in your demos.
|
||||
|
||||
## install extra
|
||||
|
||||
To install the Supervision assets utility, you can use `pip`. This utility is available
|
||||
as an extra within the Supervision package.
|
||||
|
||||
!!! example "pip install"
|
||||
|
||||
```bash
|
||||
pip install supervision[assets]
|
||||
```
|
||||
|
||||
## download_assets
|
||||
|
||||
:::supervision.assets.downloader.download_assets
|
||||
|
||||
## VideoAssets
|
||||
|
||||
:::supervision.assets.list.VideoAssets
|
||||
@@ -1,3 +1,47 @@
|
||||
### 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.
|
||||
|
||||
@@ -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.
|
||||
+31
-24
@@ -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,29 +26,36 @@ extra_css:
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Quickstart:
|
||||
- Detections: quickstart/detections.md
|
||||
- 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
|
||||
- Annotators: annotators.md
|
||||
- Trackers: trackers.md
|
||||
- Datasets: datasets.md
|
||||
- Metrics:
|
||||
- Object Detection: metrics/detection.md
|
||||
- Draw:
|
||||
- Utils: draw/utils.md
|
||||
- Utils:
|
||||
- Video: utils/video.md
|
||||
- Image: utils/image.md
|
||||
- Notebook: utils/notebook.md
|
||||
- File: utils/file.md
|
||||
- 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
|
||||
- Utils: detection/utils.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:
|
||||
- Object Detection: metrics/detection.md
|
||||
- Draw:
|
||||
- Color: draw/color.md
|
||||
- Utils: draw/utils.md
|
||||
- Utils:
|
||||
- Video: utils/video.md
|
||||
- Image: utils/image.md
|
||||
- Notebook: utils/notebook.md
|
||||
- File: utils/file.md
|
||||
- Assets: assets.md
|
||||
- Changelog: changelog.md
|
||||
|
||||
theme:
|
||||
|
||||
Generated
+123
-50
@@ -137,6 +137,23 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-
|
||||
tests = ["attrs[tests-no-zope]", "zope-interface"]
|
||||
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.13.0"
|
||||
description = "Internationalization utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"},
|
||||
{file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "backcall"
|
||||
version = "0.2.0"
|
||||
@@ -232,25 +249,26 @@ css = ["tinycss2 (>=1.1.0,<1.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "build"
|
||||
version = "0.10.0"
|
||||
version = "1.0.3"
|
||||
description = "A simple, correct Python build frontend"
|
||||
optional = false
|
||||
python-versions = ">= 3.7"
|
||||
files = [
|
||||
{file = "build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"},
|
||||
{file = "build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"},
|
||||
{file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"},
|
||||
{file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "os_name == \"nt\""}
|
||||
importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
|
||||
packaging = ">=19.0"
|
||||
pyproject_hooks = "*"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.08.31)", "sphinx (>=4.0,<5.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)"]
|
||||
test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "toml (>=0.10.0)", "wheel (>=0.36.0)"]
|
||||
typing = ["importlib-metadata (>=5.1)", "mypy (==0.991)", "tomli", "typing-extensions (>=3.7.4.3)"]
|
||||
docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"]
|
||||
test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"]
|
||||
typing = ["importlib-metadata (>=5.1)", "mypy (>=1.5.0,<1.6.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
|
||||
virtualenv = ["virtualenv (>=20.0.35)"]
|
||||
|
||||
[[package]]
|
||||
@@ -1619,13 +1637,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.4.3"
|
||||
version = "1.5.3"
|
||||
description = "Project documentation with Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd"},
|
||||
{file = "mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57"},
|
||||
{file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"},
|
||||
{file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1634,16 +1652,19 @@ colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
|
||||
ghp-import = ">=1.0"
|
||||
importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
|
||||
jinja2 = ">=2.11.1"
|
||||
markdown = ">=3.2.1,<3.4"
|
||||
markdown = ">=3.2.1"
|
||||
markupsafe = ">=2.0.1"
|
||||
mergedeep = ">=1.3.4"
|
||||
packaging = ">=20.5"
|
||||
pathspec = ">=0.11.1"
|
||||
platformdirs = ">=2.2.0"
|
||||
pyyaml = ">=5.1"
|
||||
pyyaml-env-tag = ">=0.1"
|
||||
watchdog = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["babel (>=2.9.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
@@ -1662,35 +1683,42 @@ mkdocs = ">=1.1"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.1.19"
|
||||
version = "9.4.6"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.1.19-py3-none-any.whl", hash = "sha256:fb0a149294b319aedf36983919d8c40c9e566db21ead16258e20ebd2e6c0961c"},
|
||||
{file = "mkdocs_material-9.1.19.tar.gz", hash = "sha256:73b94b08c765e92a80645aac58d6a741fc5f587deec2b715489c714827b15a6f"},
|
||||
{file = "mkdocs_material-9.4.6-py3-none-any.whl", hash = "sha256:78802035d5768a78139c84ad7dce0c6493e8f7dc4861727d36ed91d1520a54da"},
|
||||
{file = "mkdocs_material-9.4.6.tar.gz", hash = "sha256:09665e60df7ee9e5ff3a54af173f6d45be718b1ee7dd962bcff3102b81fb0c14"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = ">=0.4"
|
||||
jinja2 = ">=3.0"
|
||||
markdown = ">=3.2"
|
||||
mkdocs = ">=1.4.2"
|
||||
mkdocs-material-extensions = ">=1.1"
|
||||
pygments = ">=2.14"
|
||||
pymdown-extensions = ">=9.9.1"
|
||||
regex = ">=2022.4.24"
|
||||
requests = ">=2.26"
|
||||
babel = ">=2.10,<3.0"
|
||||
colorama = ">=0.4,<1.0"
|
||||
jinja2 = ">=3.0,<4.0"
|
||||
markdown = ">=3.2,<4.0"
|
||||
mkdocs = ">=1.5.3,<2.0"
|
||||
mkdocs-material-extensions = ">=1.2,<2.0"
|
||||
paginate = ">=0.5,<1.0"
|
||||
pygments = ">=2.16,<3.0"
|
||||
pymdown-extensions = ">=10.2,<11.0"
|
||||
regex = ">=2022.4"
|
||||
requests = ">=2.26,<3.0"
|
||||
|
||||
[package.extras]
|
||||
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2,<2.0)"]
|
||||
imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=9.4,<10.0)"]
|
||||
recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material-extensions"
|
||||
version = "1.1.1"
|
||||
version = "1.3"
|
||||
description = "Extension pack for Python Markdown and MkDocs Material."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"},
|
||||
{file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"},
|
||||
{file = "mkdocs_material_extensions-1.3-py3-none-any.whl", hash = "sha256:0297cc48ba68a9fdd1ef3780a3b41b534b0d0df1d1181a44676fda5f464eeadc"},
|
||||
{file = "mkdocs_material_extensions-1.3.tar.gz", hash = "sha256:f0446091503acb110a7cab9349cbc90eeac51b58d1caa92a704a81ca1e24ddbd"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2032,18 +2060,18 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "opencv-python"
|
||||
version = "4.8.0.74"
|
||||
version = "4.8.1.78"
|
||||
description = "Wrapper package for OpenCV python bindings."
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "opencv-python-4.8.0.74.tar.gz", hash = "sha256:009e3ce356a0cd2d7423723e00a32fd3d3cc5bb5970ed27a9a1f8a8f221d1db5"},
|
||||
{file = "opencv_python-4.8.0.74-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:31d0d59fc8fdf703de4cec46c79b9f8d026fdde9d23d6e2e6a66809feeebbda9"},
|
||||
{file = "opencv_python-4.8.0.74-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:66eadb5882ee56848b67f9fb57aadcaca2f4c9d9d00a0ef11043041925b51291"},
|
||||
{file = "opencv_python-4.8.0.74-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:038ba7075e55cb8e2846663ae970f0fb776a45b48ee69a887bf4ee15e2570083"},
|
||||
{file = "opencv_python-4.8.0.74-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43dd0dfe331fb95767af581bf3b2781d7a72cf6560ddf2f55949fe547f3e5c9f"},
|
||||
{file = "opencv_python-4.8.0.74-cp37-abi3-win32.whl", hash = "sha256:458e5dc377f15fcf769d80314f3d885bd95457b1a2891bee67df2eb24a1d3a52"},
|
||||
{file = "opencv_python-4.8.0.74-cp37-abi3-win_amd64.whl", hash = "sha256:8fe0018d0056a5187c57120b6b3f6c3e706c13b45c48e54e86d245a9a16fac84"},
|
||||
{file = "opencv-python-4.8.1.78.tar.gz", hash = "sha256:cc7adbbcd1112877a39274106cb2752e04984bc01a031162952e97450d6117f6"},
|
||||
{file = "opencv_python-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:91d5f6f5209dc2635d496f6b8ca6573ecdad051a09e6b5de4c399b8e673c60da"},
|
||||
{file = "opencv_python-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31f47e05447da8b3089faa0a07ffe80e114c91ce0b171e6424f9badbd1c5cd"},
|
||||
{file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9814beca408d3a0eca1bae7e3e5be68b07c17ecceb392b94170881216e09b319"},
|
||||
{file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c406bdb41eb21ea51b4e90dfbc989c002786c3f601c236a99c59a54670a394"},
|
||||
{file = "opencv_python-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:a7aac3900fbacf55b551e7b53626c3dad4c71ce85643645c43e91fcb19045e47"},
|
||||
{file = "opencv_python-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:b983197f97cfa6fcb74e1da1802c7497a6f94ed561aba6980f1f33123f904956"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2104,6 +2132,16 @@ files = [
|
||||
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paginate"
|
||||
version = "0.5.6"
|
||||
description = "Divides large result sets into pages for easier browsing"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandocfilters"
|
||||
version = "1.5.0"
|
||||
@@ -2420,13 +2458,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.15.1"
|
||||
version = "2.16.1"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
|
||||
{file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
|
||||
{file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"},
|
||||
{file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -2434,19 +2472,22 @@ plugins = ["importlib-metadata"]
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.1"
|
||||
version = "10.3.1"
|
||||
description = "Extension pack for Python Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc"},
|
||||
{file = "pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195"},
|
||||
{file = "pymdown_extensions-10.3.1-py3-none-any.whl", hash = "sha256:8cba67beb2a1318cdaf742d09dff7c0fc4cafcc290147ade0f8fb7b71522711a"},
|
||||
{file = "pymdown_extensions-10.3.1.tar.gz", hash = "sha256:f6c79941498a458852853872e379e7bab63888361ba20992fc8b4f8a9b61735e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown = ">=3.2"
|
||||
pyyaml = "*"
|
||||
|
||||
[package.extras]
|
||||
extra = ["pygments (>=2.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.9"
|
||||
@@ -2477,13 +2518,13 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.0"
|
||||
version = "7.4.2"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"},
|
||||
{file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"},
|
||||
{file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"},
|
||||
{file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2522,6 +2563,17 @@ files = [
|
||||
{file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2023.3.post1"
|
||||
description = "World timezone definitions, modern and historical"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"},
|
||||
{file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "306"
|
||||
@@ -3294,6 +3346,26 @@ files = [
|
||||
{file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.66.1"
|
||||
description = "Fast, Extensible Progress Meter"
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"},
|
||||
{file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"]
|
||||
notebook = ["ipywidgets (>=6)"]
|
||||
slack = ["slack-sdk"]
|
||||
telegram = ["requests"]
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
version = "5.9.0"
|
||||
@@ -3358,13 +3430,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.0.6"
|
||||
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.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"},
|
||||
{file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"},
|
||||
{file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
|
||||
{file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -3515,9 +3587,10 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
|
||||
|
||||
[extras]
|
||||
assets = ["requests", "tqdm"]
|
||||
desktop = ["opencv-python"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.8,<3.12.0"
|
||||
content-hash = "beb692d89611ce85f8b2992f4e6da71fd14fa2ca75742ddae1a1c7b77df134f7"
|
||||
content-hash = "7f29055b1cbde4699f27580f4ff0e115069821746674245274846b26ee48ce0b"
|
||||
|
||||
+9
-4
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "supervision"
|
||||
version = "0.15.0"
|
||||
version = "0.17.0rc2"
|
||||
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
|
||||
authors = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
|
||||
maintainers = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
|
||||
@@ -43,18 +43,20 @@ 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"
|
||||
requests = { version = "^2.31.0", optional = true }
|
||||
tqdm = { version = "^4.66.1", optional = true }
|
||||
|
||||
|
||||
[tool.poetry.extras]
|
||||
desktop = ["opencv-python"]
|
||||
|
||||
assets = ["requests","tqdm"]
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
twine = "^4.0.2"
|
||||
pytest = "^7.2.2"
|
||||
wheel = "^0.40.0"
|
||||
notebook = "^6.5.3"
|
||||
build = "^0.10.0"
|
||||
build = ">=0.10,<1.1"
|
||||
ruff = "^0.0.280"
|
||||
isort = "^5.12.0"
|
||||
black = "^23.7.0"
|
||||
@@ -79,6 +81,7 @@ extend-ignore = """
|
||||
"""
|
||||
per-file-ignores = """
|
||||
__init__.py: F401
|
||||
supervision/assets/list.py: E501
|
||||
"""
|
||||
|
||||
[tool.isort]
|
||||
@@ -149,6 +152,7 @@ exclude = [
|
||||
"yarn-error.log",
|
||||
"yarn.lock",
|
||||
"docs",
|
||||
"supervision/assets/list.py"
|
||||
]
|
||||
|
||||
# Same as Black.
|
||||
@@ -167,6 +171,7 @@ convention = "google"
|
||||
|
||||
[tool.ruff.per-file-ignores]
|
||||
"__init__.py" = ["E402","F401"]
|
||||
"supervision/assets/list.py" = ["E501"]
|
||||
|
||||
[tool.ruff.pylint]
|
||||
max-args = 20
|
||||
@@ -176,7 +181,7 @@ max-args = 20
|
||||
include-package-data = false
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
exclude = ["docs*", "test*"]
|
||||
exclude = ["docs*", "test*","examples*"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
+14
-1
@@ -10,12 +10,17 @@ 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,
|
||||
@@ -37,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
|
||||
@@ -46,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,
|
||||
|
||||
+485
-90
@@ -5,12 +5,7 @@ import cv2
|
||||
import numpy as np
|
||||
|
||||
from supervision.annotators.base import BaseAnnotator
|
||||
from supervision.annotators.utils import (
|
||||
ColorMap,
|
||||
Trace,
|
||||
resolve_color,
|
||||
resolve_color_idx,
|
||||
)
|
||||
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
|
||||
@@ -25,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.
|
||||
@@ -65,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),
|
||||
@@ -94,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.
|
||||
@@ -134,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
|
||||
|
||||
|
||||
@@ -169,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:
|
||||
@@ -178,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.
|
||||
@@ -213,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(
|
||||
@@ -249,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:
|
||||
@@ -258,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.
|
||||
@@ -292,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:
|
||||
@@ -325,26 +527,27 @@ class CircleAnnotator(BaseAnnotator):
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
thickness: int = 4,
|
||||
color_map: str = "class",
|
||||
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_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,
|
||||
custom_color_lookup: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Annotates the given scene with circles based on the provided detections.
|
||||
@@ -352,6 +555,8 @@ class CircleAnnotator(BaseAnnotator):
|
||||
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.
|
||||
@@ -372,25 +577,20 @@ class CircleAnnotator(BaseAnnotator):
|
||||
|
||||
|
||||

|
||||
supervision-annotator-examples/circle-annotator-example-purple.png)
|
||||
"""
|
||||
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)
|
||||
|
||||
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 = (
|
||||
self.color.by_idx(idx)
|
||||
if isinstance(self.color, ColorPalette)
|
||||
else self.color
|
||||
)
|
||||
|
||||
cv2.circle(
|
||||
img=scene,
|
||||
center=center,
|
||||
@@ -402,6 +602,83 @@ class CircleAnnotator(BaseAnnotator):
|
||||
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.
|
||||
@@ -415,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:
|
||||
@@ -427,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
|
||||
@@ -436,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(
|
||||
@@ -485,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.
|
||||
@@ -493,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.
|
||||
@@ -512,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))
|
||||
@@ -607,7 +889,7 @@ class BlurAnnotator(BaseAnnotator):
|
||||
```
|
||||
|
||||

|
||||
supervision-annotator-examples/blur-annotator-example-purple.png)
|
||||
"""
|
||||
for detection_idx in range(len(detections)):
|
||||
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
||||
@@ -633,30 +915,35 @@ class TraceAnnotator:
|
||||
def __init__(
|
||||
self,
|
||||
color: Union[Color, ColorPalette] = ColorPalette.default(),
|
||||
position: Optional[Position] = Position.CENTER,
|
||||
position: Position = Position.CENTER,
|
||||
trace_length: int = 30,
|
||||
thickness: int = 2,
|
||||
color_map: str = "class",
|
||||
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 (Optional[Position]): The position of the trace.
|
||||
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_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.position = position
|
||||
self.trace = Trace(max_size=trace_length)
|
||||
self.thickness = 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:
|
||||
"""
|
||||
Draws trace paths on the frame based on the detection coordinates provided.
|
||||
|
||||
@@ -664,6 +951,8 @@ class TraceAnnotator:
|
||||
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.
|
||||
@@ -671,30 +960,42 @@ class TraceAnnotator:
|
||||
Example:
|
||||
```python
|
||||
>>> import supervision as sv
|
||||
>>> from ultralytics import YOLO
|
||||
|
||||
>>> image = ...
|
||||
>>> detections = sv.Detections(...)
|
||||
>>> model = YOLO('yolov8x.pt')
|
||||
|
||||
>>> trace_annotator = sv.TraceAnnotator()
|
||||
>>> annotated_frame = trace_annotator.annotate(
|
||||
... scene=image.copy(),
|
||||
... detections=detections
|
||||
... )
|
||||
|
||||
>>> 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)
|
||||
```
|
||||
|
||||

|
||||
supervision-annotator-examples/trace-annotator-example-purple.png)
|
||||
"""
|
||||
self.trace.put(detections)
|
||||
|
||||
for detection_idx in range(len(detections)):
|
||||
tracker_id = int(detections.tracker_id[detection_idx])
|
||||
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)
|
||||
xy = self.trace.get(tracker_id=tracker_id)
|
||||
if len(xy) > 1:
|
||||
scene = cv2.polylines(
|
||||
@@ -705,3 +1006,97 @@ class TraceAnnotator:
|
||||
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
|
||||
|
||||
@@ -8,18 +8,29 @@ from supervision.draw.color import Color, ColorPalette
|
||||
from supervision.geometry.core import Position
|
||||
|
||||
|
||||
class ColorMap(Enum):
|
||||
class ColorLookup(Enum):
|
||||
"""
|
||||
Enum for annotator color mapping.
|
||||
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_map: ColorMap = ColorMap.CLASS
|
||||
detections: Detections,
|
||||
detection_idx: int,
|
||||
color_lookup: Union[ColorLookup, np.ndarray] = ColorLookup.CLASS,
|
||||
) -> int:
|
||||
if detection_idx >= len(detections):
|
||||
raise ValueError(
|
||||
@@ -27,16 +38,23 @@ def resolve_color_idx(
|
||||
f"is out of bounds for detections of length {len(detections)}"
|
||||
)
|
||||
|
||||
if color_map == ColorMap.INDEX:
|
||||
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_map == ColorMap.CLASS:
|
||||
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_map == ColorMap.TRACK:
|
||||
elif color_lookup == ColorLookup.TRACK:
|
||||
if detections.tracker_id is None:
|
||||
raise ValueError(
|
||||
"Could not resolve color by track because"
|
||||
@@ -45,12 +63,26 @@ def resolve_color_idx(
|
||||
return detections.tracker_id[detection_idx]
|
||||
|
||||
|
||||
def resolve_color(color: Union[Color, ColorPalette], idx: int) -> Color:
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from supervision.assets.downloader import download_assets
|
||||
from supervision.assets.list import VideoAssets
|
||||
@@ -0,0 +1,94 @@
|
||||
import os
|
||||
from hashlib import md5
|
||||
from pathlib import Path
|
||||
from shutil import copyfileobj
|
||||
from typing import Union
|
||||
|
||||
from supervision.assets.list import VIDEO_ASSETS, VideoAssets
|
||||
|
||||
try:
|
||||
from requests import get
|
||||
from tqdm.auto import tqdm
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"\n"
|
||||
"Please install requests and tqdm to download assets \n"
|
||||
"or install supervision with assets \n"
|
||||
"pip install supervision[assets] \n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
|
||||
def is_md5_hash_matching(filename: str, original_md5_hash: str) -> bool:
|
||||
"""
|
||||
Check if the MD5 hash of a file matches the original hash.
|
||||
|
||||
Parameters:
|
||||
filename (str): The path to the file to be checked as a string.
|
||||
original_md5_hash (str): The original MD5 hash to compare against.
|
||||
|
||||
Returns:
|
||||
bool: True if the hashes match, False otherwise.
|
||||
"""
|
||||
if not os.path.exists(filename):
|
||||
return False
|
||||
|
||||
with open(filename, "rb") as file:
|
||||
file_contents = file.read()
|
||||
computed_md5_hash = md5(file_contents).hexdigest()
|
||||
|
||||
return computed_md5_hash == original_md5_hash
|
||||
|
||||
|
||||
def download_assets(asset_name: Union[VideoAssets, str]) -> str:
|
||||
"""
|
||||
Download a specified asset if it doesn't already exist or is corrupted.
|
||||
|
||||
Parameters:
|
||||
asset_name (Union[VideoAssets, str]): The name or type of the asset to be
|
||||
downloaded.
|
||||
|
||||
Returns:
|
||||
str: The filename of the downloaded asset.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> from supervision.assets import download_assets, VideoAssets
|
||||
|
||||
>>> download_assets(VideoAssets.VEHICLES)
|
||||
"vehicles.mp4"
|
||||
```
|
||||
"""
|
||||
|
||||
filename = asset_name.value if isinstance(asset_name, VideoAssets) else asset_name
|
||||
|
||||
if not Path(filename).exists() and filename in VIDEO_ASSETS:
|
||||
print(f"Downloading {filename} assets \n")
|
||||
response = get(VIDEO_ASSETS[filename][0], stream=True, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
file_size = int(response.headers.get("Content-Length", 0))
|
||||
folder_path = Path(filename).expanduser().resolve()
|
||||
folder_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with tqdm.wrapattr(
|
||||
response.raw, "read", total=file_size, desc="", colour="#a351fb"
|
||||
) as raw_resp:
|
||||
with folder_path.open("wb") as file:
|
||||
copyfileobj(raw_resp, file)
|
||||
|
||||
elif Path(filename).exists():
|
||||
if not is_md5_hash_matching(filename, VIDEO_ASSETS[filename][1]):
|
||||
print("File corrupted. Re-downloading... \n")
|
||||
os.remove(filename)
|
||||
return download_assets(filename)
|
||||
|
||||
print(f"{filename} asset download complete. \n")
|
||||
|
||||
else:
|
||||
valid_assets = ", ".join(asset.value for asset in VideoAssets)
|
||||
raise ValueError(
|
||||
f"Invalid asset. It should be one of the following: {valid_assets}."
|
||||
)
|
||||
|
||||
return filename
|
||||
@@ -0,0 +1,65 @@
|
||||
from enum import Enum
|
||||
from typing import Dict, Tuple
|
||||
|
||||
BASE_VIDEO_URL = "https://media.roboflow.com/supervision/video-examples/"
|
||||
|
||||
|
||||
class VideoAssets(Enum):
|
||||
"""
|
||||
Each member of this enum represents a video asset. The value associated with each
|
||||
member is the filename of the video.
|
||||
|
||||
| Enum Member | Video Filename | Video URL |
|
||||
|------------------------|----------------------------|---------------------------------------------------------------------------------------|
|
||||
| `VEHICLES` | `vehicles.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/vehicles.mp4) |
|
||||
| `MILK_BOTTLING_PLANT` | `milk-bottling-plant.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/milk-bottling-plant.mp4) |
|
||||
| `VEHICLES_2` | `vehicles-2.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/vehicles-2.mp4) |
|
||||
| `GROCERY_STORE` | `grocery-store.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/grocery-store.mp4) |
|
||||
| `SUBWAY` | `subway.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/subway.mp4) |
|
||||
| `MARKET_SQUARE` | `market-square.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/market-square.mp4) |
|
||||
| `PEOPLE_WALKING` | `people-walking.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/people-walking.mp4) |
|
||||
"""
|
||||
|
||||
VEHICLES = "vehicles.mp4"
|
||||
MILK_BOTTLING_PLANT = "milk-bottling-plant.mp4"
|
||||
VEHICLES_2 = "vehicles-2.mp4"
|
||||
GROCERY_STORE = "grocery-store.mp4"
|
||||
SUBWAY = "subway.mp4"
|
||||
MARKET_SQUARE = "market-square.mp4"
|
||||
PEOPLE_WALKING = "people-walking.mp4"
|
||||
|
||||
@classmethod
|
||||
def list(cls):
|
||||
return list(map(lambda c: c.value, cls))
|
||||
|
||||
|
||||
VIDEO_ASSETS: Dict[str, Tuple[str, str]] = {
|
||||
VideoAssets.VEHICLES.value: (
|
||||
f"{BASE_VIDEO_URL}{VideoAssets.VEHICLES.value}",
|
||||
"8155ff4e4de08cfa25f39de96483f918",
|
||||
),
|
||||
VideoAssets.VEHICLES_2.value: (
|
||||
f"{BASE_VIDEO_URL}{VideoAssets.VEHICLES_2.value}",
|
||||
"830af6fba21ffbf14867a7fea595937b",
|
||||
),
|
||||
VideoAssets.MILK_BOTTLING_PLANT.value: (
|
||||
f"{BASE_VIDEO_URL}{VideoAssets.MILK_BOTTLING_PLANT.value}",
|
||||
"9e8fb6e883f842a38b3d34267290bdc7",
|
||||
),
|
||||
VideoAssets.GROCERY_STORE.value: (
|
||||
f"{BASE_VIDEO_URL}{VideoAssets.GROCERY_STORE.value}",
|
||||
"11402e7b861c1980527d3d74cbe3b366",
|
||||
),
|
||||
VideoAssets.SUBWAY.value: (
|
||||
f"{BASE_VIDEO_URL}{VideoAssets.SUBWAY.value}",
|
||||
"453475750691fb23c56a0cffef089194",
|
||||
),
|
||||
VideoAssets.MARKET_SQUARE.value: (
|
||||
f"{BASE_VIDEO_URL}{VideoAssets.MARKET_SQUARE.value}",
|
||||
"859179bf4a21f80a8baabfdb2ed716dc",
|
||||
),
|
||||
VideoAssets.PEOPLE_WALKING.value: (
|
||||
f"{BASE_VIDEO_URL}{VideoAssets.PEOPLE_WALKING.value}",
|
||||
"0574c053c8686c3f1dc0aa3743e45cb9",
|
||||
),
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
@@ -467,7 +426,7 @@ class Detections:
|
||||
>>> detections = sv.Detections.from_roboflow(roboflow_result)
|
||||
```
|
||||
"""
|
||||
xyxy, confidence, class_id, masks = process_roboflow_result(
|
||||
xyxy, confidence, class_id, masks, trackers = process_roboflow_result(
|
||||
roboflow_result=roboflow_result
|
||||
)
|
||||
|
||||
@@ -479,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:
|
||||
|
||||
@@ -333,14 +333,15 @@ def extract_ultralytics_masks(yolov8_results) -> Optional[np.ndarray]:
|
||||
|
||||
def process_roboflow_result(
|
||||
roboflow_result: dict,
|
||||
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]]:
|
||||
) -> 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"])
|
||||
@@ -359,6 +360,8 @@ def process_roboflow_result(
|
||||
xyxy.append([x_min, y_min, x_max, y_max])
|
||||
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
|
||||
@@ -368,13 +371,16 @@ def process_roboflow_result(
|
||||
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.utils 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
|
||||
|
||||
@@ -264,7 +264,7 @@ def test_filter_polygons_by_area(
|
||||
[
|
||||
(
|
||||
{"predictions": [], "image": {"width": 1000, "height": 1000}},
|
||||
(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
|
||||
(
|
||||
@@ -287,6 +287,7 @@ def test_filter_polygons_by_area(
|
||||
np.array([0.9]),
|
||||
np.array([0]),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # single correct object detection result
|
||||
@@ -301,6 +302,7 @@ def test_filter_polygons_by_area(
|
||||
"confidence": 0.9,
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
"tracker_id": 1,
|
||||
},
|
||||
{
|
||||
"x": 500.0,
|
||||
@@ -310,6 +312,7 @@ def test_filter_polygons_by_area(
|
||||
"confidence": 0.8,
|
||||
"class_id": 7,
|
||||
"class": "truck",
|
||||
"tracker_id": 2,
|
||||
},
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
@@ -319,6 +322,7 @@ def test_filter_polygons_by_area(
|
||||
np.array([0.9, 0.8]),
|
||||
np.array([0, 7]),
|
||||
None,
|
||||
np.array([1, 2]),
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # two correct object detection result
|
||||
@@ -334,11 +338,12 @@ def test_filter_polygons_by_area(
|
||||
"class_id": 0,
|
||||
"class": "person",
|
||||
"points": [],
|
||||
"tracker_id": None,
|
||||
}
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
(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
|
||||
(
|
||||
@@ -357,7 +362,7 @@ def test_filter_polygons_by_area(
|
||||
],
|
||||
"image": {"width": 1000, "height": 1000},
|
||||
},
|
||||
(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
|
||||
(
|
||||
@@ -386,6 +391,7 @@ def test_filter_polygons_by_area(
|
||||
np.array([0.9]),
|
||||
np.array([0]),
|
||||
TEST_MASK,
|
||||
None,
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # single incorrect instance segmentation result with no enough points
|
||||
@@ -425,6 +431,7 @@ def test_filter_polygons_by_area(
|
||||
np.array([0.9]),
|
||||
np.array([0]),
|
||||
TEST_MASK,
|
||||
None,
|
||||
),
|
||||
DoesNotRaise(),
|
||||
), # two instance segmentation results - one correct, one incorrect
|
||||
@@ -432,7 +439,9 @@ def test_filter_polygons_by_area(
|
||||
)
|
||||
def test_process_roboflow_result(
|
||||
roboflow_result: dict,
|
||||
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:
|
||||
@@ -443,6 +452,9 @@ def test_process_roboflow_result(
|
||||
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