Compare commits

...
110 Commits
Author SHA1 Message Date
Piotr Skalski 800e39d64c 🪵 supervision-0.12.0 changelog updates
Add 0.12.0 changelog
2023-07-23 15:34:44 +02:00
SkalskiP 926f935bad 🪵 supervision-0.12.0 changelog updates 2023-07-23 15:31:45 +02:00
Piotr Skalski d3da6afa07 Merge pull request #223 from roboflow/fix/confusion_matrix_plot_bug
🛠️ #212 bug fix
2023-07-23 15:00:58 +02:00
Piotr Skalski 97ae155b1e Merge pull request #226 from hardikdava/fix/yolo_loading_bug
Fix yolo dataset background instance loading
2023-07-23 14:51:11 +02:00
Piotr Skalski 24f013757f Update README.md 2023-07-23 14:12:16 +02:00
SkalskiP 532245640b ⚡ Make convert_detections_to_tensor static method 2023-07-23 13:41:57 +02:00
Hardik Dava 864764e665 Update core.py 2023-07-23 13:37:50 +02:00
hd ca348401d5 Fix for loading background image loading 2023-07-23 13:33:01 +02:00
hd 300ebea8ec Merge branch 'main' of github.com:hardikdava/supervision into main 2023-07-23 11:37:43 +02:00
SkalskiP 5cc7da289e 🛠️ #212 bug fix 2023-07-23 01:22:03 +02:00
Piotr Skalski 7a07517750 🧮 Add confusion matrix to README.md 2023-07-22 18:12:26 +02:00
Piotr Skalski 699fc01a0c Merge pull request #218 from onuralpszr/devtools
feat: 🛠  adding python dev tools into toml file
2023-07-22 17:42:58 +02:00
Piotr Skalski 43339e380a Merge pull request #220 from onuralpszr/docs-py-ver-fix
docs: 📝 Update python version for supervision version 0.12.0
2023-07-22 17:37:11 +02:00
Piotr Skalski 89a33cda8a Merge pull request #219 from onuralpszr/development-version-213
fix: 🐛 package version control if PackageNotFoundError occured
2023-07-22 17:26:55 +02:00
Piotr Skalski e84302b43f Merge pull request #217 from roboflow/fix/confusion_matrix_errors
🛠️ fix `detections_to_tensor`
2023-07-22 17:22:55 +02:00
Onuralp SEZER 93ab21dda7 docs: 📝 python 3.11 pypi classifier added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 16:16:57 +03:00
Onuralp SEZER effa1415b5 docs: 📝 Update python version for supervision version 0.12.0
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 16:14:03 +03:00
hd e9e075acaf Merge branch 'main' of github.com:hardikdava/supervision into main 2023-07-22 10:44:12 +02:00
Onuralp SEZER 61e9584f5a fix: 🐛 package version control if PackageNotFoundError occured
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 10:40:17 +03:00
Onuralp SEZER 2a640d3ab6 feat: 🛠 adding develop branch into CIs
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 09:31:07 +03:00
Onuralp SEZER 2f478f4d06 feat: 🛠 adding python dev tools into toml file
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 09:24:28 +03:00
SkalskiP 0c20830be5 🖤 make black happy 2023-07-22 01:05:07 +02:00
SkalskiP 6c51c4a1b4 🛠️ fix detections_to_tensor 2023-07-22 00:42:26 +02:00
Piotr Skalski d6883caadc Merge pull request #214 from onuralpszr/readme-installation-docs
docs:  📝 update installation and source installation methods
2023-07-21 23:52:51 +02:00
SkalskiP f1f2728e0d 📝 updated install guide 2023-07-21 23:11:03 +02:00
Onuralp SEZER b6b7663ea7 docs: 📝 update installation and source installation methods
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-21 23:46:59 +03:00
SkalskiP 3d074fa213 🧮 Confusion Matrix docs update 2023-07-21 19:51:19 +02:00
James Gallagher 9fd267acb2 add changelog updates 2023-07-21 13:30:56 +01:00
Piotr Skalski 727451b382 Merge pull request #207 from onuralpszr/docs-ci-action-version-update
chore: ✨ update github action versions (actions/checkout@v3, actions/setup-python@v4)
2023-07-21 14:17:09 +02:00
Onuralp SEZER 5f432fbc93 chore: ✨ update github action versions (actions/checkout@v3, actions/setup-python@v4)
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-21 14:46:20 +03:00
Piotr Skalski ea4638f5f5 Merge pull request #177 from kirilllzaitsev/feature/conf_matrix
Add ConfusionMatrix to EvaluationAPI
2023-07-21 13:31:26 +02:00
kirilllzaitsev 4b38634c63 add comments to _drop_extra_matches 2023-07-21 12:34:43 +02:00
kirilllzaitsev 04b7badcbe remove mock detection dataset 2023-07-21 11:44:28 +02:00
kirilllzaitsev 54524a4a1f drop docstring in _drop_extra_matches 2023-07-21 11:43:57 +02:00
kirilllzaitsev 3f55e7a79a updates patch 2023-07-21 11:42:50 +02:00
kirilllzaitsev 7a782b1ac3 cleanup test_detection 2023-07-20 15:59:50 +02:00
kirilllzaitsev 6016338a4f remove test_from_detections 2023-07-20 15:48:38 +02:00
kirilllzaitsev 8d131ad3e8 add convert_detections_to_tensor 2023-07-20 15:48:23 +02:00
kirilllzaitsev ae55e9c908 add test_convert_detections_to_tensor 2023-07-20 15:48:08 +02:00
kirilllzaitsev 5cb3c603cd remove test_benchmark 2023-07-20 15:39:55 +02:00
kirilllzaitsev 56e0b77211 add _validate_input_tensors 2023-07-20 15:35:59 +02:00
kirilllzaitsev bc9fe6f690 fix CM plot colors 2023-07-20 13:24:25 +02:00
kirilllzaitsev fb211a6160 add IoU tests 2023-07-20 10:14:06 +02:00
kirilllzaitsev 37b7224380 black 2023-07-20 10:13:58 +02:00
kirilllzaitsev 314454c7a7 add test cases to test_from_tensors 2023-07-20 09:57:44 +02:00
kirilllzaitsev ac39bdd487 add empty (preds,targets) test 2023-07-20 09:27:29 +02:00
kirilllzaitsev fc1e899575 fix failing tests (reduce threshold to 0.2) 2023-07-20 09:17:47 +02:00
kirilllzaitsev fccbe6baa2 add test_from_tensors 2023-07-20 00:22:12 +02:00
kirilllzaitsev d21154e882 fix _evaluate_detection_batch. use np.ndarray 2023-07-20 00:21:06 +02:00
kirilllzaitsev 1b3af74756 add ConfusionMatrix.from_tensors 2023-07-19 23:57:05 +02:00
kirilllzaitsev dc1083711a remove ConfusionMatrix.from_matrix 2023-07-19 23:13:19 +02:00
kirilllzaitsev 8df5c5b0c4 rm sans-serif from CM plot 2023-07-19 23:07:28 +02:00
kirilllzaitsev abbc0e44b0 fixes patch 2023-07-19 23:01:50 +02:00
kirilllzaitsev 950744111a fix type annotations 2023-07-19 22:54:39 +02:00
Piotr Skalski 20954a2996 Merge pull request #180 from onuralpszr/python-toml-file-convert
Introduce pyproject toml file with optional install method and dropping python 3.7 support
2023-07-18 22:59:55 +02:00
Piotr Skalski 22d82c306f Merge pull request #173 from hardikdava/mmdetection_support
Mmdetection support
2023-07-18 15:27:30 +02:00
Onuralp SEZER 399121b65e fix: 🐛 lower pillow version for google-colab environment
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-18 16:24:17 +03:00
Piotr Skalski 1b9c65bdf8 🛠️ Merge pull request #192 from hardikdava/fix/coco_empty_fix
🛠️ Fixing empty detection coco dataset
2023-07-14 15:26:28 +02:00
SkalskiP 3a8c4d63ea 🧪 tests added 2023-07-14 15:06:55 +02:00
hardik bfe058c307 Fixing empty dataset 2023-07-13 20:19:55 +02:00
kirilllzaitsev 2c0905ba28 reformat 2023-07-08 12:29:50 +02:00
kirilllzaitsev fd717ea749 extend docs 2023-07-08 12:29:16 +02:00
kirilllzaitsev 40853350fd make plot() return figure 2023-07-08 12:11:24 +02:00
kirilllzaitsev fdd8127293 add source 2023-07-08 11:58:51 +02:00
kirilllzaitsev 21a480b483 refactor, remove duplicated 2023-07-08 11:54:25 +02:00
kirilllzaitsev 1b013f85e5 remove num_classes attr of ConfusionMatrix 2023-07-08 11:40:35 +02:00
kirilllzaitsev 36b48def89 rename vars, refactor ternary 2023-07-08 11:40:07 +02:00
kirilllzaitsev b7f72b79de add test_benchmark 2023-07-08 10:06:14 +02:00
kirilllzaitsev 141428310a add dummy_detection_dataset to utils 2023-07-08 10:05:52 +02:00
kirilllzaitsev 8f39df2fc0 add test_from_matrix 2023-07-07 22:54:22 +02:00
kirilllzaitsev 5107dd359f add test_drop_extra_matches 2023-07-07 22:49:47 +02:00
kirilllzaitsev d480ff5be2 add test_evaluate_detection_batch 2023-07-07 22:34:23 +02:00
kirilllzaitsev 2b876bdd64 add from_matrix() 2023-07-07 22:29:17 +02:00
kirilllzaitsev 476be527e5 add test_from_detections 2023-07-07 22:28:21 +02:00
Onuralp SEZER f78ac90464 feat(package): 📦 opencv-python-headless set as default
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-07 20:53:58 +03:00
kirilllzaitsev d4ea295f09 add empty test templates 2023-07-06 22:46:45 +02:00
kirilllzaitsev 9c5c296fb6 docs 2023-07-06 22:43:43 +02:00
kirilllzaitsev 85c4d5a644 rm extra colorbar configs 2023-07-06 22:30:37 +02:00
kirilllzaitsev ad06937653 set figsize as plot() arg 2023-07-06 22:29:22 +02:00
kirilllzaitsev e01e495c92 rm test setup from plot() 2023-07-06 22:27:53 +02:00
kirilllzaitsev 6784c78b4f refine plot() 2023-07-06 22:27:21 +02:00
kirilllzaitsev 4375e19245 add benchmark() 2023-07-06 09:37:00 +02:00
Onuralp SEZER a75a92708c feat(package): 📦 pillow library added into toml file as deps
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-06 09:38:55 +03:00
Onuralp SEZER c83e57c638 fix: 📝 rename extras group into desktop and headless
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-06 09:35:27 +03:00
hd fcde1cea9c 🔥 working 2023-07-05 18:37:05 +02:00
hd 8614984f8f Initial commit for Detection Dataset Accumulator 2023-07-05 18:33:17 +02:00
Onuralp SEZER cc81ae723b feat(version): ✨ dynamic version from __package__
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 15:37:50 +03:00
Onuralp SEZER f7a4a788ed docs: 📝 removing python 3.7 metadata pyproject
docs: 📝 change min python 3.7 to 3.8 in readme

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 15:21:35 +03:00
kirilllzaitsev 35d9ee7bad non-int ticks 2023-07-05 14:08:39 +02:00
Piotr Skalski abbde887dc Merge pull request #176 from roboflow/fix/coco_incorrect_class_id_processing
🛠️ fix incorrect COCO class id loading
2023-07-05 11:40:34 +02:00
SkalskiP 4f5f87acb8 1️⃣ changing logic of to_coco to start image_id and annotation_id from 1 2023-07-05 11:37:44 +02:00
hd 3f78150a1d Fixing docstrings and model instructions 2023-07-05 10:11:55 +02:00
hd 183ba1e809 Fixing conversion 2023-07-05 10:09:04 +02:00
hd 597c776c39 Fixing typo in variable 2023-07-05 10:06:23 +02:00
hd 5291706bc1 revert to original method 2023-07-05 09:59:16 +02:00
hd a50e47e7c0 tracked detection method added along with trackstorage 2023-07-05 09:20:50 +02:00
Onuralp SEZER c9d85477f1 ci(pypa-action): introduce pypa github action for uploading to pypi and pypi-test
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 01:39:47 +03:00
Onuralp SEZER 19271f362e fix: toml file version fix and removal of setup.py and adjusment on makefile
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 00:33:15 +03:00
Onuralp SEZER 9e1e9fc2fc ci(removal): 🧪 removing python 3.7 from test and update other action versions
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 00:04:41 +03:00
Onuralp SEZER 629744dee1 feat(pyproject): 🐍 introduce pyproject toml file
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 00:04:04 +03:00
kirilllzaitsev c9d2b52900 minor doc update 2023-07-04 22:07:18 +02:00
kirilllzaitsev 43cca52a58 add _drop_extra_matches from onemetric 2023-07-04 22:04:38 +02:00
kirilllzaitsev e1ee80cb37 add docstring to from_detections 2023-07-04 22:03:10 +02:00
kirilllzaitsev 6a8f3fc03b add detection module 2023-07-04 21:47:56 +02:00
SkalskiP c4b33fe1d6 ✅ ready for test and review 2023-07-04 17:31:33 +02:00
hd ebfaee22eb updated codebase 2023-07-03 17:30:53 +02:00
hd 9c062aac29 Support for openmmlab detections 2023-07-03 17:28:40 +02:00
SkalskiP 3e4cb6df42 🪵 updating change log before 0.11.1 release 2023-06-29 15:01:35 +02:00
Piotr Skalski 5fdbb70c60 Merge pull request #165 from roboflow/fix-classification
Fix top_k processing in as_folder_structure
2023-06-29 14:53:43 +02:00
James Gallagher 9aee232d04 fix top_k processing in as_folder_structure 2023-06-29 13:50:47 +01:00
27 changed files with 4825 additions and 204 deletions
+4 -3
View File
@@ -5,14 +5,15 @@ on:
branches:
- master
- main
- develop
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: pip install mkdocs-material
- run: pip install "mkdocstrings[python]"
- run: mkdocs gh-deploy --force
- run: mkdocs gh-deploy --force
+23 -8
View File
@@ -16,17 +16,32 @@ jobs:
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: 🦾 Install dependencies
run: |
python -m pip install --upgrade virtualenv
python -m pip install --upgrade pip
pip install ".[dev]"
- name: 🚀 Publish to PyPi
env:
PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }}
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
PYPI_TEST_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }}
virtualenv venv
source venv/bin/activate
python -m pip install --upgrade pip
python -m pip install --upgrade poetry
poetry install
- name: 🏗️ Build source and wheel distributions
run: |
make publish -e PYPI_USERNAME=$PYPI_USERNAME -e PYPI_PASSWORD=$PYPI_PASSWORD -e PYPI_TEST_PASSWORD=$PYPI_TEST_PASSWORD
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: 🚀 Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_PASSWORD }}
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_TEST_PASSWORD }}
+3 -5
View File
@@ -2,21 +2,19 @@ name: Test WorkFlow
on:
pull_request:
branches: [main]
branches: [main, develop]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, '3.10']
python-version: ["3.8", "3.9", "3.10","3.11"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: 🦾 Install dependencies
+1 -1
View File
@@ -2,7 +2,7 @@ cff-version: 1.2.0
author: Roboflow
message: If you use this software, please cite it as below.
title: supervision
version: 0.11.0
version: 0.11.1
date-released: 2023-01-19
license: MIT
repository-code: https://github.com/roboflow/supervision
+1 -1
View File
@@ -16,7 +16,7 @@ check_code_quality:
flake8 $(check_dirs) --count --max-line-length=88 --exit-zero --ignore=D --extend-ignore=E203,E501,W503 --statistics
publish:
python setup.py sdist bdist_wheel
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
+28 -24
View File
@@ -25,29 +25,13 @@
## 💻 install
Pip install the supervision package in a
[**3.11>=Python>=3.7**](https://www.python.org/) environment.
[**3.11>=Python>=3.8**](https://www.python.org/) environment.
```bash
pip install supervision
pip install supervision[desktop]
```
<details close>
<summary>👉 install from source</summary>
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
python3 -m venv venv
source venv/bin/activate
# install
pip install -e ".[dev]"
```
</details>
Read more about desktop, headless and local installation in our [guide](https://roboflow.github.io/supervision/).
## 🔥 quickstart
@@ -237,6 +221,30 @@ pip install -e ".[dev]"
</details>
### [model evaluation](https://roboflow.github.io/supervision/metrics/detection/)
```python
>>> import supervision as sv
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... ...
>>> confusion_matrix = sv.ConfusionMatrix.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
## 🎬 tutorials
<p align="left">
@@ -255,11 +263,7 @@ pip install -e ".[dev]"
## 📚 documentation
Curious how Supervision can help you solve problems on your project? Visit our [documentation](https://roboflow.github.io/supervision) page!
## 💜 built with supervision
You built something cool using supervision? [Let us know!](https://github.com/roboflow/supervision/discussions/categories/built-with-supervision)
Visit our [documentation](https://roboflow.github.io/supervision) page to learn how supervision can help you build computer vision applications faster and more reliably.
## 🏆 contribution
+89 -42
View File
@@ -1,6 +1,53 @@
### 0.12.0 <small>July 24, 2023</small>
!!! warning
With the `supervision-0.12.0` release, we are terminating official support for Python 3.7.
- Added [#177](https://github.com/roboflow/supervision/pull/177): initial support for object detection model benchmarking with [`sv.ConfusionMatrix`](https://roboflow.github.io/supervision/metrics/detection/#confusionmatrix).
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... result = model(image)[0]
... return sv.Detections.from_yolov8(result)
>>> confusion_matrix = sv.ConfusionMatrix.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
- Added [#173](https://github.com/roboflow/supervision/pull/173): [`Detections.from_mmdetection`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_mmdetection) to enable seamless integration with [MMDetection](https://github.com/open-mmlab/mmdetection) framework.
- Added [#130](https://github.com/roboflow/supervision/issues/130): ability to [install](https://roboflow.github.io/supervision/) package in `headless` or `desktop` mode.
- Changed [#180](https://github.com/roboflow/supervision/pull/180): packing method from `setup.py` to `pyproject.toml`.
- Fixed [#188](https://github.com/roboflow/supervision/issues/188): [`sv.DetectionDataset.from_cooc`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_coco) can't be loaded when there are images without annotations.
- Fixed [#226](https://github.com/roboflow/supervision/issues/226): [`sv.DetectionDataset.from_yolo`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_yolo) can't load background instances.
### 0.11.1 <small>June 29, 2023</small>
- Fix [#165](https://github.com/roboflow/supervision/pull/165): [`as_folder_structure`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.ClassificationDataset.as_folder_structure) fails to save [`sv.ClassificationDataset`](https://roboflow.github.io/supervision/dataset/core/#classificationdataset) when it is result of inference.
### 0.11.0 <small>June 28, 2023</small>
- Added [[#150](https://github.com/roboflow/supervision/pull/150)]: ability to load and save [`sv.DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) in COCO format using [`as_coco`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.as_coco) and [`from_coco`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_coco) methods.
- Added [#150](https://github.com/roboflow/supervision/pull/150): ability to load and save [`sv.DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) in COCO format using [`as_coco`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.as_coco) and [`from_coco`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_coco) methods.
```python
>>> import supervision as sv
@@ -16,7 +63,7 @@
... )
```
- Added [[#158](https://github.com/roboflow/supervision/pull/158)]: ability to marge multiple [`sv.DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) together using [`merge`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.merge) method.
- Added [#158](https://github.com/roboflow/supervision/pull/158): ability to marge multiple [`sv.DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) together using [`merge`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.merge) method.
```python
>>> import supervision as sv
@@ -40,13 +87,13 @@
['cat', 'dog', 'person']
```
- Added [[#162](https://github.com/roboflow/supervision/pull/162)]: additional `start` and `end` arguments to [`sv.get_video_frames_generator`](https://roboflow.github.io/supervision/utils/video/#get_video_frames_generator) allowing to generate frames only for a selected part of the video.
- Added [#162](https://github.com/roboflow/supervision/pull/162): additional `start` and `end` arguments to [`sv.get_video_frames_generator`](https://roboflow.github.io/supervision/utils/video/#get_video_frames_generator) allowing to generate frames only for a selected part of the video.
- Fix [[#157](https://github.com/roboflow/supervision/pull/157)]: incorrect loading of YOLO dataset class names from `data.yaml`.
- Fix [#157](https://github.com/roboflow/supervision/pull/157): incorrect loading of YOLO dataset class names from `data.yaml`.
### 0.10.0 <small>June 14, 2023</small>
- Added [[#125](https://github.com/roboflow/supervision/pull/125)]: ability to load and save [`sv.ClassificationDataset`](https://roboflow.github.io/supervision/dataset/core/#classificationdataset) in a folder structure format.
- Added [#125](https://github.com/roboflow/supervision/pull/125): ability to load and save [`sv.ClassificationDataset`](https://roboflow.github.io/supervision/dataset/core/#classificationdataset) in a folder structure format.
```python
>>> import supervision as sv
@@ -60,17 +107,17 @@
... )
```
- Added [[#125](https://github.com/roboflow/supervision/pull/125)]: support for [`sv.ClassificationDataset.split`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.ClassificationDataset.split) allowing to divide `sv.ClassificationDataset` into two parts.
- Added [#125](https://github.com/roboflow/supervision/pull/125): support for [`sv.ClassificationDataset.split`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.ClassificationDataset.split) allowing to divide `sv.ClassificationDataset` into two parts.
- Added [[#110](https://github.com/roboflow/supervision/pull/110)]: ability to extract masks from Roboflow API results using [`sv.Detections.from_roboflow`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_roboflow).
- Added [#110](https://github.com/roboflow/supervision/pull/110): ability to extract masks from Roboflow API results using [`sv.Detections.from_roboflow`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_roboflow).
- Added [[commit hash](https://github.com/roboflow/supervision/commit/d000292eb2f2342544e0947b65528082e60fb8d6)]: Supervision Quickstart [notebook](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb) where you can learn more about Detection, Dataset and Video APIs.
- Added [commit hash](https://github.com/roboflow/supervision/commit/d000292eb2f2342544e0947b65528082e60fb8d6): Supervision Quickstart [notebook](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb) where you can learn more about Detection, Dataset and Video APIs.
- Changed [[#135](https://github.com/roboflow/supervision/pull/135)]: `sv.get_video_frames_generator` documentation to better describe actual behavior.
- Changed [#135](https://github.com/roboflow/supervision/pull/135): `sv.get_video_frames_generator` documentation to better describe actual behavior.
### 0.9.0 <small>June 7, 2023</small>
- Added [[#118](https://github.com/roboflow/supervision/pull/118)]: ability to select [`sv.Detections`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.__getitem__) by index, list of indexes or slice. Here is an example illustrating the new selection methods.
- Added [#118](https://github.com/roboflow/supervision/pull/118): ability to select [`sv.Detections`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.__getitem__) by index, list of indexes or slice. Here is an example illustrating the new selection methods.
```python
>>> import supervision as sv
@@ -84,11 +131,11 @@
2
```
- Added [[#101](https://github.com/roboflow/supervision/pull/101)]: ability to extract masks from YOLOv8 result using [`sv.Detections.from_yolov8`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_yolov8). Here is an example illustrating how to extract boolean masks from the result of the YOLOv8 model inference.
- Added [#101](https://github.com/roboflow/supervision/pull/101): ability to extract masks from YOLOv8 result using [`sv.Detections.from_yolov8`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_yolov8). Here is an example illustrating how to extract boolean masks from the result of the YOLOv8 model inference.
- Added [[#122](https://github.com/roboflow/supervision/pull/122)]: ability to crop image using [`sv.crop`](https://roboflow.github.io/supervision/utils/image/#crop). Here is an example showing how to get a separate crop for each detection in `sv.Detections`.
- Added [#122](https://github.com/roboflow/supervision/pull/122): ability to crop image using [`sv.crop`](https://roboflow.github.io/supervision/utils/image/#crop). Here is an example showing how to get a separate crop for each detection in `sv.Detections`.
- Added [[#120](https://github.com/roboflow/supervision/pull/120)]: ability to conveniently save multiple images into directory using [`sv.ImageSink`](https://roboflow.github.io/supervision/utils/image/#imagesink). Here is an example showing how to save every tenth video frame as a separate image.
- Added [#120](https://github.com/roboflow/supervision/pull/120): ability to conveniently save multiple images into directory using [`sv.ImageSink`](https://roboflow.github.io/supervision/utils/image/#imagesink). Here is an example showing how to save every tenth video frame as a separate image.
```python
>>> import supervision as sv
@@ -98,12 +145,12 @@
... sink.save_image(image=image)
```
- Fixed [[#106](https://github.com/roboflow/supervision/issues/106)]: inconvenient handling of [`sv.PolygonZone`](https://roboflow.github.io/supervision/detection/tools/polygon_zone/#polygonzone) coordinates. Now `sv.PolygonZone` accepts coordinates in the form of `[[x1, y1], [x2, y2], ...]` that can be both integers and floats.
- Fixed [#106](https://github.com/roboflow/supervision/issues/106): inconvenient handling of [`sv.PolygonZone`](https://roboflow.github.io/supervision/detection/tools/polygon_zone/#polygonzone) coordinates. Now `sv.PolygonZone` accepts coordinates in the form of `[[x1, y1], [x2, y2], ...]` that can be both integers and floats.
### 0.8.0 <small>May 17, 2023</small>
- Added [[#100](https://github.com/roboflow/supervision/pull/100)]: support for dataset inheritance. The current `Dataset` got renamed to `DetectionDataset`. Now [`DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) inherits from `BaseDataset`. This change was made to enforce the future consistency of APIs of different types of computer vision datasets.
- Added [[#100](https://github.com/roboflow/supervision/pull/100)]: ability to save datasets in YOLO format using [`DetectionDataset.as_yolo`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.as_yolo).
- Added [#100](https://github.com/roboflow/supervision/pull/100): support for dataset inheritance. The current `Dataset` got renamed to `DetectionDataset`. Now [`DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) inherits from `BaseDataset`. This change was made to enforce the future consistency of APIs of different types of computer vision datasets.
- Added [#100](https://github.com/roboflow/supervision/pull/100): ability to save datasets in YOLO format using [`DetectionDataset.as_yolo`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.as_yolo).
```python
>>> import roboflow
@@ -127,7 +174,7 @@
['dog', 'person']
```
- Added [[#102](https://github.com/roboflow/supervision/pull/103)]: support for [`DetectionDataset.split`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.split) allowing to divide `DetectionDataset` into two parts.
- Added [#102](https://github.com/roboflow/supervision/pull/103): support for [`DetectionDataset.split`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.split) allowing to divide `DetectionDataset` into two parts.
```python
>>> import supervision as sv
@@ -139,27 +186,27 @@
(700, 300)
```
- Changed [[#100](https://github.com/roboflow/supervision/pull/100)]: default value of `approximation_percentage` parameter from `0.75` to `0.0` in `DetectionDataset.as_yolo` and `DetectionDataset.as_pascal_voc`.
- Changed [#100](https://github.com/roboflow/supervision/pull/100): default value of `approximation_percentage` parameter from `0.75` to `0.0` in `DetectionDataset.as_yolo` and `DetectionDataset.as_pascal_voc`.
### 0.7.0 <small>May 11, 2023</small>
- Added [[#91](https://github.com/roboflow/supervision/pull/91)]: `Detections.from_yolo_nas` to enable seamless integration with [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) model.
- Added [[#86](https://github.com/roboflow/supervision/pull/86)]: ability to load datasets in YOLO format using `Dataset.from_yolo`.
- Added [[#84](https://github.com/roboflow/supervision/pull/84)]: `Detections.merge` to merge multiple `Detections` objects together.
- Fixed [[#81](https://github.com/roboflow/supervision/pull/81)]: `LineZoneAnnotator.annotate` does not return annotated frame.
- Changed [[#44](https://github.com/roboflow/supervision/pull/44)]: `LineZoneAnnotator.annotate` to allow for custom text for the in and out tags.
- Added [#91](https://github.com/roboflow/supervision/pull/91): `Detections.from_yolo_nas` to enable seamless integration with [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) model.
- Added [#86](https://github.com/roboflow/supervision/pull/86): ability to load datasets in YOLO format using `Dataset.from_yolo`.
- Added [#84](https://github.com/roboflow/supervision/pull/84): `Detections.merge` to merge multiple `Detections` objects together.
- Fixed [#81](https://github.com/roboflow/supervision/pull/81): `LineZoneAnnotator.annotate` does not return annotated frame.
- Changed [#44](https://github.com/roboflow/supervision/pull/44): `LineZoneAnnotator.annotate` to allow for custom text for the in and out tags.
### 0.6.0 <small>April 19, 2023</small>
- Added [[#71](https://github.com/roboflow/supervision/pull/71)]: initial `Dataset` support and ability to save `Detections` in Pascal VOC XML format.
- Added [[#71](https://github.com/roboflow/supervision/pull/71)]: new `mask_to_polygons`, `filter_polygons_by_area`, `polygon_to_xyxy` and `approximate_polygon` utilities.
- Added [[#72](https://github.com/roboflow/supervision/pull/72)]: ability to load Pascal VOC XML **object detections** dataset as `Dataset`.
- Changed [[#70](https://github.com/roboflow/supervision/pull/70)]: order of `Detections` attributes to make it consistent with order of objects in `__iter__` tuple.
- Changed [[#71](https://github.com/roboflow/supervision/pull/71)]: `generate_2d_mask` to `polygon_to_mask`.
- Added [#71](https://github.com/roboflow/supervision/pull/71): initial `Dataset` support and ability to save `Detections` in Pascal VOC XML format.
- Added [#71](https://github.com/roboflow/supervision/pull/71): new `mask_to_polygons`, `filter_polygons_by_area`, `polygon_to_xyxy` and `approximate_polygon` utilities.
- Added [#72](https://github.com/roboflow/supervision/pull/72): ability to load Pascal VOC XML **object detections** dataset as `Dataset`.
- Changed [#70](https://github.com/roboflow/supervision/pull/70): order of `Detections` attributes to make it consistent with order of objects in `__iter__` tuple.
- Changed [#71](https://github.com/roboflow/supervision/pull/71): `generate_2d_mask` to `polygon_to_mask`.
### 0.5.2 <small>April 13, 2023</small>
- Fixed [[#63](https://github.com/roboflow/supervision/pull/63)]: `LineZone.trigger` function expects 4 values instead of 5.
- Fixed [#63](https://github.com/roboflow/supervision/pull/63): `LineZone.trigger` function expects 4 values instead of 5.
### 0.5.1 <small>April 12, 2023</small>
@@ -168,28 +215,28 @@
### 0.5.0 <small>April 10, 2023</small>
- Added [[#58](https://github.com/roboflow/supervision/pull/58)]: `Detections.mask` to enable segmentation support.
- Added [[#58](https://github.com/roboflow/supervision/pull/58)]: `MaskAnnotator` to allow easy `Detections.mask` annotation.
- Added [[#58](https://github.com/roboflow/supervision/pull/58)]: `Detections.from_sam` to enable native Segment Anything Model (SAM) support.
- Changed [[#58](https://github.com/roboflow/supervision/pull/58)]: `Detections.area` behaviour to work not only with boxes but also with masks.
- Added [#58](https://github.com/roboflow/supervision/pull/58): `Detections.mask` to enable segmentation support.
- Added [#58](https://github.com/roboflow/supervision/pull/58): `MaskAnnotator` to allow easy `Detections.mask` annotation.
- Added [#58](https://github.com/roboflow/supervision/pull/58): `Detections.from_sam` to enable native Segment Anything Model (SAM) support.
- Changed [#58](https://github.com/roboflow/supervision/pull/58): `Detections.area` behaviour to work not only with boxes but also with masks.
### 0.4.0 <small>April 5, 2023</small>
- Added [[#46](https://github.com/roboflow/supervision/discussions/48)]: `Detections.empty` to allow easy creation of empty `Detections` objects.
- Added [[#56](https://github.com/roboflow/supervision/pull/56)]: `Detections.from_roboflow` to allow easy creation of `Detections` objects from Roboflow API inference results.
- Added [[#56](https://github.com/roboflow/supervision/pull/56)]: `plot_images_grid` to allow easy plotting of multiple images on single plot.
- Added [[#56](https://github.com/roboflow/supervision/pull/56)]: initial support for Pascal VOC XML format with `detections_to_voc_xml` method.
- Changed [[#56](https://github.com/roboflow/supervision/pull/56)]: `show_frame_in_notebook` refactored and renamed to `plot_image`.
- Added [#46](https://github.com/roboflow/supervision/discussions/48): `Detections.empty` to allow easy creation of empty `Detections` objects.
- Added [#56](https://github.com/roboflow/supervision/pull/56): `Detections.from_roboflow` to allow easy creation of `Detections` objects from Roboflow API inference results.
- Added [#56](https://github.com/roboflow/supervision/pull/56): `plot_images_grid` to allow easy plotting of multiple images on single plot.
- Added [#56](https://github.com/roboflow/supervision/pull/56): initial support for Pascal VOC XML format with `detections_to_voc_xml` method.
- Changed [#56](https://github.com/roboflow/supervision/pull/56): `show_frame_in_notebook` refactored and renamed to `plot_image`.
### 0.3.2 <small>March 23, 2023</small>
- Changed [[#50](https://github.com/roboflow/supervision/issues/50)]: Allow `Detections.class_id` to be `None`.
- Changed [#50](https://github.com/roboflow/supervision/issues/50): Allow `Detections.class_id` to be `None`.
### 0.3.1 <small>March 6, 2023</small>
- Fixed [[#41](https://github.com/roboflow/supervision/issues/41)]: `PolygonZone` throws an exception when the object touches the bottom edge of the image.
- Fixed [[#42](https://github.com/roboflow/supervision/issues/42)]: `Detections.wth_nms` method throws an exception when `Detections` is empty.
- Changed [[#36](https://github.com/roboflow/supervision/pull/36)]: `Detections.wth_nms` support class agnostic and non-class agnostic case.
- Fixed [#41](https://github.com/roboflow/supervision/issues/41): `PolygonZone` throws an exception when the object touches the bottom edge of the image.
- Fixed [#42](https://github.com/roboflow/supervision/issues/42): `Detections.wth_nms` method throws an exception when `Detections` is empty.
- Changed [#36](https://github.com/roboflow/supervision/pull/36): `Detections.wth_nms` support class agnostic and non-class agnostic case.
### 0.3.0 <small>March 6, 2023</small>
+54 -18
View File
@@ -9,29 +9,65 @@
</p>
</div>
## 👋 Welcome
## 👋 Hello
Supervision is a set of easy-to-use utilities that will come in handy in any computer vision project.
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!
**Supervision** is still in
pre-release stage 🚧 Keep your eyes open for potential bugs and be aware that at this stage our API is still fluid and may change.
## 💻 How to Install
## 💻 Install
You can install `supervision` with pip in a
[**3.11>=Python>=3.7**](https://www.python.org/) environment.
[**3.11>=Python>=3.8**](https://www.python.org/) environment.
!!! example "Pip install method (recommended)"
!!! example "pip install (recommended)"
```bash
pip install supervision
```
=== "headless"
The headless installation of `supervision` is designed for environments where graphical user interfaces (GUI) are not needed, making it more lightweight and suitable for server-side applications.
!!! example "Git clone method (for development)"
```bash
pip install supervision
```
=== "desktop"
If you require the full version of `supervision` with GUI support you can install the desktop version. This version includes the GUI components of OpenCV, allowing you to display images and videos on the screen.
```bash
git https://github.com/roboflow/supervision.git
cd supervision
pip install -e '.[dev]'
```
See contributing section to know more about contributing to the project
```bash
pip install supervision[desktop]
```
!!! example "git clone (for development)"
=== "virtualenv"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
python3 -m venv venv
source venv/bin/activate
# headless install
pip install -e "."
# desktop install
pip install -e ".[desktop]"
```
=== "poetry"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
poetry env use python 3.10
poetry shell
# headless install
poetry install
# desktop install
poetry install --extras "desktop"
```
+8
View File
@@ -0,0 +1,8 @@
!!! warning
Evaluation API is still fluid and may change. If you use Evaluation API in your project until further notice, freeze the
`supervision` version in your `requirements.txt` or `setup.py`.
## ConfusionMatrix
:::supervision.metrics.detection.ConfusionMatrix
+2
View File
@@ -39,6 +39,8 @@ nav:
- Polygon Zone: detection/tools/polygon_zone.md
- Dataset:
- Core: dataset/core.md
- Metrics:
- Detection Models: metrics/detection.md
- Draw:
- Utils: draw/utils.md
- Utils:
Generated
+3446
View File
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
[tool.poetry]
name = "supervision"
version = "0.12.0"
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
authors = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
maintainers = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
readme = "README.md"
packages = [{include = "supervision"}]
homepage = "https://github.com/roboflow/supervision"
repository = "https://github.com/roboflow/supervision"
documentation = "https://github.com/roboflow/supervision/blob/main/README.md"
keywords = ["machine-learning", "deep-learning", "vision", "ML", "DL", "AI", "YOLOv5", "YOLOv8", "Roboflow"]
classifiers=[
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development',
'Topic :: Scientific/Engineering',
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
'Typing :: Typed',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Operating System :: Unix',
'Operating System :: MacOS'
]
[tool.poetry.dependencies]
python = ">=3.8,<3.12.0"
numpy = "^1.20.0"
matplotlib = "^3.7.1"
pyyaml = "^6.0"
pillow = "^8.4.0"
opencv-python = { version = "^4.8.0.74", optional = true }
opencv-python-headless = "^4.8.0.74"
[tool.poetry.extras]
desktop = ["opencv-python"]
[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"
ruff = "^0.0.280"
isort = "^5.12.0"
black = "^23.7.0"
mypy = "^1.4.1"
pre-commit = "^3.3.3"
flake8 = { version = "*", python = ">=3.8.1,<3.12.0" }
[tool.poetry.group.docs.dependencies]
mkdocs-material = "^9.1.4"
mkdocstrings = {extras = ["python"], version = "^0.20.0"}
[tool.setuptools]
include-package-data = false
[tool.setuptools.packages.find]
exclude = ["docs*", "test*"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
-71
View File
@@ -1,71 +0,0 @@
import setuptools
from setuptools import find_packages
import re
from pathlib import Path
FILE = Path(__file__).resolve()
PARENT = FILE.parent # root directory
README = (PARENT / "README.md").read_text(encoding="utf-8")
def get_version():
file = PARENT / 'supervision/__init__.py'
return re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', file.read_text(encoding="utf-8"), re.M)[1]
setuptools.setup(
name='supervision',
version=get_version(),
author='Piotr Skalski',
author_email='piotr.skalski92@gmail.com',
license='MIT',
description='A set of easy-to-use utils that will come in handy in any Computer Vision project',
long_description=README,
long_description_content_type='text/markdown',
url='https://github.com/roboflow/supervision',
install_requires=[
'numpy>=1.20.0',
'opencv-python',
'matplotlib',
'pyyaml'
],
packages=find_packages(exclude=("tests",)),
extras_require={
'dev': [
'flake8',
'black==22.3.0',
'isort',
'twine',
'pytest',
'wheel',
'notebook',
'mkdocs-material',
'mkdocstrings[python]'
],
},
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development',
'Topic :: Scientific/Engineering',
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
'Typing :: Typed',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Operating System :: Unix',
'Operating System :: MacOS'
],
keywords="machine-learning, deep-learning, vision, ML, DL, AI, YOLOv5, YOLOv8, SAM, Roboflow",
python_requires='>=3.7',
)
+9 -1
View File
@@ -1,4 +1,11 @@
__version__ = "0.11.0"
import importlib.metadata as importlib_metadata
try:
# This will read version from pyproject.toml
__version__ = importlib_metadata.version(__package__ or __name__)
except importlib_metadata.PackageNotFoundError:
__version__ = "development"
from supervision.classification.core import Classifications
from supervision.dataset.core import (
@@ -23,6 +30,7 @@ from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import draw_filled_rectangle, draw_polygon, draw_text
from supervision.geometry.core import Point, Position, Rect
from supervision.geometry.utils import get_polygon_center
from supervision.metrics.detection import ConfusionMatrix
from supervision.utils.file import list_files_with_extensions
from supervision.utils.image import ImageSink, crop
from supervision.utils.notebook import plot_image, plot_images_grid
+1 -1
View File
@@ -584,7 +584,7 @@ class ClassificationDataset(BaseDataset):
class_id = (
classification.class_id[0]
if classification.confidence is None
else classification.get_top_k(1)[0]
else classification.get_top_k(1)[0][0]
)
class_name = self.classes[class_id]
image_path = os.path.join(root_directory_path, class_name, image_name)
+24 -3
View File
@@ -6,7 +6,10 @@ from typing import Dict, List, Tuple
import cv2
import numpy as np
from supervision.dataset.utils import approximate_mask_with_polygons
from supervision.dataset.utils import (
approximate_mask_with_polygons,
map_detections_class_id,
)
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask
from supervision.utils.file import read_json_file, save_json_file
@@ -19,6 +22,18 @@ def coco_categories_to_classes(coco_categories: List[dict]) -> List[str]:
]
def build_coco_class_index_mapping(
coco_categories: List[dict], target_classes: List[str]
) -> Dict[int, int]:
source_class_to_index = {
category["name"]: category["id"] for category in coco_categories
}
return {
source_class_to_index[target_class_name]: target_class_index
for target_class_index, target_class_name in enumerate(target_classes)
}
def classes_to_coco_categories(classes: List[str]) -> List[dict]:
return [
{
@@ -124,6 +139,9 @@ def load_coco_annotations(
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
coco_data = read_json_file(file_path=annotations_path)
classes = coco_categories_to_classes(coco_categories=coco_data["categories"])
class_index_mapping = build_coco_class_index_mapping(
coco_categories=coco_data["categories"], target_classes=classes
)
coco_images = coco_data["images"]
coco_annotations_groups = group_coco_annotations_by_image_id(
coco_annotations=coco_data["annotations"]
@@ -147,6 +165,10 @@ def load_coco_annotations(
resolution_wh=(image_width, image_height),
with_masks=force_masks,
)
annotation = map_detections_class_id(
source_to_target_mapping=class_index_mapping,
detections=annotation,
)
images[image_name] = image
annotations[image_name] = annotation
@@ -177,8 +199,7 @@ def save_coco_annotations(
coco_images = []
coco_categories = classes_to_coco_categories(classes=classes)
image_id = 0
annotation_id = 0
image_id, annotation_id = 1, 1
for image_name, image in images.items():
image_height, image_width, _ = image.shape
+15 -16
View File
@@ -52,24 +52,16 @@ def _polygons_to_masks(
)
def _pair_image_with_annotation(
image_paths: List[Union[str, Path]], annotation_paths: List[Union[str, Path]]
) -> List[Tuple[Union[str, Path], Union[str, Path]]]:
image_dict = {p.stem: p for p in image_paths}
return [
(image_dict[annotation_path.stem], annotation_path)
for annotation_path in annotation_paths
if annotation_path.stem in image_dict
]
def _with_mask(lines: List[str]) -> bool:
return any([len(line.split()) > 5 for line in lines])
def _extract_class_names(file_path: str) -> List[str]:
data = read_yaml_file(file_path=file_path)
return data["names"]
names = data["names"]
if isinstance(names, dict):
names = [names[key] for key in sorted(names.keys())]
return names
def _image_name_to_annotation_name(image_name: str) -> str:
@@ -137,14 +129,21 @@ def load_yolo_annotations(
annotation_paths = list_files_with_extensions(
directory=annotations_directory_path, extensions=["txt"]
)
path_pairs = _pair_image_with_annotation(
image_paths=image_paths, annotation_paths=annotation_paths
)
classes = _extract_class_names(file_path=data_yaml_path)
images = {}
annotations = {}
for image_path, annotation_path in path_pairs:
for image_path in image_paths:
image_name = Path(image_path).stem
image = cv2.imread(str(image_path))
annotation_path = os.path.join(annotations_directory_path, f"{image_name}.txt")
if not os.path.exists(annotation_path):
images[image_path.name] = image
annotations[image_path.name] = Detections.empty()
continue
lines = read_txt_file(str(annotation_path))
h, w, _ = image.shape
resolution_wh = (w, h)
+5 -3
View File
@@ -83,9 +83,11 @@ def map_detections_class_id(
)
detections_copy = copy.deepcopy(detections)
detections_copy.class_id = np.vectorize(source_to_target_mapping.get)(
detections_copy.class_id
)
if len(detections) > 0:
detections_copy.class_id = np.vectorize(source_to_target_mapping.get)(
detections_copy.class_id
)
return detections_copy
+29
View File
@@ -228,6 +228,35 @@ class Detections:
class_id=yolo_nas_results.prediction.labels.astype(int),
)
@classmethod
def from_mmdetection(cls, mmdet_results) -> Detections:
"""
Creates a Detections instance from a [mmdetection](https://github.com/open-mmlab/mmdetection) inference result.
Also supported for [mmyolo](https://github.com/open-mmlab/mmyolo)
Args:
mmdet_results (mmdet.structures.DetDataSample): The output Results instance from MMDetection
Returns:
Detections: A new Detections object.
Example:
```python
>>> import cv2
>>> import supervision as sv
>>> from mmdet.apis import DetInferencer
>>> inferencer = DetInferencer(model_name, checkpoint, device)
>>> mmdet_result = inferencer(SOURCE_IMAGE_PATH, out_dir='./output', return_datasample=True)["predictions"][0]
>>> detections = sv.Detections.from_mmdet(mmdet_result)
```
"""
return cls(
xyxy=mmdet_results.pred_instances.bboxes.cpu().numpy(),
confidence=mmdet_results.pred_instances.scores.cpu().numpy(),
class_id=mmdet_results.pred_instances.labels.cpu().numpy().astype(int),
)
@classmethod
def from_transformers(cls, transformers_results: dict) -> Detections:
"""
View File
+458
View File
@@ -0,0 +1,458 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable, List, Optional, Tuple
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from supervision.dataset.core import DetectionDataset
from supervision.detection.core import Detections
from supervision.detection.utils import box_iou_batch
@dataclass
class ConfusionMatrix:
"""
Confusion matrix for object detection tasks.
Attributes:
matrix (np.ndarray): An 2D `np.ndarray` of shape `(len(classes) + 1, len(classes) + 1)` containing the number of `TP`, `FP`, `FN` and `TN` for each class.
classes (List[str]): Model class names.
conf_threshold (float): Detection confidence threshold between `0` and `1`. Detections with lower confidence will be excluded from the matrix.
iou_threshold (float): Detection IoU threshold between `0` and `1`. Detections with lower IoU will be classified as `FP`.
"""
matrix: np.ndarray
classes: List[str]
conf_threshold: float
iou_threshold: float
@classmethod
def from_detections(
cls,
predictions: List[Detections],
targets: List[Detections],
classes: List[str],
conf_threshold: float = 0.3,
iou_threshold: float = 0.5,
) -> ConfusionMatrix:
"""
Calculate confusion matrix based on predicted and ground-truth detections.
Args:
targets (List[Detections]): Detections objects from ground-truth.
predictions (List[Detections]): Detections objects predicted by the model.
classes (List[str]): Model class names.
conf_threshold (float): Detection confidence threshold between `0` and `1`. Detections with lower confidence will be excluded.
iou_threshold (float): Detection IoU threshold between `0` and `1`. Detections with lower IoU will be classified as `FP`.
Returns:
ConfusionMatrix: New instance of ConfusionMatrix.
Example:
```python
>>> import supervision as sv
>>> targets = [
... sv.Detections(...),
... sv.Detections(...)
... ]
>>> predictions = [
... sv.Detections(...),
... sv.Detections(...)
... ]
>>> confusion_matrix = sv.ConfusionMatrix.from_detections(
... predictions=predictions,
... targets=target,
... classes=['person', ...]
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
"""
prediction_tensors = []
target_tensors = []
for prediction, target in zip(predictions, targets):
prediction_tensors.append(
ConfusionMatrix.detections_to_tensor(prediction, with_confidence=True)
)
target_tensors.append(
ConfusionMatrix.detections_to_tensor(target, with_confidence=False)
)
return cls.from_tensors(
predictions=prediction_tensors,
targets=target_tensors,
classes=classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
@staticmethod
def detections_to_tensor(
detections: Detections, with_confidence: bool = False
) -> np.ndarray:
if detections.class_id is None:
raise ValueError(
"ConfusionMatrix can only be calculated for Detections with class_id"
)
arrays_to_concat = [detections.xyxy, np.expand_dims(detections.class_id, 1)]
if with_confidence:
if detections.confidence is None:
raise ValueError(
"ConfusionMatrix can only be calculated for Detections with confidence"
)
arrays_to_concat.append(np.expand_dims(detections.confidence, 1))
return np.concatenate(arrays_to_concat, axis=1)
@classmethod
def from_tensors(
cls,
predictions: List[np.ndarray],
targets: List[np.ndarray],
classes: List[str],
conf_threshold: float = 0.3,
iou_threshold: float = 0.5,
) -> ConfusionMatrix:
"""
Calculate confusion matrix based on predicted and ground-truth detections.
Args:
predictions (List[np.ndarray]): Each element of the list describes a single image and has `shape = (M, 6)` where `M` is the number of detected objects. Each row is expected to be in `(x_min, y_min, x_max, y_max, class, conf)` format.
targets (List[np.ndarray]): Each element of the list describes a single image and has `shape = (N, 5)` where `N` is the number of ground-truth objects. Each row is expected to be in `(x_min, y_min, x_max, y_max, class)` format.
classes (List[str]): Model class names.
conf_threshold (float): Detection confidence threshold between `0` and `1`. Detections with lower confidence will be excluded.
iou_threshold (float): Detection iou threshold between `0` and `1`. Detections with lower iou will be classified as `FP`.
Returns:
ConfusionMatrix: New instance of ConfusionMatrix.
Example:
```python
>>> import supervision as sv
>>> targets = (
... [
... array(
... [
... [0.0, 0.0, 3.0, 3.0, 1],
... [2.0, 2.0, 5.0, 5.0, 1],
... [6.0, 1.0, 8.0, 3.0, 2],
... ]
... ),
... array([1.0, 1.0, 2.0, 2.0, 2]),
... ]
... )
>>> predictions = [
... array(
... [
... [0.0, 0.0, 3.0, 3.0, 1, 0.9],
... [0.1, 0.1, 3.0, 3.0, 0, 0.9],
... [6.0, 1.0, 8.0, 3.0, 1, 0.8],
... [1.0, 6.0, 2.0, 7.0, 1, 0.8],
... ]
... ),
... array([[1.0, 1.0, 2.0, 2.0, 2, 0.8]])
... ]
>>> confusion_matrix = sv.ConfusionMatrix.from_tensors(
... predictions=predictions,
... targets=targets,
... classes=['person', ...]
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
"""
cls._validate_input_tensors(predictions, targets)
num_classes = len(classes)
matrix = np.zeros((num_classes + 1, num_classes + 1))
for true_batch, detection_batch in zip(targets, predictions):
matrix += cls.evaluate_detection_batch(
predictions=detection_batch,
targets=true_batch,
num_classes=num_classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
return cls(
matrix=matrix,
classes=classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
@classmethod
def _validate_input_tensors(
cls, predictions: List[np.ndarray], targets: List[np.ndarray]
):
"""
Checks for shape consistency of input tensors.
"""
if len(predictions) != len(targets):
raise ValueError(
f"Number of predictions ({len(predictions)}) and targets ({len(targets)}) must be equal."
)
if len(predictions) > 0:
if not isinstance(predictions[0], np.ndarray) or not isinstance(
targets[0], np.ndarray
):
raise ValueError(
f"Predictions and targets must be lists of numpy arrays. Got {type(predictions[0])} and {type(targets[0])} instead."
)
if predictions[0].shape[1] != 6:
raise ValueError(
f"Predictions must have shape (N, 6). Got {predictions[0].shape} instead."
)
if targets[0].shape[1] != 5:
raise ValueError(
f"Targets must have shape (N, 5). Got {targets[0].shape} instead."
)
@staticmethod
def evaluate_detection_batch(
predictions: np.ndarray,
targets: np.ndarray,
num_classes: int,
conf_threshold: float,
iou_threshold: float,
) -> np.ndarray:
"""
Calculate confusion matrix for a batch of detections for a single image.
Args:
predictions (List[np.ndarray]): Each element of the list describes a single image and has `shape = (M, 6)` where `M` is the number of detected objects. Each row is expected to be in `(x_min, y_min, x_max, y_max, class, conf)` format.
targets (List[np.ndarray]): Each element of the list describes a single image and has `shape = (N, 5)` where `N` is the number of ground-truth objects. Each row is expected to be in `(x_min, y_min, x_max, y_max, class)` format.
num_classes (int): Number of classes.
conf_threshold (float): Detection confidence threshold between `0` and `1`. Detections with lower confidence will be excluded.
iou_threshold (float): Detection iou threshold between `0` and `1`. Detections with lower iou will be classified as `FP`.
Returns:
np.ndarray: Confusion matrix based on a single image.
"""
result_matrix = np.zeros((num_classes + 1, num_classes + 1))
conf_idx = 5
confidence = predictions[:, conf_idx]
detection_batch_filtered = predictions[confidence > conf_threshold]
class_id_idx = 4
true_classes = np.array(targets[:, class_id_idx], dtype=np.int16)
detection_classes = np.array(
detection_batch_filtered[:, class_id_idx], dtype=np.int16
)
true_boxes = targets[:, :class_id_idx]
detection_boxes = detection_batch_filtered[:, :class_id_idx]
iou_batch = box_iou_batch(
boxes_true=true_boxes, boxes_detection=detection_boxes
)
matched_idx = np.asarray(iou_batch > iou_threshold).nonzero()
if matched_idx[0].shape[0]:
matches = np.stack(
(matched_idx[0], matched_idx[1], iou_batch[matched_idx]), axis=1
)
matches = ConfusionMatrix._drop_extra_matches(matches=matches)
else:
matches = np.zeros((0, 3))
matched_true_idx, matched_detection_idx, _ = matches.transpose().astype(
np.int16
)
for i, true_class_value in enumerate(true_classes):
j = matched_true_idx == i
if matches.shape[0] > 0 and sum(j) == 1:
result_matrix[
true_class_value, detection_classes[matched_detection_idx[j]]
] += 1 # TP
else:
result_matrix[true_class_value, num_classes] += 1 # FN
for i, detection_class_value in enumerate(detection_classes):
if not any(matched_detection_idx == i):
result_matrix[num_classes, detection_class_value] += 1 # FP
return result_matrix
@staticmethod
def _drop_extra_matches(matches: np.ndarray) -> np.ndarray:
"""
Deduplicate matches. If there are multiple matches for the same true or predicted box,
only the one with the highest IoU is kept.
"""
if matches.shape[0] > 0:
matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
return matches
@classmethod
def benchmark(
cls,
dataset: DetectionDataset,
callback: Callable[[np.ndarray], Detections],
conf_threshold: float = 0.3,
iou_threshold: float = 0.5,
) -> ConfusionMatrix:
"""
Create confusion matrix from dataset and callback function.
Args:
dataset (DetectionDataset): Object detection dataset used for evaluation.
callback (Callable[[np.ndarray], Detections]): Function that takes an image as input and returns Detections object.
conf_threshold (float): Detection confidence threshold between `0` and `1`. Detections with lower confidence will be excluded.
iou_threshold (float): Detection IoU threshold between `0` and `1`. Detections with lower IoU will be classified as `FP`.
Returns:
ConfusionMatrix: New instance of ConfusionMatrix.
Example:
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... result = model(image)[0]
... return sv.Detections.from_yolov8(result)
>>> confusion_matrix = sv.ConfusionMatrix.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
"""
predictions, targets = [], []
for img_name, img in dataset.images.items():
predictions_batch = callback(img)
predictions.append(predictions_batch)
targets_batch = dataset.annotations[img_name]
targets.append(targets_batch)
return cls.from_detections(
predictions=predictions,
targets=targets,
classes=dataset.classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
def plot(
self,
save_path: Optional[str] = None,
title: Optional[str] = None,
classes: Optional[List[str]] = None,
normalize: bool = False,
fig_size: Tuple[int, int] = (12, 10),
) -> matplotlib.figure.Figure:
"""
Create confusion matrix plot and save it at selected location.
Args:
save_path (Optional[str]): Path to save the plot. If not provided, plot will be displayed.
title (Optional[str]): Title of the plot.
classes (Optional[List[str]]): List of classes to be displayed on the plot. If not provided, all classes will be displayed.
normalize (bool): If True, normalize the confusion matrix.
fig_size (Tuple[int, int]): Size of the plot.
Returns:
matplotlib.figure.Figure: Confusion matrix plot.
"""
array = self.matrix.copy()
if normalize:
eps = 1e-8
array = array / (array.sum(0).reshape(1, -1) + eps)
array[array < 0.005] = np.nan
fig, ax = plt.subplots(figsize=fig_size, tight_layout=True, facecolor="white")
class_names = classes if classes is not None else self.classes
use_labels_for_ticks = class_names is not None and (0 < len(class_names) < 99)
if use_labels_for_ticks:
x_tick_labels = class_names + ["FN"]
y_tick_labels = class_names + ["FP"]
num_ticks = len(x_tick_labels)
else:
x_tick_labels = None
y_tick_labels = None
num_ticks = len(array)
im = ax.imshow(array, cmap="Blues")
cbar = ax.figure.colorbar(im, ax=ax)
cbar.mappable.set_clim(vmin=0, vmax=np.nanmax(array))
if x_tick_labels is None:
tick_interval = 2
else:
tick_interval = 1
ax.set_xticks(np.arange(0, num_ticks, tick_interval), labels=x_tick_labels)
ax.set_yticks(np.arange(0, num_ticks, tick_interval), labels=y_tick_labels)
plt.setp(ax.get_xticklabels(), rotation=90, ha="right", rotation_mode="default")
labelsize = 10 if num_ticks < 50 else 8
ax.tick_params(axis="both", which="both", labelsize=labelsize)
if num_ticks < 30:
for i in range(array.shape[0]):
for j in range(array.shape[1]):
n_preds = array[i, j]
if not np.isnan(n_preds):
ax.text(
j,
i,
f"{n_preds:.2f}" if normalize else f"{n_preds:.0f}",
ha="center",
va="center",
color="black"
if n_preds < 0.5 * np.nanmax(array)
else "white",
)
if title:
ax.set_title(title, fontsize=20)
ax.set_xlabel("Predicted")
ax.set_ylabel("True")
ax.set_facecolor("white")
if save_path:
fig.savefig(
save_path, dpi=250, facecolor=fig.get_facecolor(), transparent=True
)
return fig
+134 -2
View File
@@ -1,11 +1,11 @@
from contextlib import ExitStack as DoesNotRaise
from typing import List, Tuple
from typing import List, Tuple, Dict
import pytest
from supervision import Detections
from supervision.dataset.formats.coco import classes_to_coco_categories, coco_categories_to_classes, \
group_coco_annotations_by_image_id, coco_annotations_to_detections
group_coco_annotations_by_image_id, coco_annotations_to_detections, build_coco_class_index_mapping
import numpy as np
@@ -290,3 +290,135 @@ def test_coco_annotations_to_detections(
with_masks=with_masks
)
assert result == expected_result
@pytest.mark.parametrize(
"coco_categories, target_classes, expected_result, exception",
[
(
[],
[],
{},
DoesNotRaise()
), # empty coco categories
(
[
{
"id": 0,
"name": "fashion-assistant",
"supercategory": "none"
}
],
[
"fashion-assistant"
],
{
0: 0
},
DoesNotRaise()
), # single coco category starting from 0
(
[
{
"id": 1,
"name": "fashion-assistant",
"supercategory": "none"
}
],
[
"fashion-assistant"
],
{
1: 0
},
DoesNotRaise()
), # single coco category starting from 1
(
[
{
"id": 0,
"name": "fashion-assistant",
"supercategory": "none"
},
{
"id": 2,
"name": "hoodie",
"supercategory": "fashion-assistant"
},
{
"id": 1,
"name": "baseball cap",
"supercategory": "fashion-assistant"
}
],
[
"fashion-assistant",
"baseball cap",
"hoodie"
],
{
0: 0,
1: 1,
2: 2
},
DoesNotRaise()
), # three coco categories
(
[
{
"id": 2,
"name": "hoodie",
"supercategory": "fashion-assistant"
},
{
"id": 1,
"name": "baseball cap",
"supercategory": "fashion-assistant"
}
],
[
"baseball cap",
"hoodie"
],
{
2: 1,
1: 0
},
DoesNotRaise()
), # two coco categories
(
[
{
"id": 3,
"name": "hoodie",
"supercategory": "fashion-assistant"
},
{
"id": 1,
"name": "baseball cap",
"supercategory": "fashion-assistant"
}
],
[
"baseball cap",
"hoodie"
],
{
3: 1,
1: 0
},
DoesNotRaise()
), # two coco categories with missing category
]
)
def test_build_coco_class_index_mapping(
coco_categories: List[dict],
target_classes: List[str],
expected_result: Dict[int, int],
exception: Exception
) -> None:
with exception:
result = build_coco_class_index_mapping(
coco_categories=coco_categories,
target_classes=target_classes
)
assert result == expected_result
-1
View File
@@ -172,5 +172,4 @@ def test_dataset_merge(
) -> None:
with exception:
result = DetectionDataset.merge(dataset_list=dataset_list)
print(result.images.keys())
assert result == expected_result
+6
View File
@@ -204,6 +204,12 @@ def test_build_class_index_mapping(
mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[1]),
DoesNotRaise()
), # single mapping
(
{0: 1, 1: 2},
Detections.empty(),
Detections.empty(),
DoesNotRaise()
), # empty detections
(
{0: 1, 1: 2},
mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[0]),
View File
+402
View File
@@ -0,0 +1,402 @@
from contextlib import ExitStack as DoesNotRaise
from typing import Optional, Union
import numpy as np
import pytest
from supervision.detection.core import Detections
from supervision.metrics.detection import ConfusionMatrix
from test.utils import mock_detections
CLASSES = np.arange(80)
NUM_CLASSES = len(CLASSES)
PREDICTIONS = np.array(
[
[2254, 906, 2447, 1353, 0.90538, 0],
[2049, 1133, 2226, 1371, 0.59002, 56],
[727, 1224, 838, 1601, 0.51119, 39],
[808, 1214, 910, 1564, 0.45287, 39],
[6, 52, 1131, 2133, 0.45057, 72],
[299, 1225, 512, 1663, 0.45029, 39],
[529, 874, 645, 945, 0.31101, 39],
[8, 47, 1935, 2135, 0.28192, 72],
[2265, 813, 2328, 901, 0.2714, 62],
],
dtype=np.float32,
)
TARGET_TENSORS = [
np.array(
[
[2254, 906, 2447, 1353, 0],
[2049, 1133, 2226, 1371, 56],
[727, 1224, 838, 1601, 39],
[808, 1214, 910, 1564, 39],
[6, 52, 1131, 2133, 72],
[299, 1225, 512, 1663, 39],
[529, 874, 645, 945, 39],
[8, 47, 1935, 2135, 72],
[2265, 813, 2328, 901, 62],
]
)
]
DETECTIONS = Detections(
xyxy=PREDICTIONS[:, :4],
confidence=PREDICTIONS[:, 4],
class_id=PREDICTIONS[:, 5].astype(int),
)
CERTAIN_DETECTIONS = Detections(
xyxy=PREDICTIONS[:, :4],
confidence=np.ones(len(PREDICTIONS)),
class_id=PREDICTIONS[:, 5].astype(int),
)
DETECTION_TENSORS = [
np.concatenate(
[
det.xyxy,
np.expand_dims(det.class_id, 1),
np.expand_dims(det.confidence, 1),
],
axis=1,
)
for det in [DETECTIONS]
]
CERTAIN_DETECTION_TENSORS = [
np.concatenate(
[
det.xyxy,
np.expand_dims(det.class_id, 1),
np.ones((len(det), 1)),
],
axis=1,
)
for det in [DETECTIONS]
]
IDEAL_MATCHES = np.stack(
[
np.arange(len(PREDICTIONS)),
np.arange(len(PREDICTIONS)),
np.ones(len(PREDICTIONS)),
],
axis=1,
)
def create_empty_conf_matrix(num_classes: int, do_add_dummy_class: bool = True):
if do_add_dummy_class:
num_classes += 1
return np.zeros((num_classes, num_classes))
def update_ideal_conf_matrix(conf_matrix: np.ndarray, class_ids: np.ndarray):
for class_id, count in zip(*np.unique(class_ids, return_counts=True)):
class_id = int(class_id)
conf_matrix[class_id, class_id] += count
return conf_matrix
def worsen_ideal_conf_matrix(
conf_matrix: np.ndarray, class_ids: Union[np.ndarray, list]
):
for class_id in class_ids:
class_id = int(class_id)
conf_matrix[class_id, class_id] -= 1
conf_matrix[class_id, 80] += 1
return conf_matrix
IDEAL_CONF_MATRIX = create_empty_conf_matrix(NUM_CLASSES)
IDEAL_CONF_MATRIX = update_ideal_conf_matrix(IDEAL_CONF_MATRIX, PREDICTIONS[:, 5])
GOOD_CONF_MATRIX = worsen_ideal_conf_matrix(IDEAL_CONF_MATRIX.copy(), [62, 72])
BAD_CONF_MATRIX = worsen_ideal_conf_matrix(
IDEAL_CONF_MATRIX.copy(), [62, 72, 72, 39, 39, 39, 39, 56]
)
@pytest.mark.parametrize(
"detections, with_confidence, expected_result, exception",
[
(
Detections.empty(),
False,
np.empty((0, 5), dtype=np.float32),
DoesNotRaise(),
), # empty detections; no confidence
(
Detections.empty(),
True,
np.empty((0, 6), dtype=np.float32),
DoesNotRaise(),
), # empty detections; with confidence
(
mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[0], confidence=[0.5]),
False,
np.array([[0, 0, 10, 10, 0]], dtype=np.float32),
DoesNotRaise(),
), # single detection; no confidence
(
mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[0], confidence=[0.5]),
True,
np.array([[0, 0, 10, 10, 0, 0.5]], dtype=np.float32),
DoesNotRaise(),
), # single detection; with confidence
(
mock_detections(xyxy=[[0, 0, 10, 10], [0, 0, 20, 20]], class_id=[0, 1], confidence=[0.5, 0.2]),
False,
np.array([[0, 0, 10, 10, 0], [0, 0, 20, 20, 1]], dtype=np.float32),
DoesNotRaise(),
), # multiple detections; no confidence
(
mock_detections(xyxy=[[0, 0, 10, 10], [0, 0, 20, 20]], class_id=[0, 1], confidence=[0.5, 0.2]),
True,
np.array([[0, 0, 10, 10, 0, 0.5], [0, 0, 20, 20, 1, 0.2]], dtype=np.float32),
DoesNotRaise(),
), # multiple detections; with confidence
],
)
def test_detections_to_tensor(
detections: Detections,
with_confidence: bool,
expected_result: Optional[np.ndarray],
exception: Exception
):
with exception:
result = ConfusionMatrix.detections_to_tensor(
detections=detections,
with_confidence=with_confidence
)
assert np.array_equal(result, expected_result)
@pytest.mark.parametrize(
"predictions, targets, classes, conf_threshold, iou_threshold, expected_result, exception",
[
(
DETECTION_TENSORS,
TARGET_TENSORS,
CLASSES,
0.2,
0.5,
IDEAL_CONF_MATRIX,
DoesNotRaise(),
),
(
[],
[],
CLASSES,
0.2,
0.5,
create_empty_conf_matrix(NUM_CLASSES),
DoesNotRaise(),
),
(
DETECTION_TENSORS,
TARGET_TENSORS,
CLASSES,
0.3,
0.5,
GOOD_CONF_MATRIX,
DoesNotRaise(),
),
(
DETECTION_TENSORS,
TARGET_TENSORS,
CLASSES,
0.6,
0.5,
BAD_CONF_MATRIX,
DoesNotRaise(),
),
(
[
np.array(
[
[0.0, 0.0, 3.0, 3.0, 0, 0.9], # correct detection of [0]
[
0.1,
0.1,
3.0,
3.0,
0,
0.9,
], # additional detection of [0] - FP
[
6.0,
1.0,
8.0,
3.0,
1,
0.8,
], # correct detection with incorrect class
[1.0, 6.0, 2.0, 7.0, 1, 0.8], # incorrect detection - FP
[
1.0,
2.0,
2.0,
4.0,
1,
0.8,
], # incorrect detection with low IoU - FP
]
)
],
[
np.array(
[
[0.0, 0.0, 3.0, 3.0, 0], # [0] detected
[2.0, 2.0, 5.0, 5.0, 1], # [1] undetected - FN
[
6.0,
1.0,
8.0,
3.0,
2,
], # [2] correct detection with incorrect class
]
)
],
CLASSES[:3],
0.6,
0.5,
np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0], [1, 2, 0, 0]]),
DoesNotRaise(),
),
(
[
np.array(
[
[0.0, 0.0, 3.0, 3.0, 0, 0.9], # correct detection of [0]
[
0.1,
0.1,
3.0,
3.0,
0,
0.9,
], # additional detection of [0] - FP
[
6.0,
1.0,
8.0,
3.0,
1,
0.8,
], # correct detection with incorrect class
[1.0, 6.0, 2.0, 7.0, 1, 0.8], # incorrect detection - FP
[
1.0,
2.0,
2.0,
4.0,
1,
0.8,
], # incorrect detection with low IoU - FP
]
)
],
[
np.array(
[
[0.0, 0.0, 3.0, 3.0, 0], # [0] detected
[2.0, 2.0, 5.0, 5.0, 1], # [1] undetected - FN
[
6.0,
1.0,
8.0,
3.0,
2,
], # [2] correct detection with incorrect class
]
)
],
CLASSES[:3],
0.6,
1.0,
np.array([[0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1], [2, 3, 0, 0]]),
DoesNotRaise(),
),
],
)
def test_from_tensors(
predictions,
targets,
classes,
conf_threshold,
iou_threshold,
expected_result: Optional[np.ndarray],
exception: Exception,
):
with exception:
result = ConfusionMatrix.from_tensors(
predictions=predictions,
targets=targets,
classes=classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
assert result.matrix.diagonal().sum() == expected_result.diagonal().sum()
assert np.array_equal(result.matrix, expected_result)
@pytest.mark.parametrize(
"predictions, targets, num_classes, conf_threshold, iou_threshold, expected_result, exception",
[
(
DETECTION_TENSORS[0],
CERTAIN_DETECTION_TENSORS[0],
NUM_CLASSES,
0.2,
0.5,
IDEAL_CONF_MATRIX,
DoesNotRaise(),
)
],
)
def test_evaluate_detection_batch(
predictions,
targets,
num_classes,
conf_threshold,
iou_threshold,
expected_result: Optional[np.ndarray],
exception: Exception,
):
with exception:
result = ConfusionMatrix.evaluate_detection_batch(
predictions=predictions,
targets=targets,
num_classes=num_classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
assert result.diagonal().sum() == result.sum()
assert np.array_equal(result, expected_result)
@pytest.mark.parametrize(
"matches, expected_result, exception",
[
(
IDEAL_MATCHES,
IDEAL_MATCHES,
DoesNotRaise(),
)
],
)
def test_drop_extra_matches(
matches,
expected_result: Optional[np.ndarray],
exception: Exception,
):
with exception:
result = ConfusionMatrix._drop_extra_matches(matches)
assert np.array_equal(result, expected_result)
+8 -4
View File
@@ -2,18 +2,22 @@ from typing import List
import numpy as np
from supervision import Detections
from supervision.detection.core import Detections
def mock_detections(
xyxy: List[List[float]],
confidence: List[float] = None,
class_id: List[int] = None,
tracker_id: List[int] = None
tracker_id: List[int] = None,
) -> Detections:
return Detections(
xyxy=np.array(xyxy, dtype=np.float32),
confidence=confidence if confidence is None else np.array(confidence, dtype=np.float32),
confidence=confidence
if confidence is None
else np.array(confidence, dtype=np.float32),
class_id=class_id if class_id is None else np.array(class_id, dtype=int),
tracker_id=tracker_id if tracker_id is None else np.array(tracker_id, dtype=int)
tracker_id=tracker_id
if tracker_id is None
else np.array(tracker_id, dtype=int),
)