Compare commits

...
63 Commits
Author SHA1 Message Date
Linas Kondrackis 93c1b947fc Merge pull request #1351 from roboflow/develop
`supervision-0.22.0` release
2024-07-12 19:59:08 +03:00
SkalskiP 66d1f4c67d bump version from 0.22.0rc1 to 0.22.0 2024-07-12 18:53:53 +02:00
Linas Kondrackis 70a13fbf1e Merge pull request #1342 from roboflow/release/supervision-0.22.0-deprecations
Deprecations, changelog, notebook cleanup, 'new' markers
2024-07-12 19:49:17 +03:00
SkalskiP 1db8e12e1b small changes 2024-07-12 18:47:41 +02:00
LinasKo 8853321058 Release process: push to 'main', not 'master' 2024-07-12 19:10:25 +03:00
LinasKo 0a5e830159 Add release process doc 2024-07-12 18:59:06 +03:00
pre-commit-ci[bot] fb041f9b6c fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-12 15:20:00 +00:00
LinasKo 28f83b18f9 Deprecate BoundingBoxAnnotator, rename to BoxAnnotator 2024-07-12 18:18:50 +03:00
LinasKo feb779bae9 Extended code examples in changelog 2024-07-12 15:48:07 +03:00
LinasKo 521dc57b76 Brief early mention that Dataset.images is deprecated 2024-07-12 15:18:20 +03:00
LinasKo a0775544df Fix typo: future deprecation will be removed, not 'were' removed 2024-07-12 15:11:17 +03:00
LinasKo 14a0e299a1 Changelog, reorder deprecations 2024-07-12 15:08:11 +03:00
LinasKo 1e783b623b Wording: <TTF_FONT_PATH> 2024-07-12 15:05:09 +03:00
LinasKo c7a8654390 deprections text: five subsequent release 2024-07-12 15:03:24 +03:00
Piotr Skalski a7f8aee2c7 Merge pull request #1332 from roboflow/feat/cheatsheet-links
Add cheatsheet links in supervision docs, readme
2024-07-12 13:25:15 +02:00
Piotr Skalski 5ed03491f9 Merge pull request #1327 from roboflow/fix/florence2-region-xyxy
Fix bug where florence2 returns normalized xyxy regions
2024-07-12 12:17:18 +02:00
LinasKo 5d979df2f9 Add CropAnnotator PR link 2024-07-12 01:19:46 +03:00
LinasKo 5006f8c2f7 Add heading to deprecations page 2024-07-12 00:19:46 +03:00
LinasKo 8570ce68e2 'new' marker on Detections/Utils 2024-07-12 00:18:21 +03:00
LinasKo 3d3bbb4095 Add recently removed deprecations 2024-07-12 00:08:12 +03:00
LinasKo a1e3543897 Redo demo.ipynb, quickstart.ipynb images 2024-07-12 00:04:48 +03:00
LinasKo 4b82679b00 Add to changelog: KeyPoints.from_detectron2, xcycwh_to_xyxy 2024-07-11 23:17:18 +03:00
Linas Kondrackis 5d4abe6e0d Merge pull request #1310 from onuralpszr/detectron2-keypoint
feat: 🚀 Keypoint support for detectron2
2024-07-11 14:58:21 +03:00
Piotr Skalski 0405c74cf3 Merge pull request #1340 from roboflow/feature/box_utils
`xywh_to_xyxy` and `xcycwh_to_xyxy` box utils
2024-07-11 11:20:19 +02:00
Onuralp SEZER 13fe6e0942 fix: 🐞 add check if keypoints size is 0 return empty keypoint result
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-07-11 04:41:29 +03:00
Onuralp SEZER 03c92f7652 Merge pull request #1341 from roboflow/dependabot/pip/jupytext-1.16.3
⬆️ Bump jupytext from 1.16.2 to 1.16.3
2024-07-11 04:14:37 +03:00
LinasKo 084926ded3 Deprecations, changelog, notebook cleanup, 'new' markers 2024-07-11 03:21:41 +03:00
dependabot[bot] 382ed8e609 ⬆️ Bump jupytext from 1.16.2 to 1.16.3
Bumps [jupytext](https://github.com/mwouts/jupytext) from 1.16.2 to 1.16.3.
- [Release notes](https://github.com/mwouts/jupytext/releases)
- [Changelog](https://github.com/mwouts/jupytext/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mwouts/jupytext/compare/v1.16.2...v1.16.3)

---
updated-dependencies:
- dependency-name: jupytext
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-11 00:17:30 +00:00
pre-commit-ci[bot] 2ed2a793cd fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-10 10:15:01 +00:00
SkalskiP e0fb73f18a xywh_to_xyxy and xcycwh_to_xyxy box utils 2024-07-10 12:13:07 +02:00
Piotr Skalski de896189b8 Merge pull request #1326 from roboflow/feat/dataset-lazy-loading
Lazy Dataset Loading
2024-07-10 11:52:57 +02:00
Onuralp SEZER fbfd078795 Merge pull request #1336 from cdragos/docs/revise-contributing-guidelines
docs: revise and restructure CONTRIBUTING.md
2024-07-09 21:34:43 +03:00
Dragos Catarahia e31aad242a docs: revise and restructure CONTRIBUTING.md 2024-07-10 01:22:23 +09:00
LinasKo 93c5c39f8f as_pascal_voc now loads image paths 2024-07-09 15:30:19 +03:00
LinasKo 810148550b Bugfix: XML parse in object_to_pascal_voc 2024-07-09 15:23:48 +03:00
LinasKo 172877a3cf Fix accidental changes, rearrange utils, rename methods 2024-07-09 14:56:30 +03:00
pre-commit-ci[bot] a91fc1e4f3 fix(pre_commit): 🎨 auto format pre-commit hooks 2024-07-09 11:31:00 +00:00
LinasKo f4fd595783 Fix image poaths in save_dataset, copy images if lazy 2024-07-09 14:30:07 +03:00
LinasKo e690b92688 Move back save_images to utils 2024-07-09 14:17:04 +03:00
LinasKo c8f771ec69 Move coco and yolo loaders back into utils 2024-07-09 14:04:11 +03:00
LinasKo 6b09e1d67c ClassificationDataset, moved coco save function into dataset 2024-07-09 11:57:42 +03:00
LinasKo 12195705f5 LazyLoaded DetectionDataset 2024-07-09 02:13:56 +03:00
Onuralp SEZER 9f7871b4be Merge pull request #1334 from roboflow/pre-commit-ci-update-config
chore(pre_commit): ⬆ pre_commit autoupdate
2024-07-08 22:26:54 +03:00
pre-commit-ci[bot] 44890dc712 chore(pre_commit): ⬆ pre_commit autoupdate
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1)
2024-07-08 17:39:23 +00:00
LinasKo 57ae7467b9 Add cheatsheet links in supervision docs, readme 2024-07-08 13:57:12 +03:00
Iuri de Silvio 1e5e25c54a Merge pull request #1331 from roboflow/dependabot/pip/ruff-0.5.1
⬆️ Bump ruff from 0.5.0 to 0.5.1
2024-07-08 10:17:18 +02:00
dependabot[bot] ba7c4f4404 ⬆️ Bump ruff from 0.5.0 to 0.5.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.0...0.5.1)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 01:13:42 +00:00
Onuralp SEZER f3b52c08a7 Merge pull request #1329 from roboflow/dependabot/pip/certifi-2024.7.4
⬆️ Bump certifi from 2024.6.2 to 2024.7.4
2024-07-06 11:39:25 +03:00
dependabot[bot] 1a36842407 ⬆️ Bump certifi from 2024.6.2 to 2024.7.4
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-06 02:32:58 +00:00
LinasKo a8b12914c8 Fix florence 2 tests 2024-07-05 13:21:59 +03:00
LinasKo d3db15fcd0 Fix bug where florence2 returns normalized xyxy regions 2024-07-05 13:14:27 +03:00
Linas Kondrackis c40734fb8d Merge pull request #1316 from David-rn/feat/facemesh_from_mediapipe
Facemesh from mediapipe
2024-07-05 12:56:50 +03:00
LinasKo 55a9cef270 Minor edit: add Python label to dosctring example 2024-07-05 12:41:02 +03:00
LinasKo 4b9b70fbb2 LazyDataset Loading - accept List[str] of images
* Unfinished
* Untested
* Committing to show Piotr a bit earlier
2024-07-04 14:21:45 +03:00
Onuralp SEZER e61a1074ac Merge pull request #1324 from roboflow/dependabot/pip/tox-4.16.0
⬆️ Bump tox from 4.15.1 to 4.16.0
2024-07-04 13:44:27 +03:00
dependabot[bot] e6c7f970f0 ⬆️ Bump tox from 4.15.1 to 4.16.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.15.1 to 4.16.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.15.1...4.16.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-04 01:08:33 +00:00
David Redo a023259445 Updated from_mediapipe docstring with FaceLandmaker 2024-07-01 19:30:17 +02:00
David Redo b164ef1111 Added check for FaceMesh legacy object when no faces are detected 2024-06-28 15:30:07 +02:00
David Redo 74d19dd069 Added FaceMesh support in from_mediapipe KeyPoints fuction 2024-06-27 17:21:30 +02:00
Onuralp SEZER 185ae462a4 fix: 🐞 confidence shape size fix for validation
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 16:09:10 +03:00
Onuralp SEZER 3676f07168 fix: 🐞 doc and validation typo fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 15:51:32 +03:00
Onuralp SEZER 099258baa6 feat: ✨ check pred_keypoints is exist if not return empty
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 15:44:32 +03:00
Onuralp SEZER 0bb3839d29 feat: ✨ Keypoints for detectron2 added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2024-06-26 15:38:31 +03:00
52 changed files with 5653 additions and 2229 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0
rev: v0.5.1
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+122 -35
View File
@@ -4,6 +4,25 @@ Thank you for your interest in contributing to Supervision!
We are actively improving this library to reduce the amount of work you need to do to solve common computer vision problems.
## Code of Conduct
Please read and adhere to our [Code of Conduct](CODE_OF_CONDUCT.md). This document outlines the expected behavior for all participants in our project.
## Table of Contents
- [Contribution Guidelines](#contribution-guidelines)
- [Contributing Features](#contributing-features-)
- [How to Contribute Changes](#how-to-contribute-changes)
- [Installation for Contributors](#installation-for-contributors)
- [Code Style and Quality](#-code-style-and-quality)
- [Pre-commit tool](#pre-commit-tool)
- [Docstrings](#docstrings)
- [Type checking](#type-checking)
- [Documentation](#-documentation)
- [Cookbooks](#-cookbooks)
- [Tests](#-tests)
- [License](#-license)
## Contribution Guidelines
We welcome contributions to:
@@ -32,49 +51,49 @@ First, fork this repository to your own GitHub account. Click "fork" in the top
Then, run `git clone` to download the project code to your computer.
You should also set up `roboflow/supervision` as an "upstream" remote (that is, tell git that the reference Supervision repository was the source of your fork of it):
```bash
git remote add upstream https://github.com/roboflow/supervision.git
git fetch upstream
```
Move to a new branch using the `git checkout` command:
```bash
git checkout -b <your_branch_name>
git checkout -b <scope>/<your_branch_name> upstream/develop
```
The name you choose for your branch should describe the change you want to make (i.e. `line-counter-docs`).
The name you choose for your branch should describe the change you want to make and start with an appropriate prefix:
- `feat/`: for new features (e.g., `feat/line-counter`)
- `fix/`: for bug fixes (e.g., `fix/memory-leak`)
- `docs/`: for documentation changes (e.g., `docs/update-readme`)
- `chore/`: for routine tasks, maintenance, or tooling changes (e.g., `chore/update-dependencies`)
- `test/`: for adding or modifying tests (e.g., `test/add-unit-tests`)
- `refactor/`: for code refactoring (e.g., `refactor/simplify-algorithm`)
Make any changes you want to the project code, then run the following commands to commit your changes:
```bash
git add .
git commit -m "Your commit message"
git push -u origin main
git add -A
git commit -m "feat: add line counter functionality"
git push -u origin <your_branch_name>
```
## 🎨 Code quality
Use conventional commit messages to clearly describe your changes. The format is:
### Pre-commit tool
<type>[optional scope]: <description>
This project uses the [pre-commit](https://pre-commit.com/) tool to maintain code quality and consistency. Before submitting a pull request or making any commits, it is important to run the pre-commit tool to ensure that your changes meet the project's guidelines.
Furthermore, we have integrated a pre-commit GitHub Action into our workflow. This means that with every pull request opened, the pre-commit checks will be automatically enforced, streamlining the code review process and ensuring that all contributions adhere to our quality standards.
To run the pre-commit tool, follow these steps:
1. Install pre-commit by running the following command: `poetry install`. It will not only install pre-commit but also install all the deps and dev-deps of project
2. Once pre-commit is installed, navigate to the project's root directory.
3. Run the command `pre-commit run --all-files`. This will execute the pre-commit hooks configured for this project against the modified files. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the pre-commit command until all issues are resolved.
4. You can also install pre-commit as a git hook by executing `pre-commit install`. Every time you do a `git commit` pre-commit run automatically for you.
### Docstrings
All new functions and classes in `supervision` should include docstrings. This is a prerequisite for any new functions and classes to be added to the library.
`supervision` adheres to the [Google Python docstring style](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods). Please refer to the style guide while writing docstrings for your contribution.
### Type checking
So far, **there is no type checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4).
Common types include:
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- chore: Changes to the build process or auxiliary tools and libraries
Then, go back to your fork of the `supervision` repository, click "Pull Requests", and click "New Pull Request".
@@ -104,7 +123,75 @@ All pull requests will be reviewed by the maintainers of the project. We will pr
PRs must pass all tests and linting requirements before they can be merged.
## 📝 documentation
## Installation for Contributors
Before starting your work on the project, set up your development environment:
1. Clone your fork of the project:
```bash
git clone https://github.com/YOUR_USERNAME/supervision.git
cd supervision
```
Replace `YOUR_USERNAME` with your GitHub username.
2. Create and activate a virtual environment:
```bash
python3 -m venv .venv
source .venv/bin/activate
```
3. Install Poetry:
Using pip:
```bash
pip install -U pip setuptools
pip install poetry
```
Or using pipx (recommended for global installation):
```bash
pipx install poetry
```
4. Install project dependencies:
```bash
poetry install
```
5. Run pytest to verify the setup:
```bash
poetry run pytest
```
## 🎨 Code Style and Quality
### Pre-commit tool
This project uses the [pre-commit](https://pre-commit.com/) tool to maintain code quality and consistency. Before submitting a pull request or making any commits, it is important to run the pre-commit tool to ensure that your changes meet the project's guidelines.
Furthermore, we have integrated a pre-commit GitHub Action into our workflow. This means that with every pull request opened, the pre-commit checks will be automatically enforced, streamlining the code review process and ensuring that all contributions adhere to our quality standards.
To run the pre-commit tool, follow these steps:
1. Install pre-commit by running the following command: `poetry install`. It will not only install pre-commit but also install all the deps and dev-deps of project
2. Once pre-commit is installed, navigate to the project's root directory.
3. Run the command `pre-commit run --all-files`. This will execute the pre-commit hooks configured for this project against the modified files. If any issues are found, the pre-commit tool will provide feedback on how to resolve them. Make the necessary changes and re-run the pre-commit command until all issues are resolved.
4. You can also install pre-commit as a git hook by executing `pre-commit install`. Every time you do a `git commit` pre-commit run automatically for you.
### Docstrings
All new functions and classes in `supervision` should include docstrings. This is a prerequisite for any new functions and classes to be added to the library.
`supervision` adheres to the [Google Python docstring style](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods). Please refer to the style guide while writing docstrings for your contribution.
### Type checking
So far, **there is no type checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4).
## 📝 Documentation
The `supervision` documentation is stored in a folder called `docs`. The project documentation is built using `mkdocs`.
@@ -112,13 +199,13 @@ To run the documentation, install the project requirements with `poetry install
You can learn more about mkdocs on the [mkdocs website](https://www.mkdocs.org/).
## 🧑‍🍳 cookbooks
## 🧑‍🍳 Cookbooks
We are always looking for new examples and cookbooks to add to the `supervision`
documentation. If you have a use case that you think would be helpful to others, please
submit a PR with your example. Here are some guidelines for submitting a new example:
- Create a new notebook in the [`docs/nodebooks`](https://github.com/roboflow/supervision/tree/develop/docs/notebooks) folder.
- Create a new notebook in the [`docs/notebooks`](https://github.com/roboflow/supervision/tree/develop/docs/notebooks) folder.
- Add a link to the new notebook in [`docs/theme/cookbooks.html`](https://github.com/roboflow/supervision/blob/develop/docs/theme/cookbooks.html). Make sure to add the path to the new notebook, as well as a title, labels, author and supervision version.
- Use the [Count Objects Crossing the Line](https://supervision.roboflow.com/develop/notebooks/count-objects-crossing-the-line/) example as a template for your new example.
- Freeze the version of `supervision` you are using.
@@ -126,10 +213,10 @@ submit a PR with your example. Here are some guidelines for submitting a new exa
- Notebook should be self-contained. If you rely on external data ( videos, images, etc.) or libraries, include download and installation commands in the notebook.
- Annotate the code with appropriate comments, including links to the documentation describing each of the tools you have used.
## 🧪 tests
## 🧪 Tests
[`pytests`](https://docs.pytest.org/en/7.1.x/) is used to run our tests.
## 📄 license
## 📄 License
By contributing, you agree that your contributions will be licensed under an [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md).
+3 -3
View File
@@ -95,8 +95,8 @@ import supervision as sv
image = cv2.imread(...)
detections = sv.Detections(...)
bounding_box_annotator = sv.BoundingBoxAnnotator()
annotated_frame = bounding_box_annotator.annotate(
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections
)
@@ -216,7 +216,7 @@ len(dataset)
## 🎬 tutorials
Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](https://github.com/roboflow/supervision/tree/develop/examples), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!
Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](https://github.com/roboflow/supervision/tree/develop/examples), [cheatsheet](https://roboflow.github.io/cheatsheet-supervision/), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!
<br/>
Vendored
+1478 -1342
View File
File diff suppressed because one or more lines are too long
+153 -9
View File
@@ -1,8 +1,157 @@
### 0.22.0 <small>Jul 12, 2024</small>
- Added [#1326](https://github.com/roboflow/supervision/pull/1326): [`sv.DetectionsDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset) and [`sv.ClassificationDataset`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.ClassificationDataset) allowing to load the images into memory only when necessary (lazy loading).
!!! failure "Deprecated"
Constructing `DetectionDataset` with parameter `images` as `Dict[str, np.ndarray]` is deprecated and will be removed in `supervision-0.26.0`. Please pass a list of paths `List[str]` instead.
!!! failure "Deprecated"
The `DetectionDataset.images` property is deprecated and will be removed in `supervision-0.26.0`. Please loop over images with `for path, image, annotation in dataset:`, as that does not require loading all images into memory.
```python
import roboflow
from roboflow import Roboflow
import supervision as sv
roboflow.login()
rf = Roboflow()
project = rf.workspace(<WORKSPACE_ID>).project(<PROJECT_ID>)
dataset = project.version(<PROJECT_VERSION>).download("coco")
ds_train = sv.DetectionDataset.from_coco(
images_directory_path=f"{dataset.location}/train",
annotations_path=f"{dataset.location}/train/_annotations.coco.json",
)
path, image, annotation = ds_train[0]
# loads image on demand
for path, image, annotation in ds_train:
# loads image on demand
```
- Added [#1296](https://github.com/roboflow/supervision/pull/1296): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_lmm) now supports parsing results from the [Florence 2](https://huggingface.co/microsoft/Florence-2-large) model, extending the capability to handle outputs from this Large Multimodal Model (LMM). This includes detailed object detection, OCR with region proposals, segmentation, and more. Find out more in our [Colab notebook](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-florence-2-on-detection-dataset.ipynb).
- Added [#1232](https://github.com/roboflow/supervision/pull/1232) to support keypoint detection with Mediapipe. Both [legacy](https://colab.research.google.com/github/googlesamples/mediapipe/blob/main/examples/pose_landmarker/python/%5BMediaPipe_Python_Tasks%5D_Pose_Landmarker.ipynb) and [modern](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker/python) pipelines are supported. See [`sv.KeyPoints.from_mediapipe`](https://supervision.roboflow.com/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe) for more.
- Added [#1316](https://github.com/roboflow/supervision/pull/1316): [`sv.KeyPoints.from_mediapipe`](https://supervision.roboflow.com/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_mediapipe) extended to support FaceMesh from Mediapipe. This enhancement allows for processing both face landmarks from `FaceLandmarker`, and legacy results from `FaceMesh`.
- Added [#1310](https://github.com/roboflow/supervision/pull/1310): [`sv.KeyPoints.from_detectron2`](https://supervision.roboflow.com/latest/keypoint/core/#supervision.keypoint.core.KeyPoints.from_detectron2) is a new `KeyPoints` method, adding support for extracting keypoints from the popular [Detectron 2](https://github.com/facebookresearch/detectron2) platform.
- Added [#1300](https://github.com/roboflow/supervision/pull/1300): [`sv.Detections.from_detectron2`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_detectron2) now supports segmentation models detectron2. The resulting masks can be used with [`sv.MaskAnnotator`](https://supervision.roboflow.com/latest/annotators/#supervision.annotators.core.MaskAnnotator) for displaying annotations.
```python
import supervision as sv
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
import cv2
image = cv2.imread(<SOURCE_IMAGE_PATH>)
cfg = get_cfg()
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")
predictor = DefaultPredictor(cfg)
result = predictor(image)
detections = sv.Detections.from_detectron2(result)
mask_annotator = sv.MaskAnnotator()
annotated_frame = mask_annotator.annotate(scene=image.copy(), detections=detections)
```
- Added [#1277](https://github.com/roboflow/supervision/pull/1277): if you provide a font that supports symbols of a language, [`sv.RichLabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator.annotate) will draw them on your images.
- Various other annotators have been revised to ensure proper in-place functionality when used with `numpy` arrays. Additionally, we fixed a bug where `sv.ColorAnnotator` was filling boxes with solid color when used in-place.
```python
import cv2
import supervision as sv
import
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8n-640")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
rich_label_annotator = sv.RichLabelAnnotator(font_path=<TTF_FONT_PATH>)
annotated_image = rich_label_annotator.annotate(scene=image.copy(), detections=detections)
```
- Added [#1227](https://github.com/roboflow/supervision/pull/1227): Added support for loading Oriented Bounding Boxes dataset in YOLO format.
```python
import supervision as sv
train_ds = sv.DetectionDataset.from_yolo(
images_directory_path="/content/dataset/train/images",
annotations_directory_path="/content/dataset/train/labels",
data_yaml_path="/content/dataset/data.yaml",
is_obb=True
)
_, image, detections in train_ds[0]
obb_annotator = OrientedBoxAnnotator()
annotated_image = obb_annotator.annotate(scene=image.copy(), detections=detections)
```
- Fixed [#1312](https://github.com/roboflow/supervision/pull/1312): Fixed [`CropAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.TraceAnnotator.annotate).
!!! failure "Removed"
`BoxAnnotator` was removed, however `BoundingBoxAnnotator` has been renamed to `BoxAnnotator`. Use a combination of [`BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator) and [`LabelAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.LabelAnnotator) to simulate old `BoundingBox` behavior.
!!! failure "Deprecated"
The name `BoundingBoxAnnotator` has been deprecated and will be removed in `supervision-0.26.0`. It has been renamed to [`BoxAnnotator`](https://supervision.roboflow.com/latest/detection/annotators/#supervision.annotators.core.BoxAnnotator).
- Added [#975](https://github.com/roboflow/supervision/pull/975) 📝 New Cookbooks: serialize detections into [json](https://github.com/roboflow/supervision/blob/de896189b83a1f9434c0a37dd9192ee00d2a1283/docs/notebooks/serialise-detections-to-json.ipynb) and [csv](https://github.com/roboflow/supervision/blob/de896189b83a1f9434c0a37dd9192ee00d2a1283/docs/notebooks/serialise-detections-to-csv.ipynb).
- Added [#1290](https://github.com/roboflow/supervision/pull/1290): Mostly an internal change, our file utility function now support both `str` and `pathlib` paths.
- Added [#1340](https://github.com/roboflow/supervision/pull/1340): Two new methods for converting between bounding box formats - [`xywh_to_xyxy`](https://supervision.roboflow.com/latest/detection/utils/#supervision.detection.utils.xywh_to_xyxy) and [`xcycwh_to_xyxy`](https://supervision.roboflow.com/latest/detection/utils/#supervision.detection.utils.xcycwh_to_xyxy)
!!! failure "Removed"
`from_roboflow` method has been removed due to deprecation. Use [from_inference](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_inference) instead.
!!! failure "Removed"
`Color.white()` has been removed due to deprecation. Use `color.WHITE` instead.
!!! failure "Removed"
`Color.black()` has been removed due to deprecation. Use `color.BLACK` instead.
!!! failure "Removed"
`Color.red()` has been removed due to deprecation. Use `color.RED` instead.
!!! failure "Removed"
`Color.green()` has been removed due to deprecation. Use `color.GREEN` instead.
!!! failure "Removed"
`Color.blue()` has been removed due to deprecation. Use `color.BLUE` instead.
!!! failure "Removed"
`ColorPalette.default()` has been removed due to deprecation. Use [ColorPalette.DEFAULT](https://supervision.roboflow.com/latest/utils/draw/#supervision.draw.color.ColorPalette.DEFAULT) instead.
!!! failure "Removed"
`FPSMonitor.__call__` has been removed due to deprecation. Use the attribute [FPSMonitor.fps](https://supervision.roboflow.com/latest/utils/video/#supervision.utils.video.FPSMonitor.fps) instead.
### 0.21.0 <small>Jun 5, 2024</small>
- Added [#500](https://github.com/roboflow/supervision/pull/500): [`sv.Detections.with_nmm`](https://supervision.roboflow.com/develop/detection/core/#supervision.detection.core.Detections.with_nmm) to perform non-maximum merging on the current set of object detections.
- Added [#500](https://github.com/roboflow/supervision/pull/500): [`sv.Detections.with_nmm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.with_nmm) to perform non-maximum merging on the current set of object detections.
- Added [#1221](https://github.com/roboflow/supervision/pull/1221): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/develop/detection/core/#supervision.detection.core.Detections.from_lmm) allowing to parse Large Multimodal Model (LMM) text result into [`sv.Detections`](https://supervision.roboflow.com/develop/detection/core/) object. For now `from_lmm` supports only [PaliGemma](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-paligemma-on-detection-dataset.ipynb) result parsing.
- Added [#1221](https://github.com/roboflow/supervision/pull/1221): [`sv.Detections.from_lmm`](https://supervision.roboflow.com/latest/detection/core/#supervision.detection.core.Detections.from_lmm) allowing to parse Large Multimodal Model (LMM) text result into [`sv.Detections`](https://supervision.roboflow.com/latest/detection/core/) object. For now `from_lmm` supports only [PaliGemma](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-finetune-paligemma-on-detection-dataset.ipynb) result parsing.
```python
import supervision as sv
@@ -21,7 +170,7 @@ detections.class_id
# array([0])
```
- Added [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.VertexLabelAnnotator`](https://supervision.roboflow.com/develop/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator.annotate) allowing to annotate every vertex of a keypoint skeleton with custom text and color.
- Added [#1236](https://github.com/roboflow/supervision/pull/1236): [`sv.VertexLabelAnnotator`](https://supervision.roboflow.com/latest/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator.annotate) allowing to annotate every vertex of a keypoint skeleton with custom text and color.
```python
import supervision as sv
@@ -164,8 +313,6 @@ with csv_sink:
- Added [#819](https://github.com/roboflow/supervision/pull/819): [`sv.JSONSink`](/0.19.0/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink) allowing for the straightforward saving of image, video, or stream inference results in a `.json` file.
```python
```python
import supervision as sv
from ultralytics import YOLO
@@ -285,7 +432,6 @@ ColorPalette(colors=[Color(r=68, g=1, b=84), Color(r=59, g=82, b=139), ...])
`sv.ColorPalette.default()` is deprecated and will be removed in `supervision-0.22.0`. Use `sv.ColorPalette.DEFAULT` instead.
- Changed [#769](https://github.com/roboflow/supervision/pull/769): [`sv.ColorPalette.DEFAULT`](/0.18.0/draw/color/#colorpalette) value, giving users a more extensive set of annotation colors.
- Changed [#677](https://github.com/roboflow/supervision/pull/677): `sv.Detections.from_roboflow` to [`sv.Detections.from_inference`](/0.18.0/detection/core/#supervision.detection.core.Detections.from_inference) streamlining its functionality to be compatible with both the both [inference](https://github.com/roboflow/inference) pip package and the Robloflow [hosted API](https://docs.roboflow.com/deploy/hosted-api).
@@ -294,7 +440,6 @@ ColorPalette(colors=[Color(r=68, g=1, b=84), Color(r=59, g=82, b=139), ...])
`Detections.from_roboflow()` is deprecated and will be removed in `supervision-0.22.0`. Use `Detections.from_inference` instead.
- Fixed [#735](https://github.com/roboflow/supervision/pull/735): [`sv.LineZone`](/0.18.0/detection/tools/line_zone/#linezone) functionality to accurately update the counter when an object crosses a line from any direction, including from the side. This enhancement enables more precise tracking and analytics, such as calculating individual in/out counts for each lane on the road.
### 0.17.0 <small>December 06, 2023</small>
@@ -387,13 +532,12 @@ ColorPalette(colors=[Color(r=68, g=1, b=84), Color(r=59, g=82, b=139), ...])
- 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`](/0.16.0/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) to return `np.array([], dtype=int)` when `svDetections` is empty.
- Fixed [#430](https://github.com/roboflow/supervision/pull/430): [`sv.ByteTrack`](/0.16.0/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) to return `np.array([], dtype=int)` when `svDetections` is empty.
!!! failure "Deprecated"
`sv.Detections.from_yolov8` and `sv.Classifications.from_yolov8` as those are now replaced by [`sv.Detections.from_ultralytics`](/0.16.0/detection/core/#supervision.detection.core.Detections.from_ultralytics) and [`sv.Classifications.from_ultralytics`](/0.16.0/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`](/0.15.0/annotators/#supervision.annotators.core.BoundingBoxAnnotator) allowing to annotate images and videos with bounding boxes.
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Datasets Utils
+20 -12
View File
@@ -3,17 +3,25 @@ comments: true
status: deprecated
---
These features are phased out due to better alternatives or potential issues in future versions. Deprecated functionalities are supported for **three subsequent releases**, providing time for users to transition to updated methods.
# Deprecated
These features are phased out due to better alternatives or potential issues in future versions. Deprecated functionalities are supported for **five subsequent releases**, providing time for users to transition to updated methods.
- [`Detections.from_froboflow`](detection/core.md/#supervision.detection.core.Detections.from_roboflow) is deprecated and will be removed in `supervision-0.22.0`. Use [`Detections.from_inference`](detection/core.md/#supervision.detection.core.Detections.from_inference) instead.
- The method `Color.white()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.WHITE` instead.
- The method `Color.black()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.BLACK` instead.
- The method `Color.red()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.RED` instead.
- The method `Color.green()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.GREEN` instead.
- The method `Color.blue()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.BLUE` instead.
- The method [`ColorPalette.default()`](draw/color.md/#supervision.draw.color.ColorPalette.default) is deprecated and will be removed in `supervision-0.22.0`. Use the constant [`ColorPalette.DEFAULT`](draw/color.md/#supervision.draw.color.ColorPalette.DEFAULT) instead.
- `BoxAnnotator` is deprecated and will be removed in `supervision-0.22.0`. Use [`BoundingBoxAnnotator`](detection/annotators.md/#supervision.annotators.core.BoundingBoxAnnotator) and [`LabelAnnotator`](detection/annotators.md/#supervision.annotators.core.LabelAnnotator) instead.
- The method [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) is deprecated and will be removed in `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead.
- The `track_buffer`, `track_thresh`, and `match_thresh` parameters in [`ByterTrack`](trackers.md/#supervision.tracker.byte_tracker.core.ByteTrack) are deprecated and will be removed in `supervision-0.23.0`. Use `lost_track_buffer,` `track_activation_threshold`, and `minimum_matching_threshold` instead.
- The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.23.0`. Use `triggering_anchors ` instead.
- The `frame_resolution_wh ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.24.0`.
- The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) will be removed in `supervision-0.23.0`. Use `triggering_anchors ` instead.
- The `frame_resolution_wh ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) will be removed in `supervision-0.24.0`.
- Constructing `DetectionDataset` and `ClassificationDataset` with parameter `images` as `Dict[str, np.ndarray]` will be removed in `supervision-0.26.0`. Please pass a list of paths `List[str]` instead.
- The `DetectionDataset.images` property will be removed in `supervision-0.26.0`. Please loop over images with `for path, image, annotation in dataset:`, as that does not require loading all images into memory.
- `BoundingBoxAnnotator` has been renamed to `BoxAnnotator` after the old implementation of `BoxAnnotator` has been removed. `BoundingBoxAnnotator` will be removed in `supervision-0.26.0`.
# Removed
- [`Detections.from_froboflow`](detection/core.md/#supervision.detection.core.Detections.from_roboflow) is removed as of `supervision-0.22.0`. Use [`Detections.from_inference`](detection/core.md/#supervision.detection.core.Detections.from_inference) instead.
- The method `Color.white()` was removed as of `supervision-0.22.0`. Use the constant `Color.WHITE` instead.
- The method `Color.black()` was removed as of `supervision-0.22.0`. Use the constant `Color.BLACK` instead.
- The method `Color.red()` was removed as of `supervision-0.22.0`. Use the constant `Color.RED` instead.
- The method `Color.green()` was removed as of `supervision-0.22.0`. Use the constant `Color.GREEN` instead.
- The method `Color.blue()` was removed as of `supervision-0.22.0`. Use the constant `Color.BLUE` instead.
- The method [`ColorPalette.default()`](draw/color.md/#supervision.draw.color.ColorPalette.default) was removed as of `supervision-0.22.0`. Use the constant [`ColorPalette.DEFAULT`](draw/color.md/#supervision.draw.color.ColorPalette.DEFAULT) instead.
- `BoxAnnotator` was removed as of `supervision-0.22.0`, however `BoundingBoxAnnotator` was immediately renamed to `BoxAnnotator`. Use [`BoxAnnotator`](detection/annotators.md/#supervision.annotators.core.BoxAnnotator) and [`LabelAnnotator`](detection/annotators.md/#supervision.annotators.core.LabelAnnotator) instead of the old `BoxAnnotator`.
- The method [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) was removed as of `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead.
+7 -6
View File
@@ -1,10 +1,11 @@
---
comments: true
status: new
---
# Annotators
=== "BoundingBox"
=== "Box"
```python
import supervision as sv
@@ -12,8 +13,8 @@ comments: true
image = ...
detections = sv.Detections(...)
bounding_box_annotator = sv.BoundingBoxAnnotator()
annotated_frame = bounding_box_annotator.annotate(
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections
)
@@ -299,7 +300,7 @@ comments: true
]
rich_label_annotator = sv.RichLabelAnnotator(
font_path=".../font.ttf",
font_path="<TTF_FONT_PATH>",
text_position=sv.Position.CENTER
)
annotated_frame = label_annotator.annotate(
@@ -433,10 +434,10 @@ comments: true
</div>
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.BoundingBoxAnnotator">BoundingBoxAnnotator</a></h2>
<h2><a href="#supervision.annotators.core.BoxAnnotator">BoxAnnotator</a></h2>
</div>
:::supervision.annotators.core.BoundingBoxAnnotator
:::supervision.annotators.core.BoxAnnotator
<div class="md-typeset">
<h2><a href="#supervision.annotators.core.RoundBoxAnnotator">RoundBoxAnnotator</a></h2>
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Double Detection Filter
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# InferenceSlicer
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
<div class="md-typeset">
+13
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# Detection Utils
@@ -80,6 +81,18 @@ comments: true
<h2><a href="#supervision.detection.utils.contains_holes">contains_holes</a></h2>
</div>
:::supervision.detection.utils.xywh_to_xyxy
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.xywh_to_xyxy">xywh_to_xyxy</a></h2>
</div>
:::supervision.detection.utils.xcycwh_to_xyxy
<div class="md-typeset">
<h2><a href="#supervision.detection.utils.xcycwh_to_xyxy">xcycwh_to_xyxy</a></h2>
</div>
:::supervision.detection.utils.contains_holes
<div class="md-typeset">
+14 -14
View File
@@ -135,7 +135,7 @@ You can load predictions from other computer vision frameworks and libraries usi
## Annotate Image with Detections
Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoundingBoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoundingBoxAnnotator) and [`sv.LabelAnnotator`](/latest/annotators/#supervision.annotators.core.LabelAnnotator) classes.
Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoxAnnotator) and [`sv.LabelAnnotator`](/latest/annotators/#supervision.annotators.core.LabelAnnotator) classes.
=== "Inference"
@@ -149,10 +149,10 @@ Finally, we can annotate the image with the predictions. Since we are working wi
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -170,10 +170,10 @@ Finally, we can annotate the image with the predictions. Since we are working wi
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -204,10 +204,10 @@ Finally, we can annotate the image with the predictions. Since we are working wi
transformers_results=results,
id2label=model.config.id2label)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -233,7 +233,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -242,7 +242,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -260,7 +260,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -269,7 +269,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -300,7 +300,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
transformers_results=results,
id2label=model.config.id2label)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -309,7 +309,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
in zip(detections['class_name'], detections.confidence)
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -322,7 +322,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me
If you are running the segmentation model
[`sv.MaskAnnotator`](/latest/annotators/#supervision.annotators.core.MaskAnnotator)
is a drop-in replacement for
[`sv.BoundingBoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoundingBoxAnnotator)
[`sv.BoxAnnotator`](/latest/annotators/#supervision.annotators.core.BoxAnnotator)
that will allow you to draw masks instead of boxes.
=== "Inference"
+16 -17
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Detect Small Objects
@@ -32,10 +31,10 @@ size relative to the image resolution.
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -53,10 +52,10 @@ size relative to the image resolution.
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -85,7 +84,7 @@ size relative to the image resolution.
outputs=outputs, target_sizes=target_size)[0]
detections = sv.Detections.from_transformers(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -94,7 +93,7 @@ size relative to the image resolution.
in detections.class_id
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
@@ -120,10 +119,10 @@ is less effective for ultra-high-resolution images (4K and above).
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -141,10 +140,10 @@ is less effective for ultra-high-resolution images (4K and above).
results = model(image, imgsz=1280)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -180,10 +179,10 @@ objects within each, and aggregating the results.
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -207,10 +206,10 @@ objects within each, and aggregating the results.
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)
@@ -248,7 +247,7 @@ objects within each, and aggregating the results.
slicer = sv.InferenceSlicer(callback = callback)
detections = slicer(image)
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
@@ -257,7 +256,7 @@ objects within each, and aggregating the results.
in detections.class_id
]
annotated_image = bounding_box_annotator.annotate(
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
+9
View File
@@ -146,4 +146,13 @@ You can install `supervision` in a
Master the techniques to selectively filter and focus on objects within a specific zone
- **Cheatsheet**
***
Access a quick reference guide to the most common `supervision` functions
[:octicons-arrow-right-24: Cheatsheet](https://roboflow.github.io/cheatsheet-supervision/)
</div>
-1
View File
@@ -1,6 +1,5 @@
---
comments: true
status: new
---
# Annotators
+333 -193
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -1,5 +1,6 @@
---
comments: true
status: new
---
# File Utils
@@ -11,7 +11,7 @@ from tqdm import tqdm
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.DEFAULT
def load_zones_config(file_path: str) -> List[np.ndarray]:
@@ -9,7 +9,7 @@ from ultralytics import YOLO
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.DEFAULT
def load_zones_config(file_path: str) -> List[np.ndarray]:
@@ -102,7 +102,7 @@ if __name__ == "__main__":
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
box_annotator = sv.BoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
text_thickness=thickness,
@@ -154,7 +154,7 @@ if __name__ == "__main__":
annotated_frame = trace_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = label_annotator.annotate(
@@ -80,7 +80,7 @@ if __name__ == "__main__":
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
box_annotator = sv.BoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
text_thickness=thickness,
@@ -132,7 +132,7 @@ if __name__ == "__main__":
annotated_frame = trace_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = label_annotator.annotate(
@@ -81,7 +81,7 @@ if __name__ == "__main__":
resolution_wh=video_info.resolution_wh
)
text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh)
bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness)
box_annotator = sv.BoxAnnotator(thickness=thickness)
label_annotator = sv.LabelAnnotator(
text_scale=text_scale,
text_thickness=thickness,
@@ -132,7 +132,7 @@ if __name__ == "__main__":
annotated_frame = trace_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=annotated_frame, detections=detections
)
annotated_frame = label_annotator.annotate(
@@ -93,7 +93,7 @@ class VideoProcessor:
self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER])
self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER])
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
@@ -134,9 +134,7 @@ class VideoProcessor:
labels = [f"#{tracker_id}" for tracker_id in detections.tracker_id]
annotated_frame = self.trace_annotator.annotate(annotated_frame, detections)
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.box_annotator.annotate(annotated_frame, detections)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
@@ -90,7 +90,7 @@ class VideoProcessor:
self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER])
self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER])
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
@@ -131,9 +131,7 @@ class VideoProcessor:
labels = [f"#{tracker_id}" for tracker_id in detections.tracker_id]
annotated_frame = self.trace_annotator.annotate(annotated_frame, detections)
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.box_annotator.annotate(annotated_frame, detections)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
+1
View File
@@ -75,6 +75,7 @@ nav:
- Geometry: utils/geometry.md
- Assets: assets.md
- Cookbooks: cookbooks.md
- Cheatsheet: https://roboflow.github.io/cheatsheet-supervision/
- Contribute:
- Contributing: contributing.md
- Code of Conduct: code_of_conduct.md
Generated
+40 -40
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "anyio"
@@ -336,13 +336,13 @@ test = ["flake8", "isort", "pytest"]
[[package]]
name = "certifi"
version = "2024.6.2"
version = "2024.7.4"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"},
{file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"},
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
]
[[package]]
@@ -1710,13 +1710,13 @@ files = [
[[package]]
name = "jupytext"
version = "1.16.2"
version = "1.16.3"
description = "Jupyter notebooks as Markdown documents, Julia, Python or R scripts"
optional = false
python-versions = ">=3.8"
files = [
{file = "jupytext-1.16.2-py3-none-any.whl", hash = "sha256:197a43fef31dca612b68b311e01b8abd54441c7e637810b16b6cb8f2ab66065e"},
{file = "jupytext-1.16.2.tar.gz", hash = "sha256:8627dd9becbbebd79cc4a4ed4727d89d78e606b4b464eab72357b3b029023a14"},
{file = "jupytext-1.16.3-py3-none-any.whl", hash = "sha256:870e0d7a716dcb1303df6ad1cec65e3315a20daedd808a55cb3dae2d56e4ed20"},
{file = "jupytext-1.16.3.tar.gz", hash = "sha256:1ebac990461dd9f477ff7feec9e3003fa1acc89f3c16ba01b73f79fd76f01a98"},
]
[package.dependencies]
@@ -1728,11 +1728,11 @@ pyyaml = "*"
tomli = {version = "*", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"]
dev = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (>=1.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"]
docs = ["myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"]
test = ["pytest", "pytest-randomly", "pytest-xdist"]
test-cov = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist"]
test-external = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"]
test-external = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (>=1.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"]
test-functional = ["pytest", "pytest-randomly", "pytest-xdist"]
test-integration = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-randomly", "pytest-xdist"]
test-ui = ["calysto-bash"]
@@ -3714,29 +3714,29 @@ files = [
[[package]]
name = "ruff"
version = "0.5.0"
version = "0.5.1"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"},
{file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"},
{file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"},
{file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"},
{file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"},
{file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"},
{file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"},
{file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"},
{file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"},
{file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"},
{file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"},
{file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"},
{file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"},
{file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"},
{file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"},
{file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"},
]
[[package]]
@@ -4000,30 +4000,30 @@ files = [
[[package]]
name = "tox"
version = "4.15.1"
version = "4.16.0"
description = "tox is a generic virtualenv management and test command line tool"
optional = false
python-versions = ">=3.8"
files = [
{file = "tox-4.15.1-py3-none-any.whl", hash = "sha256:f00a5dc4222b358e69694e47e3da0227ac41253509bca9f45aa8f012053e8d9d"},
{file = "tox-4.15.1.tar.gz", hash = "sha256:53a092527d65e873e39213ebd4bd027a64623320b6b0326136384213f95b7076"},
{file = "tox-4.16.0-py3-none-any.whl", hash = "sha256:61e101061b977b46cf00093d4319438055290ad0009f84497a07bf2d2d7a06d0"},
{file = "tox-4.16.0.tar.gz", hash = "sha256:43499656f9949edb681c0f907f86fbfee98677af9919d8b11ae5ad77cb800748"},
]
[package.dependencies]
cachetools = ">=5.3.2"
cachetools = ">=5.3.3"
chardet = ">=5.2"
colorama = ">=0.4.6"
filelock = ">=3.13.1"
packaging = ">=23.2"
platformdirs = ">=4.1"
pluggy = ">=1.3"
pyproject-api = ">=1.6.1"
filelock = ">=3.15.4"
packaging = ">=24.1"
platformdirs = ">=4.2.2"
pluggy = ">=1.5"
pyproject-api = ">=1.7.1"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
virtualenv = ">=20.25"
virtualenv = ">=20.26.3"
[package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.25.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"]
testing = ["build[virtualenv] (>=1.0.3)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=8.0.2)", "distlib (>=0.3.8)", "flaky (>=3.7)", "hatch-vcs (>=0.4)", "hatchling (>=1.21)", "psutil (>=5.9.7)", "pytest (>=7.4.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-xdist (>=3.5)", "re-assert (>=1.1)", "time-machine (>=2.13)", "wheel (>=0.42)"]
docs = ["furo (>=2024.5.6)", "sphinx (>=7.3.7)", "sphinx-argparse-cli (>=1.16)", "sphinx-autodoc-typehints (>=2.2.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"]
testing = ["build[virtualenv] (>=1.2.1)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=70.2)", "time-machine (>=2.14.2)", "wheel (>=0.43)"]
[[package]]
name = "tqdm"
+66 -16
View File
@@ -1,18 +1,26 @@
[tool.poetry]
name = "supervision"
version = "0.22.0rc1"
version = "0.22.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"
license = "MIT"
packages = [{include = "supervision"}]
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", "Roboflow"]
keywords = [
"machine-learning",
"deep-learning",
"vision",
"ML",
"DL",
"AI",
"Roboflow",
]
classifiers=[
classifiers = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Education',
@@ -27,7 +35,7 @@ classifiers=[
'Typing :: Typed',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS'
'Operating System :: MacOS',
]
[tool.poetry.dependencies]
@@ -35,7 +43,7 @@ python = "^3.8"
numpy = ">=1.21.2"
scipy = [
{ version = "1.10.0", python = "<3.9" },
{ version = "^1.10.0", python = ">=3.9" }
{ version = "^1.10.0", python = ">=3.9" },
]
matplotlib = ">=3.6.0"
pyyaml = ">=5.3"
@@ -48,7 +56,7 @@ pillow = ">=9.4"
[tool.poetry.extras]
desktop = ["opencv-python"]
assets = ["requests","tqdm"]
assets = ["requests", "tqdm"]
[tool.poetry.group.dev.dependencies]
twine = "^5.1.1"
@@ -65,13 +73,13 @@ jupytext = "^1.16.1"
nbconvert = "^7.14.2"
docutils = [
{ version = "^0.20.1", python = "<3.9" },
{ version = "^0.21.1", python = ">=3.9" }
{ version = "^0.21.1", python = ">=3.9" },
]
[tool.poetry.group.docs.dependencies]
mkdocs-material = {extras = ["imaging"], version = "^9.5.5"}
mkdocstrings = {extras = ["python"], version = ">=0.20,<0.26"}
mkdocs-material = { extras = ["imaging"], version = "^9.5.5" }
mkdocstrings = { extras = ["python"], version = ">=0.20,<0.26" }
mike = "^2.0.0"
# For Documentation Development use Python 3.10 or above
# Use Latest mkdocs-jupyter min 0.24.6 for Jupyter Notebook Theme support
@@ -79,8 +87,6 @@ mkdocs-jupyter = "^0.24.3"
mkdocs-git-committers-plugin-2 = "^2.2.3"
mkdocs-git-revision-date-localized-plugin = "^1.2.4"
[tool.isort]
line_length = 88
profile = "black"
@@ -93,7 +99,6 @@ tests = ["B201", "B301", "B318", "B314", "B303", "B413", "B412", "B410"]
check = true
imports = ["cv2", "supervision"]
[tool.black]
target-version = ["py38"]
line-length = 88
@@ -152,7 +157,52 @@ indent-width = 4
select = ["E", "F"]
ignore = []
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
fixable = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"I",
"N",
"Q",
"S",
"T",
"W",
"ANN",
"ARG",
"BLE",
"COM",
"DJ",
"DTZ",
"EM",
"ERA",
"EXE",
"FBT",
"ICN",
"INP",
"ISC",
"NPY",
"PD",
"PGH",
"PIE",
"PL",
"PT",
"PTH",
"PYI",
"RET",
"RSE",
"RUF",
"SIM",
"SLF",
"TCH",
"TID",
"TRY",
"UP",
"YTT",
]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
@@ -167,7 +217,7 @@ docstring-quotes = "double"
convention = "google"
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402","F401"]
"__init__.py" = ["E402", "F401"]
[tool.ruff.lint.mccabe]
# Flag errors (`C901`) whenever the complexity level exceeds 5.
@@ -190,7 +240,7 @@ line-ending = "auto"
include-package-data = false
[tool.setuptools.packages.find]
exclude = ["docs*", "test*","examples*"]
exclude = ["docs*", "test*", "examples*"]
[build-system]
requires = ["poetry-core"]
+19
View File
@@ -0,0 +1,19 @@
# Release Process
This doc outlines how supervision is released into production.
It assumes you already have the code changes, as well as a draft of the release notes.
1. Make sure you have all required changes were merged into `develop`.
2. Create and merge a PR, merging `develop` into `main`, containing:
- A commit that updates the project version in `pyproject.toml`.
- All changes made during the release.
3. Tag the commit with the new supervision version.
- Upon pushing the tag, the [PyPi](https://pypi.org/project/supervision/) should update to the new version. Check this!
4. Open and merge a PR, merging `main` into `develop`.
5. Update the docs by running the [Supervision Release Documentation Workflow 📚](https://github.com/roboflow/supervision/actions/workflows/publish-release-docs.yml) workflow from GitHub.
- Select the `main` branch from the dropdown.
6. Create a release on GitHub.
- Go to releases
- Assign the release notes to the tag created in step 3.
- Publish the release.
+3 -1
View File
@@ -9,6 +9,7 @@ except importlib_metadata.PackageNotFoundError:
from supervision.annotators.core import (
BlurAnnotator,
BoundingBoxAnnotator,
BoxAnnotator,
BoxCornerAnnotator,
CircleAnnotator,
ColorAnnotator,
@@ -36,7 +37,6 @@ from supervision.dataset.core import (
DetectionDataset,
)
from supervision.dataset.utils import mask_to_rle, rle_to_mask
from supervision.detection.annotate import BoxAnnotator
from supervision.detection.core import Detections
from supervision.detection.line_zone import LineZone, LineZoneAnnotator
from supervision.detection.lmm import LMM
@@ -67,6 +67,8 @@ from supervision.detection.utils import (
polygon_to_mask,
polygon_to_xyxy,
scale_boxes,
xcycwh_to_xyxy,
xywh_to_xyxy,
)
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import (
+86
View File
@@ -23,8 +23,94 @@ from supervision.utils.conversion import (
ensure_pil_image_for_annotation,
)
from supervision.utils.image import crop_image, overlay_image, scale_image
from supervision.utils.internal import deprecated
class BoxAnnotator(BaseAnnotator):
"""
A class for drawing bounding boxes on an image using provided detections.
"""
def __init__(
self,
color: Union[Color, ColorPalette] = ColorPalette.DEFAULT,
thickness: int = 2,
color_lookup: ColorLookup = ColorLookup.CLASS,
):
"""
Args:
color (Union[Color, ColorPalette]): The color or color palette to use for
annotating detections.
thickness (int): Thickness of the bounding box lines.
color_lookup (str): Strategy for mapping colors to annotations.
Options are `INDEX`, `CLASS`, `TRACK`.
"""
self.color: Union[Color, ColorPalette] = color
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@ensure_cv2_image_for_annotation
def annotate(
self,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> ImageType:
"""
Annotates the given scene with bounding boxes based on the provided detections.
Args:
scene (ImageType): The image where bounding boxes will be drawn. `ImageType`
is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`.
detections (Detections): Object detections to annotate.
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
Allows to override the default color mapping strategy.
Returns:
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
box_annotator = sv.BoxAnnotator()
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections
)
```
![bounding-box-annotator-example](https://media.roboflow.com/
supervision-annotator-examples/bounding-box-annotator-example-purple.png)
"""
for detection_idx in range(len(detections)):
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
color = resolve_color(
color=self.color,
detections=detections,
detection_idx=detection_idx,
color_lookup=self.color_lookup
if custom_color_lookup is None
else custom_color_lookup,
)
cv2.rectangle(
img=scene,
pt1=(x1, y1),
pt2=(x2, y2),
color=color.as_bgr(),
thickness=self.thickness,
)
return scene
@deprecated(
"`BoundingBoxAnnotator` is deprecated and has been renamed to `BoxAnnotator`."
" `BoundingBoxAnnotator` will be removed in supervision-0.26.0."
)
class BoundingBoxAnnotator(BaseAnnotator):
"""
A class for drawing bounding boxes on an image using provided detections.
+357 -139
View File
@@ -3,8 +3,9 @@ from __future__ import annotations
import os
from abc import ABC, abstractmethod
from dataclasses import dataclass
from itertools import chain
from pathlib import Path
from typing import Dict, Iterator, List, Optional, Tuple
from typing import Dict, Iterator, List, Optional, Tuple, Union
import cv2
import numpy as np
@@ -31,9 +32,10 @@ from supervision.dataset.utils import (
train_test_split,
)
from supervision.detection.core import Detections
from supervision.utils.internal import deprecated, warn_deprecated
from supervision.utils.iterables import find_duplicates
@dataclass
class BaseDataset(ABC):
@abstractmethod
def __len__(self) -> int:
@@ -46,30 +48,95 @@ class BaseDataset(ABC):
pass
@dataclass
class DetectionDataset(BaseDataset):
"""
Dataclass containing information about object detection dataset.
Contains information about a detection dataset. Handles lazy image loading
and annotation retrieval, dataset splitting, conversions into multiple
formats.
Attributes:
classes (List[str]): List containing dataset class names.
images (Dict[str, np.ndarray]): Dictionary mapping image name to image.
images (Union[List[str], Dict[str, np.ndarray]]):
Accepts a list of image paths, or dictionaries of loaded cv2 images
with paths as keys. If you pass a list of paths, the dataset will
lazily load images on demand, which is much more memory-efficient.
annotations (Dict[str, Detections]): Dictionary mapping
image name to annotations.
image path to annotations. The dictionary keys match
match the keys in `images` or entries in the list of
image paths.
"""
classes: List[str]
images: Dict[str, np.ndarray]
annotations: Dict[str, Detections]
def __init__(
self,
classes: List[str],
images: Union[List[str], Dict[str, np.ndarray]],
annotations: Dict[str, Detections],
) -> None:
self.classes = classes
if set(images) != set(annotations):
raise ValueError(
"The keys of the images and annotations dictionaries must match."
)
self.annotations = annotations
# Eliminate duplicates while preserving order
self.image_paths = list(dict.fromkeys(images))
self._images_in_memory: Dict[str, np.ndarray] = {}
if isinstance(images, dict):
self._images_in_memory = images
warn_deprecated(
"Passing a `Dict[str, np.ndarray]` into `DetectionDataset` is "
"deprecated and will be removed in `supervision-0.26.0`. Use "
"a list of paths `List[str]` instead."
)
# TODO: when supervision-0.26.0 is released, and Dict[str, np.ndarray]
# for images is no longer supported, also simplify the rest of
# the code. E.g. list(images) is no longer needed, and merge can
# be simplified.
@property
@deprecated(
"`DetectionDataset.images` property is deprecated and will be removed in "
"`supervision-0.26.0`. Iterate with `for path, image, annotation in dataset:` "
"instead."
)
def images(self) -> Dict[str, np.ndarray]:
"""
Load all images to memory and return them as a dictionary.
!!! warning
Only use this when you need all images at once.
It is much more memory-efficient to initialize dataset with
image paths and use `for path, image, annotation in dataset:`.
"""
if self._images_in_memory:
return self._images_in_memory
images = {image_path: cv2.imread(image_path) for image_path in self.image_paths}
return images
def _get_image(self, image_path: str) -> np.ndarray:
"""Assumes that image is in dataset"""
if self._images_in_memory:
return self._images_in_memory[image_path]
return cv2.imread(image_path)
def __len__(self) -> int:
"""
Return the number of images in the dataset.
return len(self._images_in_memory) or len(self.image_paths)
Returns:
int: The number of images.
def __getitem__(self, i: int) -> Tuple[str, np.ndarray, Detections]:
"""
return len(self.images)
Returns:
Tuple[str, np.ndarray, Detections]: The image path, image data,
and its corresponding annotation at index i.
"""
image_path = self.image_paths[i]
image = self._get_image(image_path)
annotation = self.annotations[image_path]
return image_path, image, annotation
def __iter__(self) -> Iterator[Tuple[str, np.ndarray, Detections]]:
"""
@@ -77,25 +144,33 @@ class DetectionDataset(BaseDataset):
Yields:
Iterator[Tuple[str, np.ndarray, Detections]]:
An iterator that yields tuples containing the image name,
An iterator that yields tuples containing the image path,
the image data, and its corresponding annotation.
"""
for image_name, image in self.images.items():
yield image_name, image, self.annotations.get(image_name, None)
for i in range(len(self)):
image_path, image, annotation = self[i]
yield image_path, image, annotation
def __eq__(self, other):
def __eq__(self, other) -> bool:
if not isinstance(other, DetectionDataset):
return False
if set(self.classes) != set(other.classes):
return False
for key in self.images:
if not np.array_equal(self.images[key], other.images[key]):
return False
if not self.annotations[key] == other.annotations[key]:
if self.image_paths != other.image_paths:
return False
if self._images_in_memory or other._images_in_memory:
if not np.array_equal(
list(self._images_in_memory.values()),
list(other._images_in_memory.values()),
):
return False
if self.annotations != other.annotations:
return False
return True
def split(
@@ -127,26 +202,129 @@ class DetectionDataset(BaseDataset):
```
"""
image_names = list(self.images.keys())
train_names, test_names = train_test_split(
data=image_names,
train_paths, test_paths = train_test_split(
data=self.image_paths,
train_ratio=split_ratio,
random_state=random_state,
shuffle=shuffle,
)
train_input: Union[List[str], Dict[str, np.ndarray]]
test_input: Union[List[str], Dict[str, np.ndarray]]
if self._images_in_memory:
train_input = {path: self._images_in_memory[path] for path in train_paths}
test_input = {path: self._images_in_memory[path] for path in test_paths}
else:
train_input = train_paths
test_input = test_paths
train_annotations = {path: self.annotations[path] for path in train_paths}
test_annotations = {path: self.annotations[path] for path in test_paths}
train_dataset = DetectionDataset(
classes=self.classes,
images={name: self.images[name] for name in train_names},
annotations={name: self.annotations[name] for name in train_names},
images=train_input,
annotations=train_annotations,
)
test_dataset = DetectionDataset(
classes=self.classes,
images={name: self.images[name] for name in test_names},
annotations={name: self.annotations[name] for name in test_names},
images=test_input,
annotations=test_annotations,
)
return train_dataset, test_dataset
@classmethod
def merge(cls, dataset_list: List[DetectionDataset]) -> DetectionDataset:
"""
Merge a list of `DetectionDataset` objects into a single
`DetectionDataset` object.
This method takes a list of `DetectionDataset` objects and combines
their respective fields (`classes`, `images`,
`annotations`) into a single `DetectionDataset` object.
Args:
dataset_list (List[DetectionDataset]): A list of `DetectionDataset`
objects to merge.
Returns:
(DetectionDataset): A single `DetectionDataset` object containing
the merged data from the input list.
Examples:
```python
import supervision as sv
ds_1 = sv.DetectionDataset(...)
len(ds_1)
# 100
ds_1.classes
# ['dog', 'person']
ds_2 = sv.DetectionDataset(...)
len(ds_2)
# 200
ds_2.classes
# ['cat']
ds_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
"""
def is_in_memory(dataset: DetectionDataset) -> bool:
return len(dataset._images_in_memory) > 0 or len(dataset.image_paths) == 0
def is_lazy(dataset: DetectionDataset) -> bool:
return len(dataset._images_in_memory) == 0
all_in_memory = all([is_in_memory(dataset) for dataset in dataset_list])
all_lazy = all([is_lazy(dataset) for dataset in dataset_list])
if not all_in_memory and not all_lazy:
raise ValueError(
"Merging lazy and in-memory DetectionDatasets is not supported."
)
images_in_memory = {}
for dataset in dataset_list:
images_in_memory.update(dataset._images_in_memory)
image_paths = list(
chain.from_iterable(dataset.image_paths for dataset in dataset_list)
)
image_paths_unique = list(dict.fromkeys(image_paths))
if len(image_paths) != len(image_paths_unique):
duplicates = find_duplicates(image_paths)
raise ValueError(
f"Image paths {duplicates} are not unique across datasets."
)
image_paths = image_paths_unique
classes = merge_class_lists(
class_lists=[dataset.classes for dataset in dataset_list]
)
annotations = {}
for dataset in dataset_list:
annotations.update(dataset.annotations)
for dataset in dataset_list:
class_index_mapping = build_class_index_mapping(
source_classes=dataset.classes, target_classes=classes
)
for image_path in dataset.image_paths:
annotations[image_path] = map_detections_class_id(
source_to_target_mapping=class_index_mapping,
detections=annotations[image_path],
)
return cls(
classes=classes,
images=images_in_memory or image_paths,
annotations=annotations,
)
def as_pascal_voc(
self,
images_directory_path: Optional[str] = None,
@@ -180,25 +358,22 @@ class DetectionDataset(BaseDataset):
"""
if images_directory_path:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
dataset=self,
images_directory_path=images_directory_path,
)
if annotations_directory_path:
Path(annotations_directory_path).mkdir(parents=True, exist_ok=True)
for image_path, image in self.images.items():
detections = self.annotations[image_path]
if annotations_directory_path:
for image_path, image, annotations in self:
annotation_name = Path(image_path).stem
annotations_path = os.path.join(
annotations_directory_path, f"{annotation_name}.xml"
)
image_name = Path(image_path).name
pascal_voc_xml = detections_to_pascal_voc(
detections=detections,
detections=annotations,
classes=self.classes,
filename=image_name,
image_shape=image.shape,
image_shape=image.shape, # type: ignore
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
@@ -251,13 +426,15 @@ class DetectionDataset(BaseDataset):
```
"""
classes, images, annotations = load_pascal_voc_annotations(
classes, image_paths, annotations = load_pascal_voc_annotations(
images_directory_path=images_directory_path,
annotations_directory_path=annotations_directory_path,
force_masks=force_masks,
)
return DetectionDataset(classes=classes, images=images, annotations=annotations)
return DetectionDataset(
classes=classes, images=image_paths, annotations=annotations
)
@classmethod
def from_yolo(
@@ -311,14 +488,16 @@ class DetectionDataset(BaseDataset):
# ['dog', 'person']
```
"""
classes, images, annotations = load_yolo_annotations(
classes, image_paths, annotations = load_yolo_annotations(
images_directory_path=images_directory_path,
annotations_directory_path=annotations_directory_path,
data_yaml_path=data_yaml_path,
force_masks=force_masks,
is_obb=is_obb,
)
return DetectionDataset(classes=classes, images=images, annotations=annotations)
return DetectionDataset(
classes=classes, images=image_paths, annotations=annotations
)
def as_yolo(
self,
@@ -359,13 +538,12 @@ class DetectionDataset(BaseDataset):
"""
if images_directory_path is not None:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
dataset=self, images_directory_path=images_directory_path
)
if annotations_directory_path is not None:
save_yolo_annotations(
dataset=self,
annotations_directory_path=annotations_directory_path,
images=self.images,
annotations=self.annotations,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
@@ -469,103 +647,134 @@ class DetectionDataset(BaseDataset):
"""
if images_directory_path is not None:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
dataset=self, images_directory_path=images_directory_path
)
if annotations_path is not None:
save_coco_annotations(
dataset=self,
annotation_path=annotations_path,
images=self.images,
annotations=self.annotations,
classes=self.classes,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)
@classmethod
def merge(cls, dataset_list: List[DetectionDataset]) -> DetectionDataset:
"""
Merge a list of `DetectionDataset` objects into a single
`DetectionDataset` object.
This method takes a list of `DetectionDataset` objects and combines
their respective fields (`classes`, `images`,
`annotations`) into a single `DetectionDataset` object.
Args:
dataset_list (List[DetectionDataset]): A list of `DetectionDataset`
objects to merge.
Returns:
(DetectionDataset): A single `DetectionDataset` object containing
the merged data from the input list.
Examples:
```python
import supervision as sv
ds_1 = sv.DetectionDataset(...)
len(ds_1)
# 100
ds_1.classes
# ['dog', 'person']
ds_2 = sv.DetectionDataset(...)
len(ds_2)
# 200
ds_2.classes
# ['cat']
ds_merged = sv.DetectionDataset.merge([ds_1, ds_2])
len(ds_merged)
# 300
ds_merged.classes
# ['cat', 'dog', 'person']
```
"""
merged_images, merged_annotations = {}, {}
class_lists = [dataset.classes for dataset in dataset_list]
merged_classes = merge_class_lists(class_lists=class_lists)
for dataset in dataset_list:
class_index_mapping = build_class_index_mapping(
source_classes=dataset.classes, target_classes=merged_classes
)
for image_name, image, detections in dataset:
if image_name in merged_annotations:
raise ValueError(
f"Image name {image_name} is not unique across datasets."
)
merged_images[image_name] = image
merged_annotations[image_name] = map_detections_class_id(
source_to_target_mapping=class_index_mapping,
detections=detections,
)
return cls(
classes=merged_classes, images=merged_images, annotations=merged_annotations
)
@dataclass
class ClassificationDataset(BaseDataset):
"""
Dataclass containing information about a classification dataset.
Contains information about a classification dataset, handles lazy image
loading, dataset splitting.
Attributes:
classes (List[str]): List containing dataset class names.
images (Dict[str, np.ndarray]): Dictionary mapping image name to image.
annotations (Dict[str, Detections]): Dictionary mapping
images (Union[List[str], Dict[str, np.ndarray]]):
List of image paths or dictionary mapping image name to image data.
annotations (Dict[str, Classifications]): Dictionary mapping
image name to annotations.
"""
classes: List[str]
images: Dict[str, np.ndarray]
annotations: Dict[str, Classifications]
def __init__(
self,
classes: List[str],
images: Union[List[str], Dict[str, np.ndarray]],
annotations: Dict[str, Classifications],
) -> None:
self.classes = classes
if set(images) != set(annotations):
raise ValueError(
"The keys of the images and annotations dictionaries must match."
)
self.annotations = annotations
# Eliminate duplicates while preserving order
self.image_paths = list(dict.fromkeys(images))
self._images_in_memory: Dict[str, np.ndarray] = {}
if isinstance(images, dict):
self._images_in_memory = images
warn_deprecated(
"Passing a `Dict[str, np.ndarray]` into `ClassificationDataset` is "
"deprecated and will be removed in a future release. Use "
"a list of paths `List[str]` instead."
)
@property
@deprecated(
"`DetectionDataset.images` property is deprecated and will be removed in "
"`supervision-0.26.0`. Iterate with `for path, image, annotation in dataset:` "
"instead."
)
def images(self) -> Dict[str, np.ndarray]:
"""
Load all images to memory and return them as a dictionary.
!!! warning
Only use this when you need all images at once.
It is much more memory-efficient to initialize dataset with
image paths and use `for path, image, annotation in dataset:`.
"""
if self._images_in_memory:
return self._images_in_memory
images = {image_path: cv2.imread(image_path) for image_path in self.image_paths}
return images
def _get_image(self, image_path: str) -> np.ndarray:
"""Assumes that image is in dataset"""
if self._images_in_memory:
return self._images_in_memory[image_path]
return cv2.imread(image_path)
def __len__(self) -> int:
return len(self.images)
return len(self._images_in_memory) or len(self.image_paths)
def __getitem__(self, i: int) -> Tuple[str, np.ndarray, Classifications]:
"""
Returns:
Tuple[str, np.ndarray, Classifications]: The image path, image data,
and its corresponding annotation at index i.
"""
image_path = self.image_paths[i]
image = self._get_image(image_path)
annotation = self.annotations[image_path]
return image_path, image, annotation
def __iter__(self) -> Iterator[Tuple[str, np.ndarray, Classifications]]:
"""
Iterate over the images and annotations in the dataset.
Yields:
Iterator[Tuple[str, np.ndarray, Detections]]:
An iterator that yields tuples containing the image path,
the image data, and its corresponding annotation.
"""
for i in range(len(self)):
image_path, image, annotation = self[i]
yield image_path, image, annotation
def __eq__(self, other) -> bool:
if not isinstance(other, ClassificationDataset):
return False
if set(self.classes) != set(other.classes):
return False
if self.image_paths != other.image_paths:
return False
if self._images_in_memory or other._images_in_memory:
if not np.array_equal(
list(self._images_in_memory.values()),
list(other._images_in_memory.values()),
):
return False
if self.annotations != other.annotations:
return False
return True
def split(
self, split_ratio=0.8, random_state=None, shuffle: bool = True
@@ -595,24 +804,35 @@ class ClassificationDataset(BaseDataset):
# (700, 300)
```
"""
image_names = list(self.images.keys())
train_names, test_names = train_test_split(
data=image_names,
train_paths, test_paths = train_test_split(
data=self.image_paths,
train_ratio=split_ratio,
random_state=random_state,
shuffle=shuffle,
)
train_input: Union[List[str], Dict[str, np.ndarray]]
test_input: Union[List[str], Dict[str, np.ndarray]]
if self._images_in_memory:
train_input = {path: self._images_in_memory[path] for path in train_paths}
test_input = {path: self._images_in_memory[path] for path in test_paths}
else:
train_input = train_paths
test_input = test_paths
train_annotations = {path: self.annotations[path] for path in train_paths}
test_annotations = {path: self.annotations[path] for path in test_paths}
train_dataset = ClassificationDataset(
classes=self.classes,
images={name: self.images[name] for name in train_names},
annotations={name: self.annotations[name] for name in train_names},
images=train_input,
annotations=train_annotations,
)
test_dataset = ClassificationDataset(
classes=self.classes,
images={name: self.images[name] for name in test_names},
annotations={name: self.annotations[name] for name in test_names},
images=test_input,
annotations=test_annotations,
)
return train_dataset, test_dataset
def as_folder_structure(self, root_directory_path: str) -> None:
@@ -628,18 +848,16 @@ class ClassificationDataset(BaseDataset):
for class_name in self.classes:
os.makedirs(os.path.join(root_directory_path, class_name), exist_ok=True)
for image_path in self.images:
classification = self.annotations[image_path]
image = self.images[image_path]
image_name = Path(image_path).name
for image_save_path, image, annotation in self:
image_name = Path(image_save_path).name
class_id = (
classification.class_id[0]
if classification.confidence is None
else classification.get_top_k(1)[0][0]
annotation.class_id[0]
if annotation.confidence is None
else annotation.get_top_k(1)[0][0]
)
class_name = self.classes[class_id]
image_path = os.path.join(root_directory_path, class_name, image_name)
cv2.imwrite(image_path, image)
image_save_path = os.path.join(root_directory_path, class_name, image_name)
cv2.imwrite(image_save_path, image)
@classmethod
def from_folder_structure(cls, root_directory_path: str) -> ClassificationDataset:
@@ -672,7 +890,7 @@ class ClassificationDataset(BaseDataset):
classes = os.listdir(root_directory_path)
classes = sorted(set(classes))
images = {}
image_paths = []
annotations = {}
for class_name in classes:
@@ -680,13 +898,13 @@ class ClassificationDataset(BaseDataset):
for image in os.listdir(os.path.join(root_directory_path, class_name)):
image_path = str(os.path.join(root_directory_path, class_name, image))
images[image_path] = cv2.imread(image_path)
image_paths.append(image_path)
annotations[image_path] = Classifications(
class_id=np.array([class_id]),
)
return cls(
classes=classes,
images=images,
images=image_paths,
annotations=annotations,
)
+12 -16
View File
@@ -1,9 +1,8 @@
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Tuple
from typing import TYPE_CHECKING, Dict, List, Tuple
import cv2
import numpy as np
import numpy.typing as npt
@@ -21,6 +20,9 @@ from supervision.detection.utils import (
)
from supervision.utils.file import read_json_file, save_json_file
if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
def coco_categories_to_classes(coco_categories: List[dict]) -> List[str]:
return [
@@ -161,7 +163,7 @@ def load_coco_annotations(
images_directory_path: str,
annotations_path: str,
force_masks: bool = False,
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
) -> Tuple[List[str], List[str], 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(
@@ -172,7 +174,7 @@ def load_coco_annotations(
coco_annotations=coco_data["annotations"]
)
images = {}
images = []
annotations = {}
for coco_image in coco_images:
@@ -184,7 +186,6 @@ def load_coco_annotations(
image_annotations = coco_annotations_groups.get(coco_image["id"], [])
image_path = os.path.join(images_directory_path, image_name)
image = cv2.imread(image_path)
annotation = coco_annotations_to_detections(
image_annotations=image_annotations,
resolution_wh=(image_width, image_height),
@@ -195,23 +196,20 @@ def load_coco_annotations(
detections=annotation,
)
images[image_path] = image
images.append(image_path)
annotations[image_path] = annotation
return classes, images, annotations
def save_coco_annotations(
dataset: "DetectionDataset",
annotation_path: str,
images: Dict[str, np.ndarray],
annotations: Dict[str, Detections],
classes: List[str],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> None:
Path(annotation_path).parent.mkdir(parents=True, exist_ok=True)
info = {}
licenses = [
{
"id": 1,
@@ -222,10 +220,10 @@ def save_coco_annotations(
coco_annotations = []
coco_images = []
coco_categories = classes_to_coco_categories(classes=classes)
coco_categories = classes_to_coco_categories(classes=dataset.classes)
image_id, annotation_id = 1, 1
for image_path, image in images.items():
for image_path, image, annotation in dataset:
image_height, image_width, _ = image.shape
image_name = f"{Path(image_path).stem}{Path(image_path).suffix}"
coco_image = {
@@ -238,10 +236,8 @@ def save_coco_annotations(
}
coco_images.append(coco_image)
detections = annotations[image_path]
coco_annotation, annotation_id = detections_to_coco_annotations(
detections=detections,
detections=annotation,
image_id=image_id,
annotation_id=annotation_id,
min_image_area_percentage=min_image_area_percentage,
@@ -253,7 +249,7 @@ def save_coco_annotations(
image_id += 1
annotation_dict = {
"info": info,
"info": {},
"licenses": licenses,
"categories": coco_categories,
"images": coco_images,
+18 -23
View File
@@ -5,7 +5,8 @@ from xml.etree.ElementTree import Element, SubElement
import cv2
import numpy as np
from defusedxml.ElementTree import fromstring, parse, tostring
from defusedxml.ElementTree import parse, tostring
from defusedxml.minidom import parseString
from supervision.dataset.utils import approximate_mask_with_polygons
from supervision.detection.core import Detections
@@ -129,8 +130,7 @@ def detections_to_pascal_voc(
annotation.append(next_object)
# Generate XML string
xml_string = fromstring(tostring(annotation)).toprettyxml(indent=" ")
xml_string = parseString(tostring(annotation)).toprettyxml(indent=" ")
return xml_string
@@ -138,7 +138,7 @@ def load_pascal_voc_annotations(
images_directory_path: str,
annotations_directory_path: str,
force_masks: bool = False,
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
) -> Tuple[List[str], List[str], Dict[str, Detections]]:
"""
Loads PASCAL VOC XML annotations and returns the image name,
a Detections instance, and a list of class names.
@@ -151,44 +151,39 @@ def load_pascal_voc_annotations(
annotations, regardless of whether they are present.
Returns:
Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]: A tuple
containing a list of class names,
a dictionary with image names as keys and
images as values, and a dictionary with image names as
keys and corresponding Detections instances as values.
Tuple[List[str], List[str], Dict[str, Detections]]: A tuple with a list
of class names, a list of paths to images, and a dictionary with image
paths as keys and corresponding Detections instances as values.
"""
image_paths = list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
image_paths = [
str(path)
for path in list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
]
classes = []
images = {}
classes: List[str] = []
annotations = {}
for image_path in image_paths:
image_name = Path(image_path).stem
image_path = str(image_path)
image = cv2.imread(image_path)
annotation_path = os.path.join(annotations_directory_path, f"{image_name}.xml")
image_stem = Path(image_path).stem
annotation_path = os.path.join(annotations_directory_path, f"{image_stem}.xml")
if not os.path.exists(annotation_path):
images[image_path] = image
annotations[image_path] = Detections.empty()
continue
tree = parse(annotation_path)
root = tree.getroot()
image = cv2.imread(image_path)
resolution_wh = (image.shape[1], image.shape[0])
annotation, classes = detections_from_xml_obj(
root, classes, resolution_wh, force_masks
)
images[image_path] = image
annotations[image_path] = annotation
return classes, images, annotations
return classes, image_paths, annotations
def detections_from_xml_obj(
+21 -19
View File
@@ -1,6 +1,6 @@
import os
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
import cv2
import numpy as np
@@ -17,6 +17,9 @@ from supervision.utils.file import (
save_yaml_file,
)
if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
def _parse_box(values: List[str]) -> np.ndarray:
x_center, y_center, width, height = values
@@ -124,7 +127,7 @@ def load_yolo_annotations(
data_yaml_path: str,
force_masks: bool = False,
is_obb: bool = False,
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
) -> Tuple[List[str], List[str], Dict[str, Detections]]:
"""
Loads YOLO annotations and returns class names, images,
and their corresponding detections.
@@ -142,30 +145,29 @@ def load_yolo_annotations(
where pairs of [x, y] are box corners.
Returns:
Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
Tuple[List[str], List[str], Dict[str, Detections]]:
A tuple containing a list of class names, a dictionary with
image names as keys and images as values, and a dictionary
with image names as keys and corresponding Detections instances as values.
"""
image_paths = list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
image_paths = [
str(path)
for path in list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
]
classes = _extract_class_names(file_path=data_yaml_path)
images = {}
annotations = {}
for image_path in image_paths:
image_stem = Path(image_path).stem
image_path = str(image_path)
image = cv2.imread(image_path)
annotation_path = os.path.join(annotations_directory_path, f"{image_stem}.txt")
if not os.path.exists(annotation_path):
images[image_path] = image
annotations[image_path] = Detections.empty()
continue
image = cv2.imread(image_path)
lines = read_txt_file(file_path=annotation_path, skip_empty=True)
h, w, _ = image.shape
resolution_wh = (w, h)
@@ -178,9 +180,8 @@ def load_yolo_annotations(
with_masks=with_masks,
is_obb=is_obb,
)
images[image_path] = image
annotations[image_path] = annotation
return classes, images, annotations
return classes, image_paths, annotations
def object_to_yolo(
@@ -214,6 +215,9 @@ def detections_to_yolo_annotations(
) -> List[str]:
annotation = []
for xyxy, mask, _, class_id, _, _ in detections:
if class_id is None:
raise ValueError("Class ID is required for YOLO annotations.")
if mask is not None:
polygons = approximate_mask_with_polygons(
mask=mask,
@@ -239,24 +243,22 @@ def detections_to_yolo_annotations(
def save_yolo_annotations(
dataset: "DetectionDataset",
annotations_directory_path: str,
images: Dict[str, np.ndarray],
annotations: Dict[str, Detections],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> None:
Path(annotations_directory_path).mkdir(parents=True, exist_ok=True)
for image_path, image in images.items():
detections = annotations[image_path]
for image_path, image, annotation in dataset:
image_name = Path(image_path).name
yolo_annotations_name = _image_name_to_annotation_name(image_name=image_name)
yolo_annotations_path = os.path.join(
annotations_directory_path, yolo_annotations_name
)
lines = detections_to_yolo_annotations(
detections=detections,
image_shape=image.shape,
detections=annotation,
image_shape=image.shape, # type: ignore
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
+14 -7
View File
@@ -1,8 +1,9 @@
import copy
import os
import random
import shutil
from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypeVar, Union
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, TypeVar, Union
import cv2
import numpy as np
@@ -15,6 +16,9 @@ from supervision.detection.utils import (
mask_to_polygons,
)
if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
T = TypeVar("T")
@@ -59,6 +63,7 @@ def merge_class_lists(class_lists: List[List[str]]) -> List[str]:
def build_class_index_mapping(
source_classes: List[str], target_classes: List[str]
) -> Dict[int, int]:
"""Returns the index map of source classes -> target classes."""
index_mapping = {}
for i, class_name in enumerate(source_classes):
@@ -94,14 +99,16 @@ def map_detections_class_id(
def save_dataset_images(
images_directory_path: str, images: Dict[str, np.ndarray]
dataset: "DetectionDataset", images_directory_path: str
) -> None:
Path(images_directory_path).mkdir(parents=True, exist_ok=True)
for image_path, image in images.items():
image_name = Path(image_path).name
target_image_path = os.path.join(images_directory_path, image_name)
cv2.imwrite(target_image_path, image)
for image_path in dataset.image_paths:
final_path = os.path.join(images_directory_path, Path(image_path).name)
if image_path in dataset._images_in_memory:
image = dataset._images_in_memory[image_path]
cv2.imwrite(final_path, image)
else:
shutil.copyfile(image_path, final_path)
def train_test_split(
-155
View File
@@ -1,155 +0,0 @@
from typing import List, Optional, Union
import cv2
from supervision.annotators.base import ImageType
from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.utils.conversion import ensure_cv2_image_for_annotation
from supervision.utils.internal import deprecated
class BoxAnnotator:
"""
A class for drawing bounding boxes on an image using detections provided.
Attributes:
color (Union[Color, ColorPalette]): The color to draw the bounding box,
can be a single color or a color palette
thickness (int): The thickness of the bounding box lines, default is 2
text_color (Color): The color of the text on the bounding box, default is white
text_scale (float): The scale of the text on the bounding box, default is 0.5
text_thickness (int): The thickness of the text on the bounding box,
default is 1
text_padding (int): The padding around the text on the bounding box,
default is 5
"""
def __init__(
self,
color: Union[Color, ColorPalette] = ColorPalette.DEFAULT,
thickness: int = 2,
text_color: Color = Color.BLACK,
text_scale: float = 0.5,
text_thickness: int = 1,
text_padding: int = 10,
):
self.color: Union[Color, ColorPalette] = color
self.thickness: int = thickness
self.text_color: Color = text_color
self.text_scale: float = text_scale
self.text_thickness: int = text_thickness
self.text_padding: int = text_padding
@deprecated(
"`BoxAnnotator` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `BoundingBoxAnnotator` and `LabelAnnotator` instead"
)
@ensure_cv2_image_for_annotation
def annotate(
self,
scene: ImageType,
detections: Detections,
labels: Optional[List[str]] = None,
skip_label: bool = False,
) -> ImageType:
"""
Draws bounding boxes on the frame using the detections provided.
Args:
scene (ImageType): The image on which the bounding boxes will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): The detections for which the
bounding boxes will be drawn
labels (Optional[List[str]]): An optional list of labels
corresponding to each detection. If `labels` are not provided,
corresponding `class_id` will be used as label.
skip_label (bool): Is set to `True`, skips bounding box label annotation.
Returns:
ImageType: The image with the bounding boxes drawn on it, matching the
type of `scene` (`numpy.ndarray` or `PIL.Image.Image`)
Example:
```python
import supervision as sv
classes = ['person', ...]
image = ...
detections = sv.Detections(...)
box_annotator = sv.BoxAnnotator()
labels = [
f"{classes[class_id]} {confidence:0.2f}"
for _, _, confidence, class_id, _ in detections
]
annotated_frame = box_annotator.annotate(
scene=image.copy(),
detections=detections,
labels=labels
)
```
"""
font = cv2.FONT_HERSHEY_SIMPLEX
for i in range(len(detections)):
x1, y1, x2, y2 = detections.xyxy[i].astype(int)
class_id = (
detections.class_id[i] if detections.class_id is not None else None
)
idx = class_id if class_id is not None else i
color = (
self.color.by_idx(idx)
if isinstance(self.color, ColorPalette)
else self.color
)
cv2.rectangle(
img=scene,
pt1=(x1, y1),
pt2=(x2, y2),
color=color.as_bgr(),
thickness=self.thickness,
)
if skip_label:
continue
text = (
f"{class_id}"
if (labels is None or len(detections) != len(labels))
else labels[i]
)
text_width, text_height = cv2.getTextSize(
text=text,
fontFace=font,
fontScale=self.text_scale,
thickness=self.text_thickness,
)[0]
text_x = x1 + self.text_padding
text_y = y1 - self.text_padding
text_background_x1 = x1
text_background_y1 = y1 - 2 * self.text_padding - text_height
text_background_x2 = x1 + 2 * self.text_padding + text_width
text_background_y2 = y1
cv2.rectangle(
img=scene,
pt1=(text_background_x1, text_background_y1),
pt2=(text_background_x2, text_background_y2),
color=color.as_bgr(),
thickness=cv2.FILLED,
)
cv2.putText(
img=scene,
text=text,
org=(text_x, text_y),
fontFace=font,
fontScale=self.text_scale,
color=self.text_color.as_rgb(),
thickness=self.text_thickness,
lineType=cv2.LINE_AA,
)
return scene
+2 -41
View File
@@ -30,7 +30,7 @@ from supervision.detection.utils import (
xywh_to_xyxy,
)
from supervision.geometry.core import Position
from supervision.utils.internal import deprecated, get_instance_variables
from supervision.utils.internal import get_instance_variables
from supervision.validators import validate_detections_fields
@@ -608,45 +608,6 @@ class Detections:
data=data,
)
@classmethod
@deprecated(
"`Detections.from_roboflow` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Detections.from_inference` instead."
)
def from_roboflow(cls, roboflow_result: Union[dict, Any]) -> Detections:
"""
!!! failure "Deprecated"
`Detections.from_roboflow` is deprecated and will be removed in
`supervision-0.22.0`. Use `Detections.from_inference` instead.
Create a Detections object from the [Roboflow](https://roboflow.com/)
API inference result or the [Inference](https://inference.roboflow.com/)
package results.
Args:
roboflow_result (dict): The result from the
Roboflow API containing predictions.
Returns:
(Detections): A Detections object containing the bounding boxes, class IDs,
and confidences of the predictions.
Example:
```python
import cv2
import supervision as sv
from inference import get_model
image = cv2.imread(<SOURCE_IMAGE_PATH>)
model = get_model(model_id="yolov8s-640")
result = model.infer(image)[0]
detections = sv.Detections.from_roboflow(result)
```
"""
return cls.from_inference(roboflow_result)
@classmethod
def from_sam(cls, sam_result: List[dict]) -> Detections:
"""
@@ -686,7 +647,7 @@ class Detections:
if np.asarray(xywh).shape[0] == 0:
return cls.empty()
xyxy = xywh_to_xyxy(boxes_xywh=xywh)
xyxy = xywh_to_xyxy(xywh=xywh)
return cls(xyxy=xyxy, mask=mask)
@classmethod
+2
View File
@@ -174,7 +174,9 @@ def from_florence_2(
match is not None
), f"Expected string to end in location tags, but got {result}"
w, h = resolution_wh
xyxy = np.array([match.groups()], dtype=np.float32)
xyxy *= np.array([w, h, w, h]) / 1000
result_string = result[: match.start()]
labels = np.array([result_string])
return xyxy, labels, None, None
+2 -2
View File
@@ -41,7 +41,7 @@ class DetectionsSmoother:
tracker = sv.ByteTrack(frame_rate=video_info.fps)
smoother = sv.DetectionsSmoother()
annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
with sv.VideoSink(<TARGET_FILE_PATH>, video_info=video_info) as sink:
for frame in frame_generator:
@@ -50,7 +50,7 @@ class DetectionsSmoother:
detections = tracker.update_with_detections(detections)
detections = smoother.update_with_detections(detections)
annotated_frame = bounding_box_annotator.annotate(frame.copy(), detections)
annotated_frame = box_annotator.annotate(frame.copy(), detections)
sink.write_frame(annotated_frame)
```
""" # noqa: E501 // docs
+72 -4
View File
@@ -228,10 +228,78 @@ def pad_boxes(xyxy: np.ndarray, px: int, py: Optional[int] = None) -> np.ndarray
return result
def xywh_to_xyxy(boxes_xywh: np.ndarray) -> np.ndarray:
xyxy = boxes_xywh.copy()
xyxy[:, 2] = boxes_xywh[:, 0] + boxes_xywh[:, 2]
xyxy[:, 3] = boxes_xywh[:, 1] + boxes_xywh[:, 3]
def xywh_to_xyxy(xywh: np.ndarray) -> np.ndarray:
"""
Converts bounding box coordinates from `(x, y, width, height)`
format to `(x_min, y_min, x_max, y_max)` format.
Args:
xywh (np.ndarray): A numpy array of shape `(N, 4)` where each row
corresponds to a bounding box in the format `(x, y, width, height)`.
Returns:
np.ndarray: A numpy array of shape `(N, 4)` where each row corresponds
to a bounding box in the format `(x_min, y_min, x_max, y_max)`.
Examples:
```python
import numpy as np
import supervision as sv
xywh = np.array([
[10, 20, 30, 40],
[15, 25, 35, 45]
])
sv.xywh_to_xyxy(xywh=xywh)
# array([
# [10, 20, 40, 60],
# [15, 25, 50, 70]
# ])
```
"""
xyxy = xywh.copy()
xyxy[:, 2] = xywh[:, 0] + xywh[:, 2]
xyxy[:, 3] = xywh[:, 1] + xywh[:, 3]
return xyxy
def xcycwh_to_xyxy(xcycwh: np.ndarray) -> np.ndarray:
"""
Converts bounding box coordinates from `(center_x, center_y, width, height)`
format to `(x_min, y_min, x_max, y_max)` format.
Args:
xcycwh (np.ndarray): A numpy array of shape `(N, 4)` where each row
corresponds to a bounding box in the format `(center_x, center_y, width,
height)`.
Returns:
np.ndarray: A numpy array of shape `(N, 4)` where each row corresponds
to a bounding box in the format `(x_min, y_min, x_max, y_max)`.
Examples:
```python
import numpy as np
import supervision as sv
xcycwh = np.array([
[50, 50, 20, 30],
[30, 40, 10, 15]
])
sv.xcycwh_to_xyxy(xcycwh=xcycwh)
# array([
# [40, 35, 60, 65],
# [25, 32.5, 35, 47.5]
# ])
```
"""
xyxy = xcycwh.copy()
xyxy[:, 0] = xcycwh[:, 0] - xcycwh[:, 2] / 2
xyxy[:, 1] = xcycwh[:, 1] - xcycwh[:, 3] / 2
xyxy[:, 2] = xcycwh[:, 0] + xcycwh[:, 2] / 2
xyxy[:, 3] = xcycwh[:, 1] + xcycwh[:, 3] / 2
return xyxy
+1 -68
View File
@@ -5,7 +5,7 @@ from typing import List, Tuple, Union
import matplotlib.pyplot as plt
from supervision.utils.internal import classproperty, deprecated
from supervision.utils.internal import classproperty
DEFAULT_COLOR_PALETTE = [
"A351FB",
@@ -255,46 +255,6 @@ class Color:
def ROBOFLOW(cls) -> Color:
return Color.from_hex("#A351FB")
@classmethod
@deprecated(
"`Color.white()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.WHITE` instead."
)
def white(cls) -> Color:
return Color.from_hex(color_hex="#ffffff")
@classmethod
@deprecated(
"`Color.black()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.BLACK` instead."
)
def black(cls) -> Color:
return Color.from_hex(color_hex="#000000")
@classmethod
@deprecated(
"`Color.red()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.RED` instead."
)
def red(cls) -> Color:
return Color.from_hex(color_hex="#ff0000")
@classmethod
@deprecated(
"`Color.green()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.GREEN` instead."
)
def green(cls) -> Color:
return Color.from_hex(color_hex="#00ff00")
@classmethod
@deprecated(
"`Color.blue()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.BLUE` instead."
)
def blue(cls) -> Color:
return Color.from_hex(color_hex="#0000ff")
@dataclass
class ColorPalette:
@@ -346,33 +306,6 @@ class ColorPalette:
def LEGACY(cls) -> ColorPalette:
return ColorPalette.from_hex(color_hex_list=LEGACY_COLOR_PALETTE)
@classmethod
@deprecated(
"`ColorPalette.default()` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `Color.DEFAULT` instead."
)
def default(cls) -> ColorPalette:
"""
!!! failure "Deprecated"
`ColorPalette.default()` is deprecated and will be removed in
`supervision-0.22.0`. Use `Color.DEFAULT` instead.
Returns a default color palette.
Returns:
ColorPalette: A ColorPalette instance with default colors.
Example:
```python
import supervision as sv
sv.ColorPalette.default()
# ColorPalette(colors=[Color(r=255, g=64, b=64), Color(r=255, g=161, b=160), ...])
```
""" # noqa: E501 // docs
return ColorPalette.from_hex(color_hex_list=DEFAULT_COLOR_PALETTE)
@classmethod
def from_hex(cls, color_hex_list: List[str]) -> ColorPalette:
"""
+2 -2
View File
@@ -295,8 +295,8 @@ def calculate_optimal_text_scale(resolution_wh: Tuple[int, int]) -> float:
Calculate font scale based on the resolution of an image.
Parameters:
resolution_wh (Tuple[int, int]): A tuple representing the width and height
of the image.
resolution_wh (Tuple[int, int]): A tuple representing the width and height
of the image.
Returns:
float: The calculated font scale factor.
+100 -7
View File
@@ -243,9 +243,10 @@ class KeyPoints:
pose landmark detection inference result.
Args:
mediapipe_results (Union[PoseLandmarkerResult, SolutionOutputs]):
The output results from Mediapipe. It supports both: the inference
result `PoseLandmarker` and the legacy one from `Pose`.
mediapipe_results (Union[PoseLandmarkerResult, FaceLandmarkerResult, SolutionOutputs]):
The output results from Mediapipe. It support pose and face landmarks
from `PoseLandmaker`, `FaceLandmarker` and the legacy ones
from `Pose` and `FaceMesh`.
resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
representing the resolution of the frame.
@@ -283,14 +284,55 @@ class KeyPoints:
key_points = sv.KeyPoints.from_mediapipe(
pose_landmarker_result, (image_width, image_height))
```
```python
import cv2
import mediapipe as mp
import supervision as sv
image = cv2.imread(<SOURCE_IMAGE_PATH>)
image_height, image_width, _ = image.shape
mediapipe_image = mp.Image(
image_format=mp.ImageFormat.SRGB,
data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
options = mp.tasks.vision.FaceLandmarkerOptions(
base_options=mp.tasks.BaseOptions(
model_asset_path="face_landmarker.task"
),
output_face_blendshapes=True,
output_facial_transformation_matrixes=True,
num_faces=2)
FaceLandmarker = mp.tasks.vision.FaceLandmarker
with FaceLandmarker.create_from_options(options) as landmarker:
face_landmarker_result = landmarker.detect(mediapipe_image)
key_points = sv.KeyPoints.from_mediapipe(
face_landmarker_result, (image_width, image_height))
```
""" # noqa: E501 // docs
results = mediapipe_results.pose_landmarks
if not isinstance(mediapipe_results.pose_landmarks, list):
if mediapipe_results.pose_landmarks is None:
if hasattr(mediapipe_results, "pose_landmarks"):
results = mediapipe_results.pose_landmarks
if not isinstance(mediapipe_results.pose_landmarks, list):
if mediapipe_results.pose_landmarks is None:
results = []
else:
results = [
[
landmark
for landmark in mediapipe_results.pose_landmarks.landmark
]
]
elif hasattr(mediapipe_results, "face_landmarks"):
results = mediapipe_results.face_landmarks
elif hasattr(mediapipe_results, "multi_face_landmarks"):
if mediapipe_results.multi_face_landmarks is None:
results = []
else:
results = [
[landmark for landmark in mediapipe_results.pose_landmarks.landmark]
face_landmark.landmark
for face_landmark in mediapipe_results.multi_face_landmarks
]
if len(results) == 0:
@@ -414,6 +456,57 @@ class KeyPoints:
data=data,
)
@classmethod
def from_detectron2(cls, detectron2_results) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the
[Detectron2](https://github.com/facebookresearch/detectron2) inference result.
Args:
detectron2_results: The output of a
Detectron2 model containing instances with prediction data.
Returns:
A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
and class names, and confidences of each keypoint.
Example:
```python
import cv2
import supervision as sv
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
image = cv2.imread(<SOURCE_IMAGE_PATH>)
cfg = get_cfg()
cfg.merge_from_file(<CONFIG_PATH>)
cfg.MODEL.WEIGHTS = <WEIGHTS_PATH>
predictor = DefaultPredictor(cfg)
result = predictor(image)
keypoints = sv.KeyPoints.from_detectron2(result)
```
"""
if hasattr(detectron2_results["instances"], "pred_keypoints"):
if detectron2_results["instances"].pred_keypoints.cpu().numpy().size == 0:
return cls.empty()
return cls(
xy=detectron2_results["instances"]
.pred_keypoints.cpu()
.numpy()[:, :, :2],
confidence=detectron2_results["instances"]
.pred_keypoints.cpu()
.numpy()[:, :, 2],
class_id=detectron2_results["instances"]
.pred_classes.cpu()
.numpy()
.astype(int),
)
else:
return cls.empty()
def __getitem__(
self, index: Union[int, slice, List[int], np.ndarray, str]
) -> Union[KeyPoints, List, np.ndarray, None]:
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -276,7 +276,7 @@ class ByteTrack:
model = YOLO(<MODEL_PATH>)
tracker = sv.ByteTrack()
bounding_box_annotator = sv.BoundingBoxAnnotator()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
def callback(frame: np.ndarray, index: int) -> np.ndarray:
@@ -286,7 +286,7 @@ class ByteTrack:
labels = [f"#{tracker_id}" for tracker_id in detections.tracker_id]
annotated_frame = bounding_box_annotator.annotate(
annotated_frame = box_annotator.annotate(
scene=frame.copy(), detections=detections)
annotated_frame = label_annotator.annotate(
scene=annotated_frame, detections=detections, labels=labels)
+13 -9
View File
@@ -31,6 +31,16 @@ else:
warnings.simplefilter("always", SupervisionWarnings)
def warn_deprecated(message: str):
"""
Issue a warning that a function is deprecated.
Args:
message (str): The message to display when the function is called.
"""
warnings.warn(message, category=SupervisionWarnings, stacklevel=2)
def deprecated_parameter(
old_parameter: str,
new_parameter: str,
@@ -82,15 +92,13 @@ def deprecated_parameter(
else:
function_name = func.__name__
warnings.warn(
warn_deprecated(
message=warning_message.format(
function_name=function_name,
old_parameter=old_parameter,
new_parameter=new_parameter,
**message_kwargs,
),
category=SupervisionWarnings,
stacklevel=2,
)
)
kwargs[new_parameter] = map_function(kwargs.pop(old_parameter))
@@ -106,11 +114,7 @@ def deprecated(reason: str):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated: {reason}",
category=SupervisionWarnings,
stacklevel=2,
)
warn_deprecated(f"{func.__name__} is deprecated: {reason}")
return func(*args, **kwargs)
return wrapper
+14
View File
@@ -68,3 +68,17 @@ def fill(sequence: List[V], desired_size: int, content: V) -> List[V]:
missing_size = max(0, desired_size - len(sequence))
sequence.extend([content] * missing_size)
return sequence
def find_duplicates(sequence: List) -> List:
"""
Find all duplicate elements in the input sequence.
"""
seen = set()
duplicates = set()
for element in sequence:
if element in seen:
duplicates.add(element)
else:
seen.add(element)
return list(duplicates)
-20
View File
@@ -8,8 +8,6 @@ from typing import Callable, Generator, Optional, Tuple
import cv2
import numpy as np
from supervision.utils.internal import deprecated
@dataclass
class VideoInfo:
@@ -239,24 +237,6 @@ class FPSMonitor:
""" # noqa: E501 // docs
self.all_timestamps = deque(maxlen=sample_size)
@deprecated(
"`FPSMonitor.__call__` is deprecated and will be removed in "
"`supervision-0.22.0`. Use `FPSMonitor.fps` instead."
)
def __call__(self) -> float:
"""
!!! failure "Deprecated"
`FPSMonitor.__call__` is deprecated and will be removed in
`supervision-0.22.0`. Use `FPSMonitor.fps` instead.
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.
"""
return self.fps
@property
def fps(self) -> float:
"""
+1 -2
View File
@@ -62,8 +62,7 @@ def validate_keypoint_confidence(confidence: Any, n: int, m: int) -> None:
if not is_valid:
raise ValueError(
f"confidence must be a 1D np.ndarray with shape {expected_shape}, but "
"got"
f"shape {actual_shape}"
f"got shape {actual_shape}"
)
+3 -3
View File
@@ -237,7 +237,7 @@ from supervision.detection.lmm import from_florence_2
DoesNotRaise(),
),
( # Region to Category: detected
{"<REGION_TO_CATEGORY>": "some object<loc_3><loc_4><loc_5><loc_6>"},
{"<REGION_TO_CATEGORY>": "some object<loc_300><loc_400><loc_500><loc_600>"},
(10, 10),
(
np.array([[3, 4, 5, 6]], dtype=np.float32),
@@ -254,11 +254,11 @@ from supervision.detection.lmm import from_florence_2
DoesNotRaise(),
),
( # Region to Description: detected
{"<REGION_TO_DESCRIPTION>": "some description<loc_3><loc_4><loc_5><loc_6>"},
{"<REGION_TO_DESCRIPTION>": "descr<loc_300><loc_400><loc_500><loc_600>"},
(10, 10),
(
np.array([[3, 4, 5, 6]], dtype=np.float32),
np.array(["some description"]),
np.array(["descr"]),
None,
None,
),
+48
View File
@@ -17,6 +17,8 @@ from supervision.detection.utils import (
move_boxes,
process_roboflow_result,
scale_boxes,
xcycwh_to_xyxy,
xywh_to_xyxy,
)
TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool)
@@ -1090,3 +1092,49 @@ def test_contains_multiple_segments(
with exception:
result = contains_multiple_segments(mask=mask, connectivity=connectivity)
assert result == expected_result
@pytest.mark.parametrize(
"xywh, expected_result",
[
(np.array([[10, 20, 30, 40]]), np.array([[10, 20, 40, 60]])), # standard case
(np.array([[0, 0, 0, 0]]), np.array([[0, 0, 0, 0]])), # zero size bounding box
(
np.array([[50, 50, 100, 100]]),
np.array([[50, 50, 150, 150]]),
), # large bounding box
(
np.array([[-10, -20, 30, 40]]),
np.array([[-10, -20, 20, 20]]),
), # negative coordinates
(np.array([[50, 50, 0, 30]]), np.array([[50, 50, 50, 80]])), # zero width
(np.array([[50, 50, 20, 0]]), np.array([[50, 50, 70, 50]])), # zero height
(np.array([]).reshape(0, 4), np.array([]).reshape(0, 4)), # empty array
],
)
def test_xywh_to_xyxy(xywh: np.ndarray, expected_result: np.ndarray) -> None:
result = xywh_to_xyxy(xywh)
np.testing.assert_array_equal(result, expected_result)
@pytest.mark.parametrize(
"xcycwh, expected_result",
[
(np.array([[50, 50, 20, 30]]), np.array([[40, 35, 60, 65]])), # standard case
(np.array([[0, 0, 0, 0]]), np.array([[0, 0, 0, 0]])), # zero size bounding box
(
np.array([[50, 50, 100, 100]]),
np.array([[0, 0, 100, 100]]),
), # large bounding box centered at (50, 50)
(
np.array([[-10, -10, 20, 30]]),
np.array([[-20, -25, 0, 5]]),
), # negative coordinates
(np.array([[50, 50, 0, 30]]), np.array([[50, 35, 50, 65]])), # zero width
(np.array([[50, 50, 20, 0]]), np.array([[40, 50, 60, 50]])), # zero height
(np.array([]).reshape(0, 4), np.array([]).reshape(0, 4)), # empty array
],
)
def test_xcycwh_to_xyxy(xcycwh: np.ndarray, expected_result: np.ndarray) -> None:
result = xcycwh_to_xyxy(xcycwh)
np.testing.assert_array_equal(result, expected_result)