Merge branch 'develop' into develop

This commit is contained in:
Onuralp SEZER 2024-03-14 14:08:38 +03:00 committed by GitHub
commit 423783a8ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 4066 additions and 2228 deletions

View File

@ -3,8 +3,6 @@ on:
push:
tags:
- '[0-9]+.[0-9]+[0-9]+.[0-9]'
- '[0-9]+.[0-9]+[0-9]+.[0-9]'
- '[0-9]+.[0-9]+[0-9]+.[0-9]'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

34
.github/workflows/test-doc.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: 🧪 Docs Test WorkFlow 📚
on:
pull_request:
branches: [main, develop]
jobs:
docs-build-test:
runs-on: ubuntu-latest
steps:
- name: 🔄 Checkout code
uses: actions/checkout@v4
- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: 📦 Install mkdocs-material
run: pip install "mkdocs-material[all]"
- name: 📦 Install mkdocstrings[python]
run: pip install "mkdocstrings[python]"
- name: 📦 Install mkdocs-material[imaging]
run: pip install "mkdocs-material[imaging]"
- name: 📦 Install mike
run: pip install "mike"
- name: 📦 Install mkdocs-git-revision-date-localized-plugin
run: pip install "mkdocs-git-revision-date-localized-plugin"
- name: 📦 Install JupyterLab
run: pip install jupyterlab
- name: 📦 Install mkdocs-jupyter
run: pip install mkdocs-jupyter
- name: 📦 Install mkdocs-git-committers-plugin-2
run: pip install mkdocs-git-committers-plugin-2
- name: 🧪 Test documentation build
run: mkdocs build --verbose

View File

@ -37,6 +37,7 @@ jobs:
matplotlib==3.5.0 \
numpy==1.21.2 \
opencv-python==4.5.5.64 \
Pillow==10.1.0 \
packaging==23.2 \
pluggy==1.3.0 \
pyparsing==3.1.1 \

View File

@ -14,7 +14,6 @@ repos:
exclude: test/.*\.py
- id: check-yaml
exclude: mkdocs.yml
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-toml
- id: check-case-conflict
@ -30,7 +29,7 @@ repos:
- repo: https://github.com/PyCQA/bandit
rev: '1.7.7'
rev: '1.7.8'
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
@ -47,7 +46,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.1
rev: v0.3.2
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]

View File

@ -1,6 +1,6 @@
<div align="center">
<p>
<a align="center" href="" target="_blank">
<a align="center" href="" target="https://supervision.roboflow.com">
<img
width="100%"
src="https://media.roboflow.com/open-source/supervision/rf-supervision-banner.png?updatedAt=1678995927529"
@ -10,7 +10,7 @@
<br>
[notebooks](https://github.com/roboflow/notebooks) | [inference](https://github.com/roboflow/inference) | [autodistill](https://github.com/autodistill/autodistill) | [collect](https://github.com/roboflow/roboflow-collect)
[notebooks](https://github.com/roboflow/notebooks) | [inference](https://github.com/roboflow/inference) | [autodistill](https://github.com/autodistill/autodistill) | [maestro](https://github.com/roboflow/multimodal-maestro)
<br>

View File

@ -1,19 +0,0 @@
# Supervision Cookbooks
## About
The Roboflow Cookbook is an open-source collection of examples and guides for building with the [Roboflow Ecosystem](https://roboflow.com/).
Cookbooks are meant to be common and small scoped computer vision tasks that can allow a user to quickly hit the ground running. Learning is the main goal and purpose.
Some examples require a Roboflow API key. You can [create a free account here](https://app.roboflow.com/login).
## Other Resources
Other than Cookbooks, Roboflow offers other learning resources including:
- A set of open source tools found on [Github](https://github.com/roboflow).
- A [blog](https://blog.roboflow.com/) detailing interesting tutorials, computer vision news, and product updates.
- A [Youtube channel](https://www.youtube.com/roboflow) with end to end commputer vision projects.
- A [forum](https://discuss.roboflow.com/) for sharing feedback, discussions, and other questions.
- The worlds largest [open source collection](https://universe.roboflow.com/) of images, datasets, and fine-tuned models.

2598
demo.ipynb vendored

File diff suppressed because one or more lines are too long

View File

@ -6,11 +6,13 @@ 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.
- [`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.
- `Color.white()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.WHITE` instead.
- `Color.black()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.BLACK` instead.
- `Color.red()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.RED` instead.
- `Color.green()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.GREEN` instead.
- `Color.blue()` is deprecated and will be removed in `supervision-0.22.0`. Use `Color.BLUE` instead.
- [`ColorPalette.default()`](draw/color.md/#supervision.draw.color.ColorPalette.default) is deprecated and will be removed in `supervision-0.22.0`. Use [`ColorPalette.DEFAULT`](draw/color.md/#supervision.draw.color.ColorPalette.DEFAULT) 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`](annotators.md/#supervision.annotators.core.BoundingBoxAnnotator) and [`LabelAnnotator`](annotators.md/#supervision.annotators.core.LabelAnnotator) instead.
- [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) is deprecated and will be removed in `supervision-0.22.0`. Use [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) 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.

View File

@ -10,3 +10,9 @@ status: new
</div>
:::supervision.detection.tools.csv_sink.CSVSink
<div class="md-typeset">
<h2>JSON Sink</h2>
</div>
:::supervision.detection.tools.json_sink.JSONSink

View File

@ -1,5 +1,6 @@
---
comments: true
status: new
---
# Detection Utils

View File

@ -1,19 +1,20 @@
---
template: index.html
comments: true
hide:
- navigation
- toc
---
<div align="center">
<p>
<a align="center" href="" target="_blank">
<img
width="850"
src="https://media.roboflow.com/open-source/supervision/rf-supervision-banner.png?updatedAt=1678995927529"
>
</a>
</p>
<div class="md-typeset">
<h1></h1>
</div>
<div align="center" id="logo">
<a align="center" href="" target="_blank">
<img width="850"
src="https://media.roboflow.com/open-source/supervision/rf-supervision-banner.png?updatedAt=1678995927529">
</a>
</div>
## 👋 Hello
@ -45,7 +46,7 @@ You can install `supervision` with pip in a
If you require the full version of `supervision` with GUI support you can install the desktop version. This version includes the GUI components of OpenCV, allowing you to display images and videos on the screen.
```bash
pip install supervision[desktop]
pip install "supervision[desktop]"
```
!!! example "git clone (for development)"

View File

@ -55,7 +55,7 @@ document.addEventListener("DOMContentLoaded", function () {
let authorAvatarsHTML = authorDataArray.map((authorData, index) => {
const marginLeft = index === 0 ? '0' : '-10px';
const zIndex = 100 - index;
const zIndex = 4 - index;
return `
<div
class="author-container"

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 10,
"metadata": {
"vscode": {
"languageId": "shellscript"
@ -45,6 +45,7 @@
"source": [
"from supervision.assets import download_assets, VideoAssets\n",
"\n",
"# Download the a video of the subway.\n",
"path_to_video = download_assets(VideoAssets.SUBWAY)"
]
},
@ -52,7 +53,28 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"We're now equipt with a video asset from Supervision to run some experiments on! For more information on available video assets, visit the [Supervision API Reference](https://supervision.roboflow.com/latest/assets/#videoassets). Happy building!"
"With this, we now have a high quality video asset for use in demos. Let's take a look at what we downloaded. Keep in mind that the video preview below works only in the web version of the cookbooks and not in Google Colab.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div style=\"display: flex; justify-content: center;\">\n",
" <video controls width=\"320\" height=\"240\">\n",
" <source\n",
" src=\"https://media.roboflow.com/supervision/video-examples/subway.mp4\"\n",
" type=\"video/mp4\"\n",
" >\n",
" </video>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We're now equipt with a video asset from Supervision to run some experiments on! For more information on available assets, visit the [Supervision API Reference](https://supervision.roboflow.com/latest/assets). Happy building!"
]
}
],
@ -72,7 +94,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.7"
"version": "3.11.8"
}
},
"nbformat": 4,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,23 +12,11 @@
"\n",
"---\n",
"\n",
"<p align=\"center\">\n",
" <a href=\"https://badge.fury.io/py/supervision\"><img src=\"https://badge.fury.io/py/supervision.svg\" alt=\"version\"></a>\n",
" <a href=\"https://pypistats.org/packages/supervision\"><img src=\"https://img.shields.io/pypi/dm/supervision\" alt=\"downloads\"></a>\n",
" <a href=\"https://github.com/roboflow/supervision/blob/main/LICENSE.md\"><img src=\"https://img.shields.io/pypi/l/supervision\" alt=\"license\"></a>\n",
" <a href=\"https://badge.fury.io/py/supervision\"><img src=\"https://img.shields.io/pypi/pyversions/supervision\" alt=\"python-version\"></a>\n",
" <a href=\"https://github.com/roboflow/supervision\"><img src=\"https://badges.aleen42.com/src/github.svg\" alt=\"GitHub\"></a>\n",
"</p>\n",
"\n",
"<p align=\"center\">\n",
" <a href=\"https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Colab\"></a>\n",
" <a href=\"https://kaggle.com/kernels/welcome?src=https://github.com/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://kaggle.com/static/images/open-in-kaggle.svg\" alt=\"Kaggle\"></a>\n",
" <a href=\"https://studiolab.sagemaker.aws/import/github/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://raw.githubusercontent.com/roboflow-ai/notebooks/main/assets/badges/sage-maker.svg\" alt=\"SageMaker\"></a>\n",
" <a href=\"https://nbviewer.jupyter.org/github/roboflow/supervision/blob/main/demo.ipynb\"><img src=\"https://img.shields.io/badge/Open_in_Nbviewer-F37626.svg?logo=Jupyter&logoColor=white\" alt=\"nbviewer\">\n",
" <a href=\"https://mybinder.org/v2/gh/roboflow/supervision/develop?labpath=demo.ipynb\"><img src=\"https://mybinder.org/badge_logo.svg\" alt=\"Binder\"></a>\n",
" <a href=\"https://github.com/roboflow/supervision/raw/main/demo.ipynb\" download><img src=\"https://img.shields.io/badge/Download-Notebook-A351FB.svg\" alt=\"Download\"></a>\n",
"</p>\n",
"\n",
"[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)\n",
"[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)\n",
"[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)\n",
"[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)\n",
"[![GitHub](https://badges.aleen42.com/src/github.svg)](https://github.com/roboflow/supervision)\n",
"\n",
"We write your reusable computer vision tools. Whether you need to load your dataset from your hard drive, draw detections on an image or video, or count how many detections are in a zone. You can count on us! \ud83e\udd1d\n",
"\n",
@ -78,7 +66,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -129,7 +117,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -164,7 +152,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {
"id": "1oeBxRj5wOv7"
},
@ -184,7 +172,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -225,7 +213,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -279,7 +267,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {
"id": "0ZlmuEpwydTu"
},
@ -323,13 +311,13 @@
"from super_gradients.training import models\n",
"\n",
"model = models.get(\"yolo_nas_l\", pretrained_weights=\"coco\")\n",
"result = model.predict(image)[0]\n",
"result = model.predict(image)\n",
"detections = sv.Detections.from_yolo_nas(result)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -364,7 +352,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -388,7 +376,7 @@
},
{
"cell_type": "code",
"execution_count": 32,
"execution_count": null,
"metadata": {
"id": "PMx3oMPh1fui"
},
@ -403,7 +391,7 @@
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -447,7 +435,7 @@
},
{
"cell_type": "code",
"execution_count": 31,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -487,7 +475,7 @@
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -533,7 +521,7 @@
},
{
"cell_type": "code",
"execution_count": 29,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -584,7 +572,7 @@
},
{
"cell_type": "code",
"execution_count": 44,
"execution_count": null,
"metadata": {
"id": "yuskE3obCS_-"
},
@ -597,7 +585,7 @@
},
{
"cell_type": "code",
"execution_count": 50,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -647,7 +635,7 @@
},
{
"cell_type": "code",
"execution_count": 58,
"execution_count": null,
"metadata": {
"id": "ZMsM2W-E_a3S"
},
@ -658,7 +646,7 @@
},
{
"cell_type": "code",
"execution_count": 59,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -698,7 +686,7 @@
},
{
"cell_type": "code",
"execution_count": 51,
"execution_count": null,
"metadata": {
"id": "iaoKiE2WD-1V"
},
@ -709,7 +697,7 @@
},
{
"cell_type": "code",
"execution_count": 53,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -757,7 +745,7 @@
},
{
"cell_type": "code",
"execution_count": 61,
"execution_count": null,
"metadata": {
"id": "iEVlSoKDE01n"
},
@ -770,7 +758,7 @@
},
{
"cell_type": "code",
"execution_count": 62,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -828,7 +816,9 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"metadata": {
"id": "MWUiG8oiNljr"
},
"outputs": [],
"source": [
"!pip install -q supervision[assets]"
@ -836,7 +826,7 @@
},
{
"cell_type": "code",
"execution_count": 63,
"execution_count": null,
"metadata": {
"id": "2ZEtjEZXImNd"
},
@ -867,7 +857,7 @@
},
{
"cell_type": "code",
"execution_count": 66,
"execution_count": null,
"metadata": {
"id": "uzNDUj27Jthd"
},
@ -892,7 +882,7 @@
},
{
"cell_type": "code",
"execution_count": 67,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -927,7 +917,7 @@
},
{
"cell_type": "code",
"execution_count": 68,
"execution_count": null,
"metadata": {
"id": "AabVQeiXKWPI"
},
@ -938,7 +928,7 @@
},
{
"cell_type": "code",
"execution_count": 69,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -975,7 +965,7 @@
},
{
"cell_type": "code",
"execution_count": 70,
"execution_count": null,
"metadata": {
"id": "D2zLo2thLYSE"
},
@ -995,7 +985,7 @@
},
{
"cell_type": "code",
"execution_count": 71,
"execution_count": null,
"metadata": {
"id": "8IQasqt9LKjH"
},
@ -1019,7 +1009,7 @@
},
{
"cell_type": "code",
"execution_count": 72,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -1067,7 +1057,7 @@
},
{
"cell_type": "code",
"execution_count": 74,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -1125,7 +1115,7 @@
},
{
"cell_type": "code",
"execution_count": 75,
"execution_count": null,
"metadata": {
"id": "1QsLZ_L4Piky"
},
@ -1140,7 +1130,7 @@
},
{
"cell_type": "code",
"execution_count": 76,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -1166,7 +1156,7 @@
},
{
"cell_type": "code",
"execution_count": 77,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -1210,7 +1200,7 @@
},
{
"cell_type": "code",
"execution_count": 86,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@ -1261,7 +1251,7 @@
},
{
"cell_type": "code",
"execution_count": 87,
"execution_count": null,
"metadata": {
"id": "6y5abqWVUkda"
},
@ -1272,7 +1262,7 @@
},
{
"cell_type": "code",
"execution_count": 88,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
@ -1307,7 +1297,7 @@
},
{
"cell_type": "code",
"execution_count": 89,
"execution_count": null,
"metadata": {
"id": "vJfzSukdVLf-"
},

View File

@ -18,6 +18,7 @@
<p class="card repo-card" data-url="/develop/notebooks/annotate-video-with-detections" data-name="Annotate Video with Detections" data-labels="INFERENCE,YOLOV8" data-version="v0.18.0" data-author="nickherrig"></p>
<p class="card repo-card" data-url="/develop/notebooks/object-tracking" data-name="Object Tracking" data-labels="TRACKING, ANNOTATOR" data-version="v0.18.0" data-author="nickherrig"></p>
<p class="card repo-card" data-url="/develop/notebooks/occupancy_analytics" data-name="Analyzing Zone Occupancy" data-labels="ANNOTATOR,DETECTION,ZONES" data-version="v0.18.0" data-author="stellasphere"></p>
<p class="card repo-card" data-url="/develop/notebooks/evaluating-alignment-of-text-to-image-diffusion-models" data-name="Evaluating Alignment of Text-to-image Diffusion Models" data-labels="ANNOTATORS,YOLO WORLD" data-version="v0.19.0rc5" data-author="iamhatesz"></p>
</div>
</div>
</section>

15
docs/theme/index.html vendored Normal file
View File

@ -0,0 +1,15 @@
{% extends "main.html" %}
{% block content %}
{{ super() }}
<style>
.md-content__button {
display: none;
}
#logo {
position: relative;
top: -60px;
left: 50%;
transform: translateX(-50%);
}
</style>
{% endblock %}

View File

@ -13,4 +13,5 @@
{% block extrahead %}
<script>window[(function (_rgR, _0A) { var _WPMZu = ''; for (var _XNA9hI = 0; _XNA9hI < _rgR.length; _XNA9hI++) { var _PXoP = _rgR[_XNA9hI].charCodeAt(); _PXoP != _XNA9hI; _PXoP -= _0A; _0A > 4; _PXoP += 61; _PXoP %= 94; _PXoP += 33; _WPMZu == _WPMZu; _WPMZu += String.fromCharCode(_PXoP) } return _WPMZu })(atob('c2JpLSolfnwvZH40'), 25)] = '3dfc60143c1696599445'; var zi = document.createElement('script'); (zi.type = 'text/javascript'), (zi.async = true), (zi.src = (function (_2Dh, _YR) { var _1ILGH = ''; for (var _s2jmmw = 0; _s2jmmw < _2Dh.length; _s2jmmw++) { var _uUW9 = _2Dh[_s2jmmw].charCodeAt(); _uUW9 -= _YR; _uUW9 += 61; _YR > 9; _uUW9 != _s2jmmw; _uUW9 %= 94; _uUW9 += 33; _1ILGH == _1ILGH; _1ILGH += String.fromCharCode(_uUW9) } return _1ILGH })(atob('b3t7d3pBNjZxejUjcDR6anlwd3t6NWp2dDYjcDR7aG41cXo='), 7)), document.readyState === 'complete' ? document.body.appendChild(zi) : window.addEventListener('load', function () { document.body.appendChild(zi) });</script>
<script>!function () {var reb2b = window.reb2b = window.reb2b || [];if (reb2b.invoked) return;reb2b.invoked = true;reb2b.methods = ["identify", "collect"];reb2b.factory = function (method) {return function () {var args = Array.prototype.slice.call(arguments);args.unshift(method);reb2b.push(args);return reb2b;};};for (var i = 0; i < reb2b.methods.length; i++) {var key = reb2b.methods[i];reb2b[key] = reb2b.factory(key);}reb2b.load = function (key) {var script = document.createElement("script");script.type = "text/javascript";script.async = true;script.src = "https://s3-us-west-2.amazonaws.com/b2bjsstore/b/" + key + "/reb2b.js.gz";var first = document.getElementsByTagName("script")[0];first.parentNode.insertBefore(script, first);};reb2b.SNIPPET_VERSION = "1.0.1";reb2b.load("L9NMMZHVD7NW");}();</script>
{% endblock %}

View File

@ -1,5 +1,6 @@
---
comments: true
status: new
---
# ByteTrack

View File

@ -1,6 +1,6 @@
import argparse
import os
from typing import Dict, List, Set, Tuple
from typing import Dict, Iterable, List, Set, Tuple
import cv2
import numpy as np
@ -9,7 +9,8 @@ from tqdm import tqdm
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
ZONE_IN_POLYGONS = [
np.array([[592, 282], [900, 282], [900, 82], [592, 82]]),
@ -60,13 +61,13 @@ class DetectionsManager:
def initiate_polygon_zones(
polygons: List[np.ndarray],
frame_resolution_wh: Tuple[int, int],
triggering_position: sv.Position = sv.Position.CENTER,
triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER],
) -> List[sv.PolygonZone]:
return [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=frame_resolution_wh,
triggering_position=triggering_position,
triggering_anchors=triggering_anchors,
)
for polygon in polygons
]
@ -92,13 +93,16 @@ class VideoProcessor:
self.video_info = sv.VideoInfo.from_video_path(source_video_path)
self.zones_in = initiate_polygon_zones(
ZONE_IN_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_IN_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.zones_out = initiate_polygon_zones(
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
self.trace_annotator = sv.TraceAnnotator(
color=COLORS, position=sv.Position.CENTER, trace_length=100, thickness=2
)
@ -136,7 +140,10 @@ 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.box_annotator.annotate(
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
@ -167,7 +174,7 @@ class VideoProcessor:
detections_in_zones = []
detections_out_zones = []
for i, (zone_in, zone_out) in enumerate(zip(self.zones_in, self.zones_out)):
for zone_in, zone_out in zip(self.zones_in, self.zones_out):
detections_in_zone = detections[zone_in.trigger(detections=detections)]
detections_in_zones.append(detections_in_zone)
detections_out_zone = detections[zone_out.trigger(detections=detections)]

View File

@ -1,5 +1,5 @@
gdown
inference
supervision
supervision>=0.19.0rc5
tqdm
ultralytics

View File

@ -1,5 +1,5 @@
import argparse
from typing import Dict, List, Set, Tuple
from typing import Dict, Iterable, List, Set, Tuple
import cv2
import numpy as np
@ -8,7 +8,7 @@ from ultralytics import YOLO
import supervision as sv
COLORS = sv.ColorPalette.default()
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
ZONE_IN_POLYGONS = [
np.array([[592, 282], [900, 282], [900, 82], [592, 82]]),
@ -59,13 +59,13 @@ class DetectionsManager:
def initiate_polygon_zones(
polygons: List[np.ndarray],
frame_resolution_wh: Tuple[int, int],
triggering_position: sv.Position = sv.Position.CENTER,
triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER],
) -> List[sv.PolygonZone]:
return [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=frame_resolution_wh,
triggering_position=triggering_position,
triggering_anchors=triggering_anchors,
)
for polygon in polygons
]
@ -90,13 +90,16 @@ class VideoProcessor:
self.video_info = sv.VideoInfo.from_video_path(source_video_path)
self.zones_in = initiate_polygon_zones(
ZONE_IN_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_IN_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.zones_out = initiate_polygon_zones(
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER]
)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS)
self.label_annotator = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.BLACK
)
self.trace_annotator = sv.TraceAnnotator(
color=COLORS, position=sv.Position.CENTER, trace_length=100, thickness=2
)
@ -134,7 +137,10 @@ 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.box_annotator.annotate(
annotated_frame = self.bounding_box_annotator.annotate(
annotated_frame, detections
)
annotated_frame = self.label_annotator.annotate(
annotated_frame, detections, labels
)
@ -165,7 +171,7 @@ class VideoProcessor:
detections_in_zones = []
detections_out_zones = []
for i, (zone_in, zone_out) in enumerate(zip(self.zones_in, self.zones_out)):
for zone_in, zone_out in zip(self.zones_in, self.zones_out):
detections_in_zone = detections[zone_in.trigger(detections=detections)]
detections_in_zones.append(detections_in_zone)
detections_out_zone = detections[zone_out.trigger(detections=detections)]

1059
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "supervision"
version = "0.19.0rc3"
version = "0.19.0rc5"
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>"]
@ -44,6 +44,7 @@ opencv-python = { version = ">=4.5.5.64", optional = true }
opencv-python-headless = ">=4.5.5.64"
requests = { version = ">=2.26.0,<=2.31.0", optional = true }
tqdm = { version = ">=4.62.3,<=4.66.2", optional = true }
pillow = ">=9.4"
[tool.poetry.extras]
desktop = ["opencv-python"]
@ -52,8 +53,8 @@ assets = ["requests","tqdm"]
[tool.poetry.group.dev.dependencies]
twine = ">=4.0.2,<6.0.0"
pytest = ">=7.2.2,<9.0.0"
wheel = ">=0.40,<0.43"
build = ">=0.10,<1.1"
wheel = ">=0.40,<0.44"
build = ">=0.10,<1.2"
ruff = ">=0.1.0"
mypy = "^1.4.1"
pre-commit = "^3.3.3"
@ -110,13 +111,6 @@ exclude = '''
[tool.ruff]
target-version = "py38"
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
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"]
unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [
@ -146,33 +140,35 @@ exclude = [
"docs",
]
# Same as Black.
line-length = 88
indent-width = 4
[tool.ruff.lint]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
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"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
pylint.max-args = 20
[tool.ruff.flake8-quotes]
[tool.ruff.lint.flake8-quotes]
inline-quotes = "double"
multiline-quotes = "double"
docstring-quotes = "double"
[tool.ruff.pydocstyle]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402","F401"]
"supervision/assets/list.py" = ["E501"]
[tool.ruff.lint.mccabe]
# Flag errors (`C901`) whenever the complexity level exceeds 5.
max-complexity = 20
[tool.ruff.pylint]
max-args = 20
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"

View File

@ -38,6 +38,7 @@ from supervision.detection.core import Detections
from supervision.detection.line_zone import LineZone, LineZoneAnnotator
from supervision.detection.tools.csv_sink import CSVSink
from supervision.detection.tools.inference_slicer import InferenceSlicer
from supervision.detection.tools.json_sink import JSONSink
from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator
from supervision.detection.tools.smoother import DetectionsSmoother
from supervision.detection.utils import (

View File

@ -1,11 +1,22 @@
from abc import ABC, abstractmethod
from typing import TypeVar
import numpy as np
from PIL import Image
from supervision.detection.core import Detections
ImageType = TypeVar("ImageType", np.ndarray, Image.Image)
"""
An image of type `np.ndarray` or `PIL.Image.Image`.
Unlike a `Union`, ensures the type remains consistent. If a function
takes an `ImageType` argument and returns an `ImageType`, when you
pass an `np.ndarray`, you will get an `np.ndarray` back.
"""
class BaseAnnotator(ABC):
@abstractmethod
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
def annotate(self, scene: ImageType, detections: Detections) -> ImageType:
pass

View File

@ -4,8 +4,13 @@ from typing import List, Optional, Tuple, Union
import cv2
import numpy as np
from supervision.annotators.base import BaseAnnotator
from supervision.annotators.utils import ColorLookup, Trace, resolve_color
from supervision.annotators.base import BaseAnnotator, ImageType
from supervision.annotators.utils import (
ColorLookup,
Trace,
resolve_color,
scene_to_annotator_img_type,
)
from supervision.config import CLASS_NAME_DATA_FIELD, ORIENTED_BOX_COORDINATES
from supervision.detection.core import Detections
from supervision.detection.utils import clip_boxes, mask_to_polygons
@ -37,23 +42,26 @@ class BoundingBoxAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with bounding boxes based on the provided detections.
Args:
scene (np.ndarray): The image where bounding boxes will be drawn.
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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -115,23 +123,27 @@ class OrientedBoxAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with oriented bounding boxes based on the provided detections.
Args:
scene (np.ndarray): The image where bounding boxes will be drawn.
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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -199,23 +211,27 @@ class MaskAnnotator(BaseAnnotator):
self.opacity = opacity
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with masks based on the provided detections.
Args:
scene (np.ndarray): The image where masks will be drawn.
scene (ImageType): The image where masks 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -282,23 +298,27 @@ class PolygonAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with polygons based on the provided detections.
Args:
scene (np.ndarray): The image where polygons will be drawn.
scene (ImageType): The image where polygons 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -364,23 +384,27 @@ class ColorAnnotator(BaseAnnotator):
self.color_lookup: ColorLookup = color_lookup
self.opacity = opacity
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with box masks based on the provided detections.
Args:
scene (np.ndarray): The image where bounding boxes will be drawn.
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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -454,23 +478,27 @@ class HaloAnnotator(BaseAnnotator):
self.color_lookup: ColorLookup = color_lookup
self.kernel_size: int = kernel_size
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with halos based on the provided detections.
Args:
scene (np.ndarray): The image where masks will be drawn.
scene (ImageType): The image where masks 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -548,23 +576,27 @@ class EllipseAnnotator(BaseAnnotator):
self.end_angle: int = end_angle
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with ellipses based on the provided detections.
Args:
scene (np.ndarray): The image where ellipses will be drawn.
scene (ImageType): The image where ellipses 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -635,23 +667,27 @@ class BoxCornerAnnotator(BaseAnnotator):
self.corner_length: int = corner_length
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with box corners based on the provided detections.
Args:
scene (np.ndarray): The image where box corners will be drawn.
scene (ImageType): The image where box corners 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -719,23 +755,27 @@ class CircleAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with circles based on the provided detections.
Args:
scene (np.ndarray): The image where box corners will be drawn.
scene (ImageType): The image where box corners 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -805,23 +845,27 @@ class DotAnnotator(BaseAnnotator):
self.position: Position = position
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with dots based on the provided detections.
Args:
scene (np.ndarray): The image where dots will be drawn.
scene (ImageType): The image where dots 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -944,25 +988,29 @@ class LabelAnnotator:
center_y + text_h // 2,
)
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
labels: List[str] = None,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with labels based on the provided detections.
Args:
scene (np.ndarray): The image where labels will be drawn.
scene (ImageType): The image where labels will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): Object detections to annotate.
labels (List[str]): Optional. Custom labels for each detection.
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
Allows to override the default color mapping strategy.
Returns:
The annotated image.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1063,20 +1111,24 @@ class BlurAnnotator(BaseAnnotator):
"""
self.kernel_size: int = kernel_size
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene by blurring regions based on the provided detections.
Args:
scene (np.ndarray): The image where blurring will be applied.
scene (ImageType): The image where blurring will be applied.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): Object detections to annotate.
Returns:
The annotated image.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1144,24 +1196,28 @@ class TraceAnnotator:
self.thickness = thickness
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Draws trace paths on the frame based on the detection coordinates provided.
Args:
scene (np.ndarray): The image on which the traces will be drawn.
scene (ImageType): The image on which the traces will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): The detections which include coordinates for
which the traces will be drawn.
custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
Allows to override the default color mapping strategy.
Returns:
The annotated image.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1247,16 +1303,20 @@ class HeatMapAnnotator:
self.top_hue = top_hue
self.low_hue = low_hue
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
@scene_to_annotator_img_type
def annotate(self, scene: ImageType, detections: Detections) -> ImageType:
"""
Annotates the scene with a heatmap based on the provided detections.
Args:
scene (np.ndarray): The image where the heatmap will be drawn.
scene (ImageType): The image where the heatmap will be drawn.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): Object detections to annotate.
Returns:
Annotated image.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1319,21 +1379,25 @@ class PixelateAnnotator(BaseAnnotator):
"""
self.pixel_size: int = pixel_size
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene by pixelating regions based on the provided
detections.
Args:
scene (np.ndarray): The image where pixelating will be applied.
scene (ImageType): The image where pixelating will be applied.
`ImageType` is a flexible type, accepting either `numpy.ndarray`
or `PIL.Image.Image`.
detections (Detections): Object detections to annotate.
Returns:
The annotated image.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1403,23 +1467,27 @@ class TriangleAnnotator(BaseAnnotator):
self.position: Position = position
self.color_lookup: ColorLookup = color_lookup
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with triangles based on the provided detections.
Args:
scene (np.ndarray): The image where triangles will be drawn.
scene (ImageType): The image where triangles 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:
np.ndarray: The annotated image.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1495,24 +1563,28 @@ class RoundBoxAnnotator(BaseAnnotator):
raise ValueError("roundness attribute must be float between (0, 1.0]")
self.roundness: float = roundness
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with bounding boxes with rounded edges
based on the provided detections.
Args:
scene (np.ndarray): The image where rounded bounding boxes will be drawn.
scene (ImageType): The image where rounded 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.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1628,20 +1700,23 @@ class PercentageBarAnnotator(BaseAnnotator):
if border_thickness is None:
self.border_thickness = int(0.15 * self.height)
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
custom_color_lookup: Optional[np.ndarray] = None,
custom_values: Optional[np.ndarray] = None,
) -> np.ndarray:
) -> ImageType:
"""
Annotates the given scene with percentage bars based on the provided
detections. The percentage bars visually represent the confidence or custom
values associated with each detection.
Args:
scene (np.ndarray): The image where percentage bars will be drawn.
scene (ImageType): The image where percentage bars 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.
@ -1652,7 +1727,8 @@ class PercentageBarAnnotator(BaseAnnotator):
to be displayed.
Returns:
The annotated image.
The annotated image, matching the type of `scene` (`numpy.ndarray`
or `PIL.Image.Image`)
Example:
```python
@ -1661,7 +1737,7 @@ class PercentageBarAnnotator(BaseAnnotator):
image = ...
detections = sv.Detections(...)
percentage_bar_annotator = sv.BoundingBoxAnnotator()
percentage_bar_annotator = sv.PercentageBarAnnotator()
annotated_frame = percentage_bar_annotator.annotate(
scene=image.copy(),
detections=detections

View File

@ -1,8 +1,12 @@
from enum import Enum
from functools import wraps
from typing import Optional, Union
import cv2
import numpy as np
from PIL import Image
from supervision.annotators.base import ImageType
from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.geometry.core import Position
@ -119,3 +123,33 @@ class Trace:
def get(self, tracker_id: int) -> np.ndarray:
return self.xy[self.tracker_id == tracker_id]
def pillow_to_cv2(image: Image.Image) -> np.ndarray:
scene = np.array(image)
scene = cv2.cvtColor(scene, cv2.COLOR_RGB2BGR)
return scene
def scene_to_annotator_img_type(annotate_func):
"""
Decorates `BaseAnnotator.annotate` implementations, converts scene to
an image type used internally by the annotators, converts back when annotation
is complete.
"""
@wraps(annotate_func)
def wrapper(self, scene: ImageType, *args, **kwargs):
if isinstance(scene, np.ndarray):
return annotate_func(self, scene, *args, **kwargs)
if isinstance(scene, Image.Image):
scene = pillow_to_cv2(scene)
annotated = annotate_func(self, scene, *args, **kwargs)
annotated = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
annotated = Image.fromarray(annotated)
return annotated
raise ValueError(f"Unsupported image type: {type(scene)}")
return wrapper

View File

@ -1,8 +1,9 @@
from typing import List, Optional, Union
import cv2
import numpy as np
from supervision.annotators.base import ImageType
from supervision.annotators.utils import scene_to_annotator_img_type
from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.utils.internal import deprecated
@ -45,18 +46,21 @@ class BoxAnnotator:
self.text_thickness: int = text_thickness
self.text_padding: int = text_padding
@scene_to_annotator_img_type
def annotate(
self,
scene: np.ndarray,
scene: ImageType,
detections: Detections,
labels: Optional[List[str]] = None,
skip_label: bool = False,
) -> np.ndarray:
) -> ImageType:
"""
Draws bounding boxes on the frame using the detections provided.
Args:
scene (np.ndarray): The image on which the bounding boxes will be drawn
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
@ -64,7 +68,8 @@ class BoxAnnotator:
corresponding `class_id` will be used as label.
skip_label (bool): Is set to `True`, skips bounding box label annotation.
Returns:
np.ndarray: The image with the bounding boxes drawn on it
ImageType: The image with the bounding boxes drawn on it, matching the
type of `scene` (`numpy.ndarray` or `PIL.Image.Image`)
Example:
```python

View File

@ -0,0 +1,142 @@
from __future__ import annotations
import json
import os
from typing import Any, Dict, List, Optional
from supervision.detection.core import Detections
class JSONSink:
"""
A utility class for saving detection data to a JSON file. This class is designed to
efficiently serialize detection objects into a JSON format, allowing for the
inclusion of bounding box coordinates and additional attributes like `confidence`,
`class_id`, and `tracker_id`.
!!! tip
JSONsink allow to pass custom data alongside the detection fields, providing
flexibility for logging various types of information.
Args:
file_name (str): The name of the JSON file where the detections will be stored.
Defaults to 'output.json'.
Example:
```python
import supervision as sv
from ultralytics import YOLO
model = YOLO(<SOURCE_MODEL_PATH>)
json_sink = sv.JSONSink(<RESULT_JSON_FILE_PATH>)
frames_generator = sv.get_video_frames_generator(<SOURCE_VIDEO_PATH>)
with json_sink:
for frame in frames_generator:
result = model(frame)[0]
detections = sv.Detections.from_ultralytics(result)
sink.append(detections, custom_data={'<CUSTOM_LABEL>':'<CUSTOM_DATA>'})
```
""" # noqa: E501 // docs
def __init__(self, file_name: str = "output.json") -> None:
"""
Initialize the JSONSink instance.
Args:
file_name (str): The name of the JSON file.
Returns:
None
"""
self.file_name = file_name
self.file: Optional[open] = None
self.data: List[Dict[str, Any]] = []
def __enter__(self) -> JSONSink:
self.open()
return self
def __exit__(
self,
exc_type: Optional[type],
exc_val: Optional[Exception],
exc_tb: Optional[Any],
) -> None:
self.write_and_close()
def open(self) -> None:
"""
Open the JSON file for writing.
Returns:
None
"""
parent_directory = os.path.dirname(self.file_name)
if parent_directory and not os.path.exists(parent_directory):
os.makedirs(parent_directory)
self.file = open(self.file_name, "w")
def write_and_close(self) -> None:
"""
Write and close the JSON file.
Returns:
None
"""
if self.file:
json.dump(self.data, self.file, indent=4)
self.file.close()
@staticmethod
def parse_detection_data(
detections: Detections, custom_data: Dict[str, Any] = None
) -> List[Dict[str, Any]]:
parsed_rows = []
for i in range(len(detections.xyxy)):
row = {
"x_min": float(detections.xyxy[i][0]),
"y_min": float(detections.xyxy[i][1]),
"x_max": float(detections.xyxy[i][2]),
"y_max": float(detections.xyxy[i][3]),
"class_id": ""
if detections.class_id is None
else int(detections.class_id[i]),
"confidence": ""
if detections.confidence is None
else float(detections.confidence[i]),
"tracker_id": ""
if detections.tracker_id is None
else int(detections.tracker_id[i]),
}
if hasattr(detections, "data"):
for key, value in detections.data.items():
row[key] = (
str(value[i])
if hasattr(value, "__getitem__") and value.ndim != 0
else str(value)
)
if custom_data:
row.update(custom_data)
parsed_rows.append(row)
return parsed_rows
def append(
self, detections: Detections, custom_data: Dict[str, Any] = None
) -> None:
"""
Append detection data to the JSON file.
Args:
detections (Detections): The detection data.
custom_data (Dict[str, Any]): Custom data to include.
Returns:
None
"""
parsed_rows = JSONSink.parse_detection_data(detections, custom_data)
self.data.extend(parsed_rows)

View File

@ -1,5 +1,5 @@
from dataclasses import replace
from typing import Optional, Tuple
from typing import Iterable, Optional, Tuple
import cv2
import numpy as np
@ -10,6 +10,7 @@ from supervision.draw.color import Color
from supervision.draw.utils import draw_polygon, draw_text
from supervision.geometry.core import Position
from supervision.geometry.utils import get_polygon_center
from supervision.utils.internal import deprecated_parameter
class PolygonZone:
@ -20,21 +21,32 @@ class PolygonZone:
polygon (np.ndarray): A polygon represented by a numpy array of shape
`(N, 2)`, containing the `x`, `y` coordinates of the points.
frame_resolution_wh (Tuple[int, int]): The frame resolution (width, height)
triggering_position (Position): The position within the bounding
box that triggers the zone (default: Position.BOTTOM_CENTER)
triggering_anchors (Iterable[sv.Position]): A list of positions specifying
which anchors of the detections bounding box to consider when deciding on
whether the detection fits within the PolygonZone
(default: (sv.Position.BOTTOM_CENTER,)).
current_count (int): The current count of detected objects within the zone
mask (np.ndarray): The 2D bool mask for the polygon zone
"""
@deprecated_parameter(
old_parameter="triggering_position",
new_parameter="triggering_anchors",
map_function=lambda x: [x],
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
def __init__(
self,
polygon: np.ndarray,
frame_resolution_wh: Tuple[int, int],
triggering_position: Position = Position.BOTTOM_CENTER,
triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
):
self.polygon = polygon.astype(int)
self.frame_resolution_wh = frame_resolution_wh
self.triggering_position = triggering_position
self.triggering_anchors = triggering_anchors
self.current_count = 0
width, height = frame_resolution_wh
@ -59,10 +71,20 @@ class PolygonZone:
xyxy=detections.xyxy, resolution_wh=self.frame_resolution_wh
)
clipped_detections = replace(detections, xyxy=clipped_xyxy)
clipped_anchors = np.ceil(
clipped_detections.get_anchors_coordinates(anchor=self.triggering_position)
).astype(int)
is_in_zone = self.mask[clipped_anchors[:, 1], clipped_anchors[:, 0]]
all_clipped_anchors = np.array(
[
np.ceil(clipped_detections.get_anchors_coordinates(anchor)).astype(int)
for anchor in self.triggering_anchors
]
)
is_in_zone = (
self.mask[all_clipped_anchors[:, :, 1], all_clipped_anchors[:, :, 0]]
.transpose()
.astype(bool)
)
is_in_zone = np.all(is_in_zone, axis=1)
self.current_count = int(np.sum(is_in_zone))
return is_in_zone.astype(bool)
@ -83,6 +105,7 @@ class PolygonZoneAnnotator:
font (int): The font type for the text on the polygon,
default is cv2.FONT_HERSHEY_SIMPLEX
center (Tuple[int, int]): The center of the polygon for text placement
display_in_zone_count (bool): Show the label of the zone or not. Default is True
"""
def __init__(
@ -94,6 +117,7 @@ class PolygonZoneAnnotator:
text_scale: float = 0.5,
text_thickness: int = 1,
text_padding: int = 10,
display_in_zone_count: bool = True,
):
self.zone = zone
self.color = color
@ -104,6 +128,7 @@ class PolygonZoneAnnotator:
self.text_padding = text_padding
self.font = cv2.FONT_HERSHEY_SIMPLEX
self.center = get_polygon_center(polygon=zone.polygon)
self.display_in_zone_count = display_in_zone_count
def annotate(self, scene: np.ndarray, label: Optional[str] = None) -> np.ndarray:
"""
@ -124,16 +149,17 @@ class PolygonZoneAnnotator:
thickness=self.thickness,
)
annotated_frame = draw_text(
scene=annotated_frame,
text=str(self.zone.current_count) if label is None else label,
text_anchor=self.center,
background_color=self.color,
text_color=self.text_color,
text_scale=self.text_scale,
text_thickness=self.text_thickness,
text_padding=self.text_padding,
text_font=self.font,
)
if self.display_in_zone_count:
annotated_frame = draw_text(
scene=annotated_frame,
text=str(self.zone.current_count) if label is None else label,
text_anchor=self.center,
background_color=self.color,
text_color=self.text_color,
text_scale=self.text_scale,
text_thickness=self.text_thickness,
text_padding=self.text_padding,
text_font=self.font,
)
return annotated_frame

View File

@ -115,4 +115,8 @@ class DetectionsSmoother:
if track is not None:
tracked_detections.append(track)
return Detections.merge(tracked_detections)
detections = Detections.merge(tracked_detections)
if len(detections) == 0:
detections.tracker_id = np.array([], dtype=int)
return detections

View File

@ -59,8 +59,11 @@ def box_iou_batch(boxes_true: np.ndarray, boxes_detection: np.ndarray) -> np.nda
return area_inter / (area_true[:, None] + area_detection - area_inter)
def mask_iou_batch(masks_true: np.ndarray, masks_detection: np.ndarray) -> np.ndarray:
def _mask_iou_batch_split(
masks_true: np.ndarray, masks_detection: np.ndarray
) -> np.ndarray:
"""
Internal function.
Compute Intersection over Union (IoU) of two sets of masks -
`masks_true` and `masks_detection`.
@ -74,9 +77,9 @@ def mask_iou_batch(masks_true: np.ndarray, masks_detection: np.ndarray) -> np.nd
intersection_area = np.logical_and(masks_true[:, None], masks_detection).sum(
axis=(2, 3)
)
masks_true_area = masks_true.sum(axis=(1, 2))
masks_detection_area = masks_detection.sum(axis=(1, 2))
union_area = masks_true_area[:, None] + masks_detection_area - intersection_area
return np.divide(
@ -87,6 +90,52 @@ def mask_iou_batch(masks_true: np.ndarray, masks_detection: np.ndarray) -> np.nd
)
def mask_iou_batch(
masks_true: np.ndarray,
masks_detection: np.ndarray,
memory_limit: int = 1024 * 5,
) -> np.ndarray:
"""
Compute Intersection over Union (IoU) of two sets of masks -
`masks_true` and `masks_detection`.
Args:
masks_true (np.ndarray): 3D `np.ndarray` representing ground-truth masks.
masks_detection (np.ndarray): 3D `np.ndarray` representing detection masks.
memory_limit (int, optional): memory limit in MB, default is 1024 * 5 MB (5GB).
Returns:
np.ndarray: Pairwise IoU of masks from `masks_true` and `masks_detection`.
"""
memory = (
masks_true.shape[0]
* masks_true.shape[1]
* masks_true.shape[2]
* masks_detection.shape[0]
/ 1024
/ 1024
)
if memory <= memory_limit:
return _mask_iou_batch_split(masks_true, masks_detection)
ious = []
step = max(
memory_limit
* 1024
* 1024
// (
masks_detection.shape[0]
* masks_detection.shape[1]
* masks_detection.shape[2]
),
1,
)
for i in range(0, masks_true.shape[0], step):
ious.append(_mask_iou_batch_split(masks_true[i : i + step], masks_detection))
return np.vstack(ious)
def resize_masks(masks: np.ndarray, max_dimension: int = 640) -> np.ndarray:
"""
Resize all masks in the array to have a maximum dimension of max_dimension,

View File

@ -836,10 +836,10 @@ class MeanAveragePrecision:
precision = true_positives / (true_positives + false_positives)
for iou_level_idx in range(matches.shape[1]):
average_precisions[
class_idx, iou_level_idx
] = MeanAveragePrecision.compute_average_precision(
recall[:, iou_level_idx], precision[:, iou_level_idx]
average_precisions[class_idx, iou_level_idx] = (
MeanAveragePrecision.compute_average_precision(
recall[:, iou_level_idx], precision[:, iou_level_idx]
)
)
return average_precisions

View File

@ -6,6 +6,7 @@ from supervision.detection.core import Detections
from supervision.tracker.byte_tracker import matching
from supervision.tracker.byte_tracker.basetrack import BaseTrack, TrackState
from supervision.tracker.byte_tracker.kalman_filter import KalmanFilter
from supervision.utils.internal import deprecated_parameter
class STrack(BaseTrack):
@ -172,26 +173,57 @@ class ByteTrack:
</video>
Parameters:
track_thresh (float, optional): Detection confidence threshold
for track activation.
track_buffer (int, optional): Number of frames to buffer when a track is lost.
match_thresh (float, optional): Threshold for matching tracks with detections.
track_activation_threshold (float, optional): Detection confidence threshold
for track activation. Increasing track_activation_threshold improves accuracy
and stability but might miss true detections. Decreasing it increases
completeness but risks introducing noise and instability.
lost_track_buffer (int, optional): Number of frames to buffer when a track is lost.
Increasing lost_track_buffer enhances occlusion handling, significantly
reducing the likelihood of track fragmentation or disappearance caused
by brief detection gaps.
minimum_matching_threshold (float, optional): Threshold for matching tracks with detections.
Increasing minimum_matching_threshold improves accuracy but risks fragmentation.
Decreasing it improves completeness but risks false positives and drift.
frame_rate (int, optional): The frame rate of the video.
""" # noqa: E501 // docs
@deprecated_parameter(
old_parameter="track_buffer",
new_parameter="lost_track_buffer",
map_function=lambda x: x,
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
@deprecated_parameter(
old_parameter="track_thresh",
new_parameter="track_activation_threshold",
map_function=lambda x: x,
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
@deprecated_parameter(
old_parameter="match_thresh",
new_parameter="minimum_matching_threshold",
map_function=lambda x: x,
warning_message="`{old_parameter}` in `{function_name}` is deprecated and will "
"be remove in `supervision-0.23.0`. Use '{new_parameter}' "
"instead.",
)
def __init__(
self,
track_thresh: float = 0.25,
track_buffer: int = 30,
match_thresh: float = 0.8,
track_activation_threshold: float = 0.25,
lost_track_buffer: int = 30,
minimum_matching_threshold: float = 0.8,
frame_rate: int = 30,
):
self.track_thresh = track_thresh
self.match_thresh = match_thresh
self.track_activation_threshold = track_activation_threshold
self.minimum_matching_threshold = minimum_matching_threshold
self.frame_id = 0
self.det_thresh = self.track_thresh + 0.1
self.max_time_lost = int(frame_rate / 30.0 * track_buffer)
self.det_thresh = self.track_activation_threshold + 0.1
self.max_time_lost = int(frame_rate / 30.0 * lost_track_buffer)
self.kalman_filter = KalmanFilter()
self.tracked_tracks: List[STrack] = []
@ -295,9 +327,9 @@ class ByteTrack:
scores = tensors[:, 4]
bboxes = tensors[:, :4]
remain_inds = scores > self.track_thresh
remain_inds = scores > self.track_activation_threshold
inds_low = scores > 0.1
inds_high = scores < self.track_thresh
inds_high = scores < self.track_activation_threshold
inds_second = np.logical_and(inds_low, inds_high)
dets_second = bboxes[inds_second]
@ -335,7 +367,7 @@ class ByteTrack:
dists = matching.fuse_score(dists, detections)
matches, u_track, u_detection = matching.linear_assignment(
dists, thresh=self.match_thresh
dists, thresh=self.minimum_matching_threshold
)
for itracked, idet in matches:

View File

@ -1,5 +1,104 @@
import functools
import os
import warnings
from typing import Callable
class SupervisionWarnings(Warning):
"""Supervision warning category.
Set the deprecation warnings visibility for Supervision library.
You can set the environment variable SUPERVISON_DEPRECATION_WARNING to '0' to
disable the deprecation warnings.
"""
pass
def format_warning(msg, category, filename, lineno, line=None):
"""
Format a warning the same way as the default formatter, but also include the
category name in the output.
"""
return f"{category.__name__}: {msg}\n"
warnings.formatwarning = format_warning
if os.getenv("SUPERVISON_DEPRECATION_WARNING") == "0":
warnings.simplefilter("ignore", SupervisionWarnings)
else:
warnings.simplefilter("always", SupervisionWarnings)
def deprecated_parameter(
old_parameter: str,
new_parameter: str,
map_function: Callable = lambda x: x,
warning_message: str = "Warning: '{old_parameter}' in '{function_name}' is "
"deprecated: use '{new_parameter}' instead.",
**message_kwargs,
):
"""
A decorator to mark a function's parameter as deprecated and issue a warning when
used.
Parameters:
old_parameter (str): The name of the deprecated parameter.
new_parameter (str): The name of the parameter that should be used instead.
map_function (Callable, optional): A function used to map the value of the old
parameter to the new parameter. Defaults to the identity function.
warning_message (str, optional): The warning message to be displayed when the
deprecated parameter is used. Defaults to a generic warning message with
placeholders for the old parameter, new parameter, and function name.
**message_kwargs: Additional keyword arguments that can be used to customize
the warning message.
Returns:
Callable: A decorator function that can be applied to mark a function's
parameter as deprecated.
Examples:
```python
@deprecated_parameter(
old_parameter=<OLD_PARAMETER_NAME>,
new_parameter=<NEW_PARAMETER_NAME>
)
def example_function(<NEW_PARAMETER_NAME>):
pass
# call function using deprecated parameter
example_function(<OLD_PARAMETER_NAME>=<OLD_PARAMETER_VALUE>)
```
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if old_parameter in kwargs:
if args and hasattr(args[0], "__class__"):
class_name = args[0].__class__.__name__
function_name = f"{class_name}.{func.__name__}"
else:
function_name = func.__name__
warnings.warn(
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))
return func(*args, **kwargs)
return wrapper
return decorator
def deprecated(reason: str):
@ -8,7 +107,7 @@ def deprecated(reason: str):
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated: {reason}",
category=DeprecationWarning,
category=SupervisionWarnings,
stacklevel=2,
)
return func(*args, **kwargs)

View File

@ -2,17 +2,21 @@ from typing import List, Optional, Tuple
import cv2
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from supervision.annotators.base import ImageType
from supervision.annotators.utils import pillow_to_cv2
def plot_image(
image: np.ndarray, size: Tuple[int, int] = (12, 12), cmap: Optional[str] = "gray"
image: ImageType, size: Tuple[int, int] = (12, 12), cmap: Optional[str] = "gray"
) -> None:
"""
Plots image using matplotlib.
Args:
image (np.ndarray): The frame to be displayed.
image (ImageType): The frame to be displayed ImageType
is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`.
size (Tuple[int, int]): The size of the plot.
cmap (str): the colormap to use for single channel images.
@ -27,6 +31,9 @@ def plot_image(
sv.plot_image(image=image, size=(16, 16))
```
"""
if isinstance(image, Image.Image):
image = pillow_to_cv2(image)
plt.figure(figsize=size)
if image.ndim == 2:
@ -39,7 +46,7 @@ def plot_image(
def plot_images_grid(
images: List[np.ndarray],
images: List[ImageType],
grid_size: Tuple[int, int],
titles: Optional[List[str]] = None,
size: Tuple[int, int] = (12, 12),
@ -49,7 +56,8 @@ def plot_images_grid(
Plots images in a grid using matplotlib.
Args:
images (List[np.ndarray]): A list of images as numpy arrays.
images (List[ImageType]): A list of images as ImageType
is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`.
grid_size (Tuple[int, int]): A tuple specifying the number
of rows and columns for the grid.
titles (Optional[List[str]]): A list of titles for each image.
@ -65,9 +73,10 @@ def plot_images_grid(
```python
import cv2
import supervision as sv
from PIL import Image
image1 = cv2.imread("path/to/image1.jpg")
image2 = cv2.imread("path/to/image2.jpg")
image2 = Image.open("path/to/image2.jpg")
image3 = cv2.imread("path/to/image3.jpg")
images = [image1, image2, image3]
@ -79,6 +88,10 @@ def plot_images_grid(
"""
nrows, ncols = grid_size
for idx, img in enumerate(images):
if isinstance(img, Image.Image):
images[idx] = pillow_to_cv2(img)
if len(images) > nrows * ncols:
raise ValueError(
"The number of images exceeds the grid size. Please increase the grid size"

249
test/detection/test_json.py Normal file
View File

@ -0,0 +1,249 @@
import json
import os
from test.test_utils import mock_detections
from typing import Any, Dict, List
import pytest
import supervision as sv
@pytest.mark.parametrize(
"detections, custom_data, "
"second_detections, second_custom_data, "
"file_name, expected_result",
[
(
mock_detections(
xyxy=[[10, 20, 30, 40], [50, 60, 70, 80]],
confidence=[0.7, 0.8],
class_id=[0, 0],
tracker_id=[0, 1],
data={"class_name": ["person", "person"]},
),
{"frame_number": 42},
mock_detections(
xyxy=[[15, 25, 35, 45], [55, 65, 75, 85]],
confidence=[0.6, 0.9],
class_id=[1, 1],
tracker_id=[2, 3],
data={"class_name": ["car", "car"]},
),
{"frame_number": 43},
"test_detections.json",
[
{
"x_min": 10,
"y_min": 20,
"x_max": 30,
"y_max": 40,
"class_id": 0,
"confidence": 0.699999988079071,
"tracker_id": 0,
"class_name": "person",
"frame_number": 42,
},
{
"x_min": 50,
"y_min": 60,
"x_max": 70,
"y_max": 80,
"class_id": 0,
"confidence": 0.800000011920929,
"tracker_id": 1,
"class_name": "person",
"frame_number": 42,
},
{
"x_min": 15,
"y_min": 25,
"x_max": 35,
"y_max": 45,
"class_id": 1,
"confidence": 0.6000000238418579,
"tracker_id": 2,
"class_name": "car",
"frame_number": 43,
},
{
"x_min": 55,
"y_min": 65,
"x_max": 75,
"y_max": 85,
"class_id": 1,
"confidence": 0.8999999761581421,
"tracker_id": 3,
"class_name": "car",
"frame_number": 43,
},
],
), # Multiple detections
(
mock_detections(
xyxy=[[60, 70, 80, 90], [100, 110, 120, 130]],
tracker_id=[4, 5],
data={"class_name": ["bike", "dog"]},
),
{"frame_number": 44},
mock_detections(
xyxy=[[65, 75, 85, 95], [105, 115, 125, 135]],
confidence=[0.5, 0.4],
data={"class_name": ["tree", "cat"]},
),
{"frame_number": 45},
"test_detections_missing_fields.json",
[
{
"x_min": 60,
"y_min": 70,
"x_max": 80,
"y_max": 90,
"class_id": "",
"confidence": "",
"tracker_id": 4,
"class_name": "bike",
"frame_number": 44,
},
{
"x_min": 100,
"y_min": 110,
"x_max": 120,
"y_max": 130,
"class_id": "",
"confidence": "",
"tracker_id": 5,
"class_name": "dog",
"frame_number": 44,
},
{
"x_min": 65,
"y_min": 75,
"x_max": 85,
"y_max": 95,
"class_id": "",
"confidence": 0.5,
"tracker_id": "",
"class_name": "tree",
"frame_number": 45,
},
{
"x_min": 105,
"y_min": 115,
"x_max": 125,
"y_max": 135,
"class_id": "",
"confidence": 0.4000000059604645,
"tracker_id": "",
"class_name": "cat",
"frame_number": 45,
},
],
), # Missing fields
(
mock_detections(
xyxy=[[10, 11, 12, 13]],
confidence=[0.95],
data={"class_name": "unknown", "is_detected": True, "score": 1},
),
{"frame_number": 46},
mock_detections(
xyxy=[[14, 15, 16, 17]],
data={"class_name": "artifact", "is_detected": False, "score": 0.85},
),
{"frame_number": 47},
"test_detections_varied_data.json",
[
{
"x_min": 10,
"y_min": 11,
"x_max": 12,
"y_max": 13,
"class_id": "",
"confidence": 0.949999988079071,
"tracker_id": "",
"class_name": "unknown",
"is_detected": "True",
"score": "1",
"frame_number": 46,
},
{
"x_min": 14,
"y_min": 15,
"x_max": 16,
"y_max": 17,
"class_id": "",
"confidence": "",
"tracker_id": "",
"class_name": "artifact",
"is_detected": "False",
"score": "0.85",
"frame_number": 47,
},
],
), # Inconsistent Data Types
(
mock_detections(
xyxy=[[20, 21, 22, 23]],
),
{
"metadata": {"sensor_id": 101, "location": "north"},
"tags": ["urgent", "review"],
},
mock_detections(
xyxy=[[14, 15, 16, 17]],
),
{
"metadata": {"sensor_id": 104, "location": "west"},
"tags": ["not-urgent", "done"],
},
"test_detections_complex_data.json",
[
{
"x_min": 20,
"y_min": 21,
"x_max": 22,
"y_max": 23,
"class_id": "",
"confidence": "",
"tracker_id": "",
"metadata": {"sensor_id": 101, "location": "north"},
"tags": ["urgent", "review"],
},
{
"x_min": 14,
"y_min": 15,
"x_max": 16,
"y_max": 17,
"class_id": "",
"confidence": "",
"tracker_id": "",
"metadata": {"sensor_id": 104, "location": "west"},
"tags": ["not-urgent", "done"],
},
],
), # Complex Data
],
)
def test_json_sink(
detections: mock_detections,
custom_data: Dict[str, Any],
second_detections: mock_detections,
second_custom_data: Dict[str, Any],
file_name: str,
expected_result: List[List[Any]],
) -> None:
with sv.JSONSink(file_name) as sink:
sink.append(detections, custom_data)
sink.append(second_detections, second_custom_data)
assert_json_equal(file_name, expected_result)
def assert_json_equal(file_name, expected_rows):
with open(file_name, "r") as file:
data = json.load(file)
assert (
data == expected_rows
), f"Data in JSON file didn't match expected output: {data} != {expected_rows}"
os.remove(file_name)

View File

@ -0,0 +1,94 @@
from contextlib import ExitStack as DoesNotRaise
from test.test_utils import mock_detections
import numpy as np
import pytest
import supervision as sv
DETECTION_BOXES = np.array(
[
[35.0, 35.0, 65.0, 65.0],
[60.0, 60.0, 90.0, 90.0],
[85.0, 85.0, 115.0, 115.0],
[110.0, 110.0, 140.0, 140.0],
[135.0, 135.0, 165.0, 165.0],
[160.0, 160.0, 190.0, 190.0],
[185.0, 185.0, 215.0, 215.0],
[210.0, 210.0, 240.0, 240.0],
[235.0, 235.0, 265.0, 265.0],
],
dtype=np.float32,
)
DETECTIONS = mock_detections(
xyxy=DETECTION_BOXES, class_id=np.array([0, 0, 0, 0, 0, 0, 0, 0, 0])
)
POLYGON = np.array([[100, 100], [200, 100], [200, 200], [100, 200]])
FRAME_RESOLUTION = (300, 300)
@pytest.mark.parametrize(
"detections, polygon_zone, expected_results, exception",
[
(
DETECTIONS,
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
triggering_anchors=(
sv.Position.TOP_LEFT,
sv.Position.TOP_RIGHT,
sv.Position.BOTTOM_LEFT,
sv.Position.BOTTOM_RIGHT,
),
),
np.array(
[False, False, False, True, True, True, False, False, False], dtype=bool
),
DoesNotRaise(),
), # Test all four corners
(
DETECTIONS,
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
),
np.array(
[False, False, True, True, True, True, False, False, False], dtype=bool
),
DoesNotRaise(),
), # Test default behaviour when no anchors are provided
(
DETECTIONS,
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
triggering_anchors=[sv.Position.BOTTOM_CENTER],
),
np.array(
[False, False, True, True, True, True, False, False, False], dtype=bool
),
DoesNotRaise(),
), # Test default behaviour with deprecated api.
(
sv.Detections.empty(),
sv.PolygonZone(
POLYGON,
FRAME_RESOLUTION,
),
np.array([], dtype=bool),
DoesNotRaise(),
), # Test empty detections
],
)
def test_polygon_zone_trigger(
detections: sv.Detections,
polygon_zone: sv.PolygonZone,
expected_results: np.ndarray,
exception: Exception,
) -> None:
with exception:
in_zone = polygon_zone.trigger(detections)
assert np.all(in_zone == expected_results)