Compare commits

..
906 Commits
Author SHA1 Message Date
Piotr Skalski f34993c202 Merge pull request #493 from roboflow/develop
`supervision-0.16.0` release
2023-10-19 10:23:59 +02:00
SkalskiP f44a9376ec Update version number in pyproject.toml
Updated the version number in pyproject.toml from 0.16.0rc3 to 0.16.0. This denotes the transition from the release candidate to the official version.
2023-10-19 10:22:38 +02:00
Piotr Skalski 48752b432b Merge pull request #501 from roboflow/feature/0.16.0-changelog
Remove custom 404 page and update documentation for new release
2023-10-19 00:44:16 +02:00
pre-commit-ci[bot] c1797ffd81 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 22:42:55 +00:00
SkalskiP a640e527d0 Remove custom 404 page and update documentation for new release
The custom 404.html was removed as it was no longer necessary with the updates to the Mkdocs.yml file. Upgraded the project version from 0.16.0rc2 to 0.16.0rc3 in the pyproject.toml file. Various updates were added to the changelog.md to reflect improvements and bug fixes in the new project release. This includes new annotations, utility improvements, and bug-fixes to improve project functionality.
2023-10-19 00:41:13 +02:00
Piotr Skalski d894c7814b Merge pull request #499 from roboflow/feature/add_draw_image_docs
Add new drawing methods and improve type hints
2023-10-18 23:13:06 +02:00
pre-commit-ci[bot] 815c43beb8 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 21:11:27 +00:00
SkalskiP 2fed8ae0c3 test 2023-10-18 23:10:36 +02:00
pre-commit-ci[bot] 586ff5b3fb fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 20:59:14 +00:00
SkalskiP 8b73c12bcc test 2023-10-18 22:58:58 +02:00
SkalskiP 67d1323daa Merge remote-tracking branch 'origin/feature/add_draw_image_docs' into feature/add_draw_image_docs
# Conflicts:
#	supervision/draw/utils.py
2023-10-18 22:56:20 +02:00
SkalskiP 0e6d69cf85 test 2023-10-18 22:56:09 +02:00
pre-commit-ci[bot] a5c211ff61 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 20:54:16 +00:00
SkalskiP 5f5b4da845 Merge remote-tracking branch 'origin/feature/add_draw_image_docs' into feature/add_draw_image_docs
# Conflicts:
#	supervision/draw/utils.py
2023-10-18 22:53:22 +02:00
SkalskiP be16b84340 test 2023-10-18 22:53:10 +02:00
pre-commit-ci[bot] 08af18ad41 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 20:49:57 +00:00
SkalskiP caf504c0c9 Merge remote-tracking branch 'origin/feature/add_draw_image_docs' into feature/add_draw_image_docs 2023-10-18 22:49:44 +02:00
SkalskiP 893a19a991 test 2023-10-18 22:49:35 +02:00
pre-commit-ci[bot] b35a6161dd fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 20:46:25 +00:00
SkalskiP aabca4a4b5 Merge remote-tracking branch 'origin/feature/add_draw_image_docs' into feature/add_draw_image_docs 2023-10-18 22:46:10 +02:00
SkalskiP 57ba059f1c test 2023-10-18 22:45:58 +02:00
pre-commit-ci[bot] 743f3281a5 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 20:33:06 +00:00
SkalskiP 737cb37249 Add new drawing methods and improve type hints
Expanded the drawing functionality by adding new utility methods 'draw_image', 'draw_line' and 'draw_rectangle' and imported them in supervision/__init__.py. Updated the documentation of draw/utils, to include 'draw_image'. Improved type hinting in draw/color.py and draw/utils.py. Also, some redundant comments and unnecessary immediate memory freeing line are removed from draw/utils.py to enhance code readability.
2023-10-18 22:31:34 +02:00
Piotr Skalski 78a4e73f25 Merge pull request #449 from mario-dg/watermark_images_videos
feat: 🚀 Enable Watermarking images and videos
2023-10-18 22:23:25 +02:00
Piotr Skalski dc9c1151c5 Merge pull request #498 from roboflow/feature/heat_map_docs_update
Add HeatMapAnnotator class to supervision
2023-10-18 21:38:00 +02:00
SkalskiP 5f82aac9e6 Update example images link in core.py and annotators.md
The hyperlink to images in the example blocks in `supervision/annotators/core.py`, and `docs/annotators.md` was modified to reflect recent updates to the annotated heat map image.
2023-10-18 21:34:56 +02:00
SkalskiP d66a539779 Update example code blocks in core.py and annotators.md
Code examples in `supervision/annotators/core.py` and `docs/annotators.md` have been updated for clarity and consistency. The changes include renaming variables more appropriately and elaborating on the video processing steps which now includes tracking and annotating frames using a YOLO model. This revision aims to make examples more understandable and consistent for users following the documentation.
2023-10-18 21:25:44 +02:00
SkalskiP a037a4a404 Add HeatMapAnnotator class to supervision
A HeatMapAnnotator class has been added for visualization purposes. This class can be used to draw heatmaps on an image based on provided detections, where the heat accumulates over time and is drawn as a semi-transparent overlay of blurred circles.
2023-10-18 21:17:51 +02:00
Piotr Skalski 871968a916 Merge pull request #466 from kapter/addHeatmapAnnotator
New feature HeatmapAnnotator
2023-10-18 20:59:53 +02:00
Piotr Skalski 5ca30f5898 Merge pull request #489 from roboflow/add-new-docs
Add How to Detect and Annotate guide
2023-10-18 20:55:03 +02:00
pre-commit-ci[bot] 54bb79c220 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 18:51:33 +00:00
SkalskiP 809d8946e6 Update docs on object detection and annotation
This commit clarifies the process of object detection and annotation using Supervision and the Ultralytics YOLOv8 model. The changes also include updated instructions on how to load model predictions into Supervision and using them to annotate images, with links to the methods used in the process and the available options. An image showing the resulting annotated image has also been added for better understanding. The changes are shaped to make the documentation more informative, concise, and easy to follow.
2023-10-18 20:50:52 +02:00
Piotr Skalski e2305e6f57 Merge pull request #496 from roboflow/feature/dot_annotator_docs_update
Expand and update `Color`, `ColorPalette`, and `DotAnnotator` classes
2023-10-18 19:05:07 +02:00
SkalskiP e173510117 Refactor DotAnnotator docstring for clarity 2023-10-18 19:01:54 +02:00
pre-commit-ci[bot] e209c7436c fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 16:57:05 +00:00
SkalskiP 25a45aa423 Expand and update Color, ColorPalette, and DotAnnotator classes
Added detailed comments and examples to the Color and ColorPalette classes in supervision/draw/color.py, in addition to the DotAnnotator class in supervision/annotators/core.py. The changes clarify the usage and function of these classes. Also appended to the documentation by creating a new section for Color in docs/draw/color.md and including Color and DotAnnotator in docs/annotators.md. Incorporated the new documentation into the navigation structure in mkdocs.yml for user accessibility. The improvements enhance overall code-readability and understandability.
2023-10-18 18:53:39 +02:00
pre-commit-ci[bot] b9d1d78f31 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 16:45:51 +00:00
kapter 142fe02959 add params for hsv 2023-10-18 19:44:15 +03:00
Piotr Skalski 6815814aec Merge pull request #492 from PankajKrana/Create_sv.DotMarkerAnnotator_#396
Create sv.dot marker annotator #396
2023-10-18 18:16:12 +02:00
pre-commit-ci[bot] 1b6613d4a3 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 16:09:41 +00:00
SkalskiP fabfe0783a Refactor DotMarkerAnnotator and merge into core annotators
The DotMarkerAnnotator class has been refactored and named DotAnnotator for simplicity. It's now part of the core annotators to conform with the project structure. The color lookup on the DotAnnotator now uses the 'color_lookup' parameter, allowing for more flexibility and customization in the colors used for the annotations. All the changes aim to standardize the way different annotators are organized and make color customization easier.
2023-10-18 18:09:13 +02:00
Piotr Skalski 2c4e7589b0 Merge pull request #477 from roboflow/poetry-install-docs-fix
docs(fix): 📓 poetry env typo fix
2023-10-18 16:34:59 +02:00
Piotr Skalski 0abe36e079 Merge pull request #490 from roboflow/feature/update_line_zone
Add documentation and improvements to "LineZone" functionality
2023-10-18 16:15:38 +02:00
pre-commit-ci[bot] 61809f9329 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 13:53:26 +00:00
Pankaj Kumar Rana 69f3d10e42 Merge branch 'roboflow:develop' into Create_sv.DotMarkerAnnotator_#396 2023-10-18 19:13:40 +05:30
pre-commit-ci[bot] 62c45548a6 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 12:44:20 +00:00
SkalskiP 00e209744f Add documentation and improvements to "LineZone" functionality
Descriptions for various attributes and functionalities have been added into LineZone class to aid developers in understanding their behaviour. Also, the trigger function was modified to return two separate boolean arrays - 'crossed_in' and 'crossed_out' to denote objects crossing the line in specific directions. Moreover, Line Zone documentation is added to the side navigation in mkdocs.yml.
2023-10-18 14:42:59 +02:00
James Gallagher 8dcfb42751 add data loader link 2023-10-18 13:13:09 +01:00
James Gallagher a5a853e141 resolve conflict 2023-10-18 13:11:29 +01:00
Piotr Skalski 034c2bd9a8 Merge pull request #482 from abhishek7kalra/linezonetrigger
Linezone.trigger returns bool nparray
2023-10-18 13:48:00 +02:00
Piotr Skalski a7449ba56f Update README.md 2023-10-18 12:42:26 +02:00
Onuralp SEZER 8f302c0646 Merge pull request #488 from roboflow/dependabot/pip/urllib3-2.0.7
Bump urllib3 from 2.0.6 to 2.0.7
2023-10-18 12:26:39 +03:00
pre-commit-ci[bot] 1ddb78b662 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-18 09:22:43 +00:00
James Gallagher db4afe9885 add how to detect and annotate guide 2023-10-18 10:20:42 +01:00
dependabot[bot] 72a780b74a Bump urllib3 from 2.0.6 to 2.0.7
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.6...2.0.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-17 21:06:21 +00:00
PankajKrana b2e9a668b2 Create sv.DotMarkerAnnotator #396 2023-10-17 01:48:54 +05:30
Piotr Skalski 57ef24bcb3 Merge pull request #485 from roboflow/domain-change
Change domain to supervision.roboflow.com in mkdocs
2023-10-16 14:13:19 +02:00
James Gallagher eda6909c91 change domain to supervision.roboflow.com in mkdocs 2023-10-16 13:10:52 +01:00
Piotr Skalski 9a9bf62fe0 Merge pull request #484 from roboflow/404-fix
Add `/` to mkdocs site_url
2023-10-16 14:09:49 +02:00
James Gallagher 1ab50cdb3c add / to mkdocs site_url 2023-10-16 13:06:43 +01:00
Onuralp SEZER f78c282b7e Add 404 page - PR #483 from roboflow/add-404 2023-10-16 11:13:26 +03:00
pre-commit-ci[bot] 3d843d710d fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-16 08:04:17 +00:00
James Gallagher 75db51aa90 add 404 page 2023-10-16 09:00:12 +01:00
abhishek7kalra fe8a8260dc change return type from list to nparray 2023-10-15 19:06:18 +05:30
abhishek7kalra bf725ef0f7 fix pre-commit 2023-10-15 18:10:54 +05:30
abhishek7kalra 338c589032 LineZone.trigger update to return info 2023-10-15 17:42:40 +05:30
Onuralp SEZER 4baf570b28 docs(fix): 📓 poetry env typo fix 2023-10-14 01:12:16 +03:00
kapter 1324cda600 unnecessary function(get_xy) removed 2023-10-13 16:09:24 +03:00
Piotr Skalski 9ded70c8da Merge pull request #465 from roboflow/feature/annotator-color-lookup-improvements
Update color mapping system in detection annotations
2023-10-13 13:34:43 +02:00
Piotr Skalski 8cda753334 Merge pull request #469 from roboflow/docs-virtualenv-pip
docs: 📓 development virtualenv installation pip upgrade added
2023-10-13 00:27:19 +02:00
Onuralp SEZER a14159c150 docs: 📓 development virtualenv installation pip upgrade added 2023-10-13 00:56:01 +03:00
pre-commit-ci[bot] ae00a9d65f fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-12 20:48:20 +00:00
kapter c51159b598 added HeatmapAnnotator 2023-10-12 23:44:26 +03:00
pre-commit-ci[bot] 476a31fa79 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-12 17:11:59 +00:00
SkalskiP e1e455f99c Update color mapping system in detection annotations
Adjusted the way colors are applied to detection annotations to allow support for custom color schemes. Modified 'ColorMap' to 'ColorLookup' across multiple files, adding ability to use a color lookup array. This refinement will enable users to assign custom colors to different detection annotations, offering greater flexibility in visual representation of detection data. Updated related tests to reflect these changes.
2023-10-12 19:10:19 +02:00
Piotr Skalski 5b5e0eb88d Merge pull request #457 from roboflow/cache-pip
ci: 🚧 github action - python cache added 🚀
2023-10-12 14:32:02 +02:00
Onuralp SEZER d0139f5293 ci: 🚧 clear cache once a month and manual run 🚀
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-12 13:44:03 +03:00
Onuralp SEZER bed7ed45b4 ci: 🚧 python github action cache added 🚀
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-12 12:44:48 +03:00
Piotr Skalski 35142fc755 Merge pull request #456 from roboflow/revert-436-develop
Revert "#418 Make sv.ByteTrack work with segmentation model "
2023-10-12 01:11:14 +02:00
pre-commit-ci[bot] f041f0ddcc fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-11 23:09:07 +00:00
Piotr Skalski 253a136f43 Revert "#418 Make sv.ByteTrack work with segmentation model " 2023-10-12 01:08:35 +02:00
SkalskiP 9cefff8627 Supervision Examples guidelines refactor. 2023-10-11 17:03:37 +02:00
Piotr Skalski f2af290166 Merge pull request #344 from onuralpszr/examples_improvements
Adding example and  example guidelines
2023-10-11 15:06:50 +02:00
Piotr Skalski 988da89bf4 Merge pull request #454 from jayaBalaR/patch-1
Adding HuggingFace Annotators Space link to `README.md`
2023-10-11 12:12:37 +02:00
JAYABALAMBIKA.R b4a8859616 Update README.md
Adding HuggingFace Annotators Space link to README.md
2023-10-11 15:34:17 +05:30
Piotr Skalski 9fff77a6e3 Merge pull request #448 from roboflow/pre-commit-isort-duplicate
chore: 🔥 remove duplicate isort from pre-commit-config
2023-10-10 11:41:45 +02:00
pre-commit-ci[bot] e7f95e0761 chore(pre_commit): ⬆ pre_commit autoupdate (#445)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-10 11:28:50 +03:00
Onuralp SEZER 2254e59bdf chore: 🔥 remove duplicate isort from pre-commit-config 2023-10-10 11:26:53 +03:00
Mario da Graca 6a0482ded5 Fixed pre-commit issues 2023-10-10 08:24:29 +02:00
SkalskiP 29532db02f Update version number in pyproject.toml
Upgraded project version from "0.16.0rc1" to "0.16.0rc2" in pyproject.toml.
2023-10-09 19:59:55 +02:00
Piotr Skalski 8eb827b31d Merge pull request #437 from ashishdatta/from_roboflow_tracker_id
Update sv.Detections.from_roboflow to extract tracker_id values from inference response
2023-10-09 19:58:50 +02:00
Piotr Skalski 33e43bc92a Merge pull request #444 from roboflow/fix/halo-annotator-docs
Update example images and modify code parameters
2023-10-09 16:24:26 +02:00
SkalskiP 8b5b0a8516 Bump project version in pyproject.toml
The project version has been upgraded from "0.15.0" to "0.16.0rc1" in the pyproject.toml file. This change signifies the start of a new release candidate cycle.
2023-10-09 16:20:16 +02:00
pre-commit-ci[bot] 9a7b4c34ba fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-09 14:17:52 +00:00
SkalskiP b2ab398029 Update example images and modify code parameters 2023-10-09 16:15:35 +02:00
mario-dg 082f248159 feat: 🚀 watermarking images and videos 2023-10-09 15:42:54 +02:00
Piotr Skalski 72463f5f89 Update README.md 2023-10-09 15:31:20 +02:00
Piotr Skalski 83342ca36f Merge pull request #433 from kapter/HaloAnnotator
new feature HaloAnnotator
2023-10-09 14:57:10 +02:00
Piotr Skalski 48851b5ae5 Merge pull request #436 from AntonioConsiglio/develop
#418 Make sv.ByteTrack work with segmentation model
2023-10-09 14:18:31 +02:00
SkalskiP e5894df156 Reorganize documentation structure and reformat code
Moved advanced filtering documentation to `how_to` directory from `quickstart` to make way for more comprehensive starter guide. Reorganized MkDocs configuration accordingly. Formatted some code in `classification/core.py` and `dataset/core.py` for better readability. Added placeholder files in `how_to` for future guides on object detection, video processing, object tracking, model evaluation, and detections filtering.
2023-10-09 13:41:56 +02:00
pre-commit-ci[bot] 49d268c250 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-09 11:13:48 +00:00
Antonio Consiglio a7a7dac216 Merge branch 'develop' of https://github.com/AntonioConsiglio/supervision into develop 2023-10-09 13:13:32 +02:00
Antonio Consiglio 8bca45bde4 style and doc change as requested 2023-10-09 13:13:28 +02:00
Piotr Skalski 282db45c00 Merge pull request #440 from roboflow/removal_makefile
chore: 🔥 delete redundant Makefile because pypi and formatters are automatic.
2023-10-09 12:18:54 +02:00
Onuralp SEZER 1c5006c013 chore: 🔥 delete Makefile, pypi and formatters has been automatic, we don't need this file anymore 2023-10-09 13:12:55 +03:00
SkalskiP 9b4fa8cb89 Remove redundant file
The file was removed because it was no longer needed in the current implementation.
2023-10-09 12:03:18 +02:00
Piotr Skalski 333408d33a Merge pull request #438 from roboflow/remove_old_yolov8
chore!: 🔥 removal from_yolov8 function from classification and detection
2023-10-09 12:00:21 +02:00
SkalskiP 41a19ac4d6 Remove unnecessary empty line in core.py 2023-10-09 11:58:21 +02:00
pre-commit-ci[bot] fe0df11918 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-09 09:52:13 +00:00
SkalskiP 375b5f8f54 Remove deprecated method from_yolov8 in Classification class
The method 'from_yolov8' was deprecated and was scheduled for removal in 0.16.0 release. The users are encouraged to use the more general method 'from_ultralytics' which supports results from any Ultralytics engine.
2023-10-09 11:51:56 +02:00
Onuralp SEZER 18fe2e99de chore!: 🔥 removal from_yolov8
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-08 19:52:33 +03:00
pre-commit-ci[bot] a83d3b4c7e fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-07 20:12:29 +00:00
kapter 89c9f88aeb add kernel size var and change method of overlay 2023-10-07 23:12:11 +03:00
ashishdatta 6ef7bf31d3 formatting 2023-10-07 10:32:25 -07:00
ashishdatta c3a025cc9d Extending testing to support tracker_id field.
Cases covered:
empty
non-empty without tracker_id key
non-empty with tracker_id key and None value
non-empty with tracker_id key and non-None value
2023-10-07 10:27:40 -07:00
ashishdatta f489f944b8 Extending from_roboflow to support tracker_ids 2023-10-07 10:26:04 -07:00
ashishdatta cfb1e0ea60 Inference module now supports tracking detected objcets, extending process_roboflow_result to support that. 2023-10-07 10:24:26 -07:00
pre-commit-ci[bot] 5efd535e9d fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-07 17:20:31 +00:00
Antonio Consiglio a550aaa812 Merge branch 'develop' of https://github.com/AntonioConsiglio/supervision into develop 2023-10-07 19:15:04 +02:00
Antonio Consiglio 30994243ae Make sv.ByteTrack work with segmentation model#418 2023-10-07 19:15:03 +02:00
pre-commit-ci[bot] 99707582bc fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-07 17:08:17 +00:00
Antonio Consiglio c0b87a6f04 Make sv.ByteTrack work with segmentation model#418 2023-10-07 19:03:30 +02:00
Piotr Skalski d30d80717c Merge pull request #422 from hardikdava/box_mask_annotator
Added box mask annotators
2023-10-07 00:47:09 +02:00
Piotr Skalski 4733ebe27d Merge pull request #432 from roboflow/fix/fps_monitor_docs
Move FpsMonitor from utils to video module
2023-10-07 00:35:06 +02:00
pre-commit-ci[bot] 44e4eeaa69 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-06 22:33:32 +00:00
pre-commit-ci[bot] 2952286f95 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-06 22:25:02 +00:00
SkalskiP c5e1211c74 Move FpsMonitor from utils to video module
This commit relocates the FpsMonitor class from the utils module to the video module.
2023-10-07 00:24:20 +02:00
kapter 72dc07e332 added HaloAnnotator 2023-10-07 01:11:49 +03:00
Piotr Skalski c5c37e4a6c Merge pull request #430 from akashpambhar/fix-return-empty-detections
Fix return value when sv.Detections is empty
2023-10-07 00:04:04 +02:00
Piotr Skalski 8000043bc8 Update supervision/tracker/byte_tracker/core.py 2023-10-07 00:01:24 +02:00
Akash Pambhar 14d0189e2c Fix return value when sv.Detections is empty 2023-10-07 00:51:08 +05:30
Hardik Dava 68a761349d Merge pull request #411 from roboflow/add-colormap-to-init
fix(import): 🐞 add ColorMap into init.py
2023-10-06 21:19:29 +02:00
Onuralp SEZER d5d5725cea Merge pull request #280 from hardikdava/feature/fps
Feature FPS meaure
2023-10-06 22:18:05 +03:00
pre-commit-ci[bot] 669c0658fe fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-06 18:54:41 +00:00
SkalskiP bed28e3db4 Improve the documentation for annotators class 2023-10-06 20:54:09 +02:00
pre-commit-ci[bot] cc93bba741 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-06 18:52:00 +00:00
SkalskiP e7c242bdd4 Update annotators in docs and code 2023-10-06 20:51:31 +02:00
SkalskiP 89c1b63979 Optimize color assignment in mask annotation 2023-10-06 19:24:10 +02:00
Piotr Skalski 456655c2a4 Merge pull request #426 from kapter/Make-MaskAnnotator-faster
faster sv.MaskAnnotator
2023-10-06 18:15:37 +02:00
Piotr Skalski f67a67bd7c Merge pull request #425 from roboflow/discord-button
Add Discord button to README
2023-10-06 11:56:00 +02:00
James Gallagher 30687609f5 update button 2023-10-06 09:51:56 +01:00
Piotr Skalski ab1b2d6d80 🎃 Add hacktoberfest banner to README.md 2023-10-06 10:41:56 +02:00
pre-commit-ci[bot] 5c6323bed0 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-06 08:00:00 +00:00
kapter d2fe2ad40c 10 percent speed up and no ver 2023-10-06 10:58:43 +03:00
pre-commit-ci[bot] 5aaa15c894 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-05 23:13:12 +00:00
kapter 4bb62f06ff add ma ver 2023-10-06 01:51:19 +03:00
kapter 4e7a763ad9 faster maskannotator 2023-10-06 01:22:08 +03:00
James cb225b26e8 Update README.md 2023-10-05 19:01:19 +01:00
Keshav Subramanianandkeshav278 07ec604102 add #a351fb as first default color into sv.draw.color.ColorPalette (#424)
Co-authored-by: keshav278 <vkeshav278@gmail.com>
2023-10-05 20:04:48 +03:00
Hardik Dava ef43640c88 Added box mask annotators 2023-10-05 17:56:32 +02:00
Piotr Skalski 77c126e421 Merge pull request #416 from roboflow/notebook-fixes
Supervision demo notebook fix and yolo-nas fix
2023-10-05 15:18:13 +02:00
Onuralp SEZER edd8b8f41e fix(formatter): 🐛 exclude ipynb for pretty-format-json formatter
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-05 15:36:13 +03:00
Onuralp SEZER cad38104e0 fix(notebook): 🐛📘 sam detection and sv version update
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-05 15:23:47 +03:00
Onuralp SEZER 618fadae2a fix(detection): 🐞 yolo-nas detection check fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-05 15:22:45 +03:00
Piotr Skalski 71d23619b0 Merge pull request #412 from roboflow/main
Merge pull request #404 from roboflow/develop
2023-10-05 09:24:56 +02:00
Piotr Skalski 1bddf26f34 Merge pull request #404 from roboflow/develop
`supervision-0.15.0` release
2023-10-05 09:23:31 +02:00
SkalskiP 73ed3f5f5e 👊 bump develop version to 0.15.0 2023-10-05 09:21:05 +02:00
Onuralp SEZER a57176568a fix(import): 🐞 add ColorMap into init.py
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-05 01:10:26 +03:00
SkalskiP 88e7fb76d2 Fixed link targeting in changelog documentation. 2023-10-04 23:57:03 +02:00
Piotr Skalski 09c5407347 Merge pull request #410 from roboflow/feature/blur-annotator-docs-update
Refactor BlurAnnotator class and update documentation
2023-10-04 23:54:48 +02:00
pre-commit-ci[bot] bfcf6469d4 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-04 21:52:50 +00:00
SkalskiP ec72f81749 Refactor BlurAnnotator class and update documentation 2023-10-04 23:50:47 +02:00
Piotr Skalski 3a52f7b082 Merge pull request #405 from Rajarshi-Misra/new1
Create sv.BlurAnnotator #395
2023-10-04 23:29:26 +02:00
SkalskiP 5efb3e2df1 👊 bump develop version to 0.15.0rc3 2023-10-04 19:43:03 +02:00
SkalskiP 6e33cb04ca 🪵 supervision-0.15.0 changelog.md update 2023-10-04 19:35:01 +02:00
Rajarshi-Misra 2455ef7cfe Merge branch 'roboflow:develop' into new1 2023-10-04 22:24:53 +05:30
Rajarshi-Misra 1d0ffeba72 Comments corrected 2023-10-04 22:23:54 +05:30
Piotr Skalski 55d14cf33e Merge pull request #407 from roboflow/feature/color_map_strategy_support_for_trace_annotator
Add color mapping strategy to InferenceSlicer
2023-10-04 18:49:45 +02:00
pre-commit-ci[bot] a66d835a57 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-04 16:37:40 +00:00
SkalskiP 440be5d416 Add color mapping strategy to InferenceSlicer
Added a new argument, 'color_map' in InferenceSlicer to provide more options for color coding while annotating. This includes mapping colors by 'index', 'class' or 'track'. Also refactored the way colors are resolved by separating the responsibility into a new function 'resolve_color_idx' and 'resolve_color'.
2023-10-04 18:36:30 +02:00
Hardik Dava 4fd1ba4dd3 Update var type 2023-10-04 18:34:51 +02:00
Piotr Skalski 61108048e1 Merge pull request #361 from roboflow/add-concurrent-slicer
Add concurrent processing for `sv.InferenceSlicer`
2023-10-04 18:17:52 +02:00
pre-commit-ci[bot] a0b64342ca fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-04 16:17:35 +00:00
Rajarshi-Misra 7cf593b55d Replaced with blur filter 2023-10-04 21:47:24 +05:30
SkalskiP 1c1936e411 Refactor args description in InferenceSlicer 2023-10-04 18:15:08 +02:00
pre-commit-ci[bot] 331ccb8dde fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-04 16:09:38 +00:00
Rajarshi-Misra 7366ba9036 Error resolved 2023-10-04 21:38:44 +05:30
pre-commit-ci[bot] 9d5e53030a fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-04 14:25:37 +00:00
Piotr Skalski d67fa3ae9b Merge branch 'develop' into new1 2023-10-04 16:24:13 +02:00
Piotr Skalski 10ad27d726 Merge pull request #406 from roboflow/feature/annotators-refactor-before-0.15.0-release
Refactor TraceAnnotator to separate utility class
2023-10-04 16:18:49 +02:00
SkalskiP 8511bbb762 Add Tracker ID warning and image to TraceAnnotator doc
Added a warning about the requirement of `tracker_id` for TraceAnnotator in the `core.py`. Also, included an example image in the TraceAnnotator initialization for better understanding in both `core.py` and `annotators.md`.
2023-10-04 16:15:38 +02:00
SkalskiP 14388e56a2 Merge remote-tracking branch 'origin/feature/annotators-refactor-before-0.15.0-release' into feature/annotators-refactor-before-0.15.0-release 2023-10-04 15:35:17 +02:00
SkalskiP 1eb74ed88a Add TraceAnnotator description to the documentation
The documentation has been updated to include the `TraceAnnotator` class.
2023-10-04 15:35:07 +02:00
pre-commit-ci[bot] 87d830d3a1 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-04 13:31:17 +00:00
SkalskiP d7a5624d60 Refactor TraceAnnotator to separate utility class 2023-10-04 15:28:00 +02:00
Rajarshi-Misra d3c71c2119 Merge branch 'roboflow:develop' into new1 2023-10-04 18:01:13 +05:30
Rajarshi-Misra 9ad5e50ae9 Addition of gaussian blur filter 2023-10-04 17:59:03 +05:30
Piotr Skalski 67fa66c7e7 Merge pull request #399 from akashAD98/update_detection_sv
Remove class list dependency for roboflow to detection conversion
2023-10-04 12:08:04 +02:00
dependabot[bot] 492f033882 chore(deps): bump pillow from 9.5.0 to 10.0.1 (#403)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.5.0 to 10.0.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/9.5.0...10.0.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 13:07:50 +03:00
SkalskiP e911516180 Change prediction class handling from text to ids
Updated the prediction parsing logic in supervision/detection/utils.py and supervision/detection/core.py to use 'class_id' instead of 'class' names. Adapted tests in test/detection/test_utils.py accordingly to reflect the same change.
2023-10-04 10:44:49 +02:00
pre-commit-ci[bot] ed30ef68af fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-03 17:47:44 +00:00
Hardik Dava fefd273a0f removing class list from test inputs 2023-10-03 19:45:38 +02:00
pre-commit-ci[bot] 8746dd3d0e fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-03 17:34:30 +00:00
Hardik Dava 7cb57c9cc9 Fixing int conversion bug 2023-10-03 19:33:56 +02:00
James ddc036ea3a Update CODE_OF_CONDUCT.md (#400) 2023-10-03 19:40:56 +03:00
pre-commit-ci[bot] c83959bb16 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-10-03 16:18:29 +00:00
Akash A Desai 4f513ce275 Update core.py
remoed class_list arg from core.py file
2023-10-03 21:41:13 +05:30
Hardik Dava b482784403 Merge branch 'develop' into feature/fps 2023-10-03 17:57:53 +02:00
Hardik Dava 3b00e04101 Merge remote-tracking branch 'origin/develop' into feature/fps 2023-10-03 17:53:52 +02:00
Piotr Skalski 6f30c87612 Merge pull request #392 from roboflow/docs/documentation-refactoring
Refactor documentation structure and improve annotator details
2023-10-03 11:53:09 +02:00
SkalskiP 93a8110908 Refactor documentation structure and improve annotator details
Update the file and section structure of the project documentation for better readability. Removed 'annotate.md' and replaced it with an expanded 'annotators.md'. The new document provides examples of each annotator and includes preview images for better understanding. The API reference structure was flattened for simplicity and related documents 'core.py' and 'mkdocs.yml' were updated accordingly. The core tracker and dataset documents were renamed for consistency.
2023-10-03 11:38:28 +02:00
dependabot[bot] 410b7eb4fc chore(deps-dev): bump urllib3 from 2.0.4 to 2.0.6 (#389)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.6.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.4...2.0.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 12:07:45 +03:00
Piotr Skalski e6e1397f56 Merge pull request #386 from onuralpszr/feature/circle_annotator
feat(annotator): 🚀 introduce circle annotator
2023-10-02 23:56:24 +02:00
Piotr Skalski 0ca9a3ecdf Merge pull request #384 from roboflow/code-of-conduct
docs: 📝 code of conduct added
2023-10-02 23:54:36 +02:00
pre-commit-ci[bot] c5016bbd8f chore(pre_commit): ⬆ pre_commit autoupdate (#388)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.291 → v0.0.292](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.291...v0.0.292)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-10-03 00:16:20 +03:00
Onuralp SEZER 9fbd0ddf62 docs(annotator): 📝 circle annotator example picture added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-03 00:03:20 +03:00
Onuralp SEZER a630870968 feat(annotator): 🚀 introduce circle annotator
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-10-01 20:51:46 +03:00
Onuralp SEZER 06416607cc docs: 📝 code of conduct added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-29 14:54:56 +03:00
SkalskiP 1b449294c4 👊 bump develop version to 0.15.0rc2 2023-09-29 13:46:39 +02:00
Onuralp SEZER 36f95d060d Merge pull request #170 from roboflow/feature/redesigned_annotators
redesigned annotators
2023-09-29 14:36:43 +03:00
Onuralp SEZER 07c344e050 fix: 🐛 add missing TraceAnnotator (traffic_analysis fix) and sv version adjusted
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-29 13:25:26 +03:00
pre-commit-ci[bot] f3bdb6efa2 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 21:55:12 +00:00
SkalskiP f4f9e7ddc7 ready for review 2023-09-28 23:54:34 +02:00
pre-commit-ci[bot] 53f2b4fd45 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 21:24:53 +00:00
SkalskiP c7582f7c07 Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 23:24:39 +02:00
SkalskiP 3e8906d830 📘 docs updates for LabelAnnotator 2023-09-28 23:24:22 +02:00
pre-commit-ci[bot] 6521f9f4ed fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 19:47:18 +00:00
SkalskiP 560a35b75f 🧪 test LabelAnnotator supporting more Position values 2023-09-28 21:46:02 +02:00
pre-commit-ci[bot] 405a4c73f9 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 16:16:11 +00:00
SkalskiP 87fd10c366 Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 18:15:57 +02:00
SkalskiP 8141cb357f Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 18:15:45 +02:00
SkalskiP 87fb336eb6 Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 18:13:32 +02:00
SkalskiP 28d279bec3 Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 18:12:43 +02:00
pre-commit-ci[bot] 1b0bcea5a6 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 16:10:18 +00:00
SkalskiP 507e000bca Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 18:08:45 +02:00
SkalskiP f7bf766a60 🧪 Test of LabelAnnotator alternative annotate method implementation. 2023-09-28 18:08:01 +02:00
pre-commit-ci[bot] 1e0f18f792 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 15:11:28 +00:00
SkalskiP be5c1b1aa1 Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 17:11:14 +02:00
SkalskiP c35a7714d2 🧪 Test of BoxCornerAnnotator alternative annotate method implementation. 2023-09-28 17:11:06 +02:00
pre-commit-ci[bot] a8cc5a68bb fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 14:48:53 +00:00
SkalskiP de4056192e 🛠️ Refactor of BoxCornerAnnotator docs and annotate method implementation. 2023-09-28 16:48:03 +02:00
SkalskiP d337a03d28 Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 16:27:07 +02:00
SkalskiP 00b67588d8 🛠️ EllipseAnnotator.end_angle fix 2023-09-28 16:25:35 +02:00
pre-commit-ci[bot] e10e0f21df fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 14:14:41 +00:00
SkalskiP 4fb6760d2c Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 16:14:28 +02:00
SkalskiP de7ed5e115 🔨 EllipseAnnotator refactor. Changes in annotate method + new docstrings. 2023-09-28 16:14:17 +02:00
pre-commit-ci[bot] d929b86c9f fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 13:52:31 +00:00
SkalskiP 0b1627f835 Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 15:51:06 +02:00
SkalskiP 20b1b21774 🧹 Removed old MaskAnnotator implementation as it is no longer used. Moved ColorMap, resolve_color_idx, resolve_color and BaseAnnotator to supervision/annotators/base. Removed BoxMaskAnnotator as it can be achieved by BoundingBoxAnnotator and MaskAnnotator composition. 2023-09-28 15:50:55 +02:00
pre-commit-ci[bot] e5eaf26137 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 13:37:05 +00:00
SkalskiP be3e67949c Merge remote-tracking branch 'origin/feature/redesigned_annotators' into feature/redesigned_annotators 2023-09-28 15:36:28 +02:00
SkalskiP 2587fdfefd ☝️ updating MaskAnnotator docs and code to be consistent with BoundingBoxAnnotator 2023-09-28 15:36:18 +02:00
pre-commit-ci[bot] 08a88a8c0c fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 11:47:28 +00:00
SkalskiP 181c0b833b 🔨 small names refactor 2023-09-28 13:46:52 +02:00
pre-commit-ci[bot] 11601e3f2d fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-28 10:14:38 +00:00
SkalskiP 1b8ff617c3 📘 docs updates for Position and get_anchor_coordinates 2023-09-28 12:13:52 +02:00
SkalskiP 5bc83bf6cc Merge branch 'develop' into feature/redesigned_annotators 2023-09-27 12:08:07 +02:00
fatihandpre-commit-ci[bot] dd80753b2b add sahi paper url into docstring (#382)
* add sahi paper url into docstring
* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-27 00:59:57 +03:00
Piotr Skalski b2bb69bebb Merge pull request #381 from Killua7362/utils-fix
Adding Fourcc codecs option to VideoSink
2023-09-26 14:32:44 +02:00
pre-commit-ci[bot] 9c081b0e11 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-26 05:40:22 +00:00
Akshay 7f613c6d27 Merge branch 'utils-fix' of https://github.com/Killua7362/supervision into utils-fix 2023-09-26 08:40:01 +03:00
Akshay 9736eba9e6 FIX: Added type error exception in videosink 2023-09-26 08:38:46 +03:00
pre-commit-ci[bot] 53dfeac19b fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-26 05:25:00 +00:00
Akshay 5c25e58e39 Added docstrings for codec 2023-09-26 08:22:07 +03:00
Akshay 52949f2fd1 Support for all the codes 2023-09-26 08:12:18 +03:00
pre-commit-ci[bot] 56c511b5d8 chore(pre_commit): ⬆ pre_commit autoupdate (#379)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.290 → v0.0.291](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.290...v0.0.291)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-26 00:20:59 +03:00
Piotr Skalski 62014186a2 Update README.md with latest supervision YouTube tutorial 2023-09-22 13:43:56 +02:00
dependabot[bot] 6cd7cf0209 Bump cryptography from 41.0.3 to 41.0.4 (#375)
Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.3 to 41.0.4.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/41.0.3...41.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-22 10:17:28 +03:00
Onuralp SEZER a39c92016f docs(example): 📝 example LabelAnnotator doc fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-21 15:45:04 +03:00
Onuralp SEZER 4e88e26d91 chore(formatting): pre-commit fixes
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-21 15:43:07 +03:00
pre-commit-ci[bot] 484e53324e fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-21 12:36:21 +00:00
Onuralp SEZER e5c4d8e3b9 docs(example): 📝 Update tracking example with new annotators
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-21 15:34:17 +03:00
Onuralp SEZER 52ef3fadcb fix(redefinition): 🐞 fix redefinition for MaskAnnotator TraceAnnotator
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-21 14:46:35 +03:00
pre-commit-ci[bot] 472ddd8d0e fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-21 11:35:41 +00:00
Onuralp SEZER 90b23430d2 Merge branch 'develop' into feature/redesigned_annotators
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-21 14:35:24 +03:00
pre-commit-ci[bot] e91f0cb496 chore(pre_commit): ⬆ pre_commit autoupdate (#366)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.288 → v0.0.290](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.288...v0.0.290)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-19 16:34:16 +03:00
pre-commit-ci[bot] fa6238dbc4 chore(pre_commit): ⬆ pre_commit autoupdate (#362)
updates:
- [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1)
- [github.com/astral-sh/ruff-pre-commit: v0.0.287 → v0.0.288](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.287...v0.0.288)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-12 00:32:39 +03:00
Onuralp SEZER 1a7306c4ae ci(test): 🔥 removal formatters from test ci (#317)
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-10 23:29:45 +03:00
Onuralp SEZER c6b8fbed31 fix(pre-commit): formatting fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-08 21:29:37 +03:00
Onuralp SEZER b9598f54b6 feat: ✨ control thread worker and set default 1
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-08 15:08:37 +03:00
pre-commit-ci[bot] 21e5d5139c fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-08 12:04:17 +00:00
Onuralp SEZER 8f583404ce fix(import): 📦 import only ThreadPoolExecutor,as_completed
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-09-08 15:03:59 +03:00
pre-commit-ci[bot] bb42753e23 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-08 11:46:17 +00:00
James Gallagher ea0ec29c81 add concurrent processing for inference slicer 2023-09-08 12:43:20 +01:00
Onuralp SEZER 3d4326c79b Merge pull request #349 from onuralpszr/empty_box_checks
fix: 🐛 Empty check for xyxy results for prevent ValueError and return as empty detection
2023-09-05 20:17:17 +03:00
pre-commit-ci[bot] ca212b70c8 chore(pre_commit): ⬆ pre_commit autoupdate (#355)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.286 → v0.0.287](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.286...v0.0.287)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-05 20:15:19 +03:00
Piotr Skalski 0008b33eb1 Add traffic analysis demo to README.md 2023-09-05 17:52:29 +02:00
SkalskiP 77190f287c 📍 Pin supervision==0.15.0rc1 in examples/traffic_analysis 2023-09-04 23:08:12 +02:00
Piotr Skalski 121d4f31bb 👊 supervision version to `0.15.0rc1 in pyproject.toml` 2023-09-04 23:06:02 +02:00
Piotr Skalski 7c97e87e79 Merge pull request #354 from roboflow/example/traffic_analysis
example/traffic_analysis
2023-09-04 23:03:40 +02:00
SkalskiP 9640f1d756 TraceAnnotator documentation 2023-09-04 22:56:42 +02:00
SkalskiP 2419d42224 TraceAnnotator documentation 2023-09-04 22:52:47 +02:00
Piotr Skalski c6517de143 Update README.md 2023-09-04 22:36:26 +02:00
pre-commit-ci[bot] 2706304a73 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-04 20:18:46 +00:00
SkalskiP 8538c26a22 Add TraceAnnotator, refactor traffic analysis code and improve documentation
Introduced TraceAnnotator in the supervision package, refactored traffic analysis script for improved readability, and added quality documentation.
2023-09-04 22:16:37 +02:00
SkalskiP a0c60fe043 Refactor video stream and traffic analysis process
Restructuring the video stream and traffics analysis codes resulted in two new key classes, VideoProcessor, and DetectionsManager. The main aim behind this was to improve code readability and reusability. VideoProcessor class handles video processing as an object, while DetectionsManager is responsible for managing detections and trackings. This change has also led to the removal of the process_frame function, as its functionality is now encapsulated within VideoProcessor class methods. Moreover, the code now identifies zones in and out separately, thereby handling detections more distinctly and efficiently. Lastly, Polygon Zones are no longer differentiated by color.
2023-09-04 17:28:19 +02:00
SkalskiP 01d8b0316c "Improve detection and annotation in the supervision module
The change modifies the polygon zone verification method in `polygon_zone.py` to ensure `self.current_count` always returns an integer, preventing possible type errors in downstream processing. Two classes, `Trace` and `TraceAnnotator`, were added to `annotate.py`, providing functionality for trace annotation during detection. In `script.py`, optimized the trace annotation process by replacing the previous class implementations with the new ones from `annotate.py`, and also cleaned up and simplified polygon zone initiation and frame processing. This provides more efficient and robust video processing and zone-based detection features."
2023-09-03 18:25:28 +02:00
SkalskiP 6356a8ac3d Add Trace and TraceAnnotator classes to traffic_analysis script
Added two new classes to enhance traffic analysis over video frames. Trace class stores the positional information of different objects detected in each video frame for a specified length. TraceAnnotator class uses the information stored in Trace instance to annotate the motion path of detected objects on the video frames. Also, corrected the incorrect numpy import statement in the dependence code.
2023-09-03 17:11:25 +02:00
pre-commit-ci[bot] bbd9a955f8 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-09-02 19:45:43 +00:00
Onuralp SEZER 7b2bcf00ad fix: 🐛 sam xyxy check added
Signed-off-by: Onuralp SEZER <thunderbirdtr@fedoraproject.org>
2023-09-02 22:45:29 +03:00
Onuralp SEZER 775a3f2795 fix: 🐛 cpu().numpy() converts not require empty check
Signed-off-by: Onuralp SEZER <thunderbirdtr@fedoraproject.org>
2023-09-02 22:43:27 +03:00
Onuralp SEZER 5dae02e93e fix: 🐛 deepsparse model_stub to model_path docs fix
Signed-off-by: Onuralp SEZER <thunderbirdtr@fedoraproject.org>
2023-09-02 20:40:35 +03:00
Onuralp SEZER 666427df08 fix: 🐛 empty check for xyxy results for prevent ValueError and return as empty detection
Signed-off-by: Onuralp SEZER <thunderbirdtr@fedoraproject.org>
2023-09-02 20:36:00 +03:00
Hardik Davaandpre-commit-ci[bot] 78411b14b5 fixed empty detections for deepsparse (#348)
* fixed empty detections for deepsparse
* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-09-02 20:08:30 +03:00
SkalskiP 69ae5c6cd9 Add traffic analysis script
Introduced a python script for video processing using YOLO and ByteTrack. The script processes videos frame by frame and allows annotation based on model's detections. Additionally, the requirements.txt file was added along with .gitignore to exclude the data/ directory. The requirements file lists the necessary packages for the script to run.
2023-09-01 18:06:09 +02:00
dependabot[bot] 94d08bc9b5 Bump cryptography from 41.0.2 to 41.0.3 (#345)
Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.2 to 41.0.3.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/41.0.2...41.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-01 12:27:48 +03:00
Onuralp SEZER 227615b766 fix: 🐛 exclude examples folder from package
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-31 20:08:29 +03:00
Onuralp SEZER baff75332f docs: 📝 example guide visual rules and license rule added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-31 20:03:02 +03:00
Onuralp SEZER 434a695324 docs: 📝 example guidelines added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-31 19:54:08 +03:00
Piotr Skalski 693465d74d Merge pull request #342 from roboflow/main
Merge pull request #337 from roboflow/develop
2023-08-31 17:54:46 +02:00
Piotr Skalski f82f0fade6 Merge pull request #337 from roboflow/develop
`supervision-0.14.0` release
2023-08-31 15:18:14 +02:00
Piotr Skalski 474a4db248 Merge pull request #312 from onuralpszr/ci-release-clean
ci(pypi): 🚧 release pypi action automatic release per tag and main branch
2023-08-31 15:16:13 +02:00
SkalskiP 89a9307855 😊 make pre-commit happy 2023-08-31 15:13:11 +02:00
SkalskiP 5f94b6c23a 👊 supervision version to 0.14.0 2023-08-31 15:11:33 +02:00
SkalskiP 18c972e422 🪵 supervision-0.14.0 changelog update 2023-08-31 15:10:33 +02:00
Piotr Skalski 426c4498df Merge pull request #341 from roboflow/example/tracking
tracking supervision example
2023-08-31 14:57:18 +02:00
pre-commit-ci[bot] a3596584c7 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-31 12:54:34 +00:00
SkalskiP d8787080ec Merge remote-tracking branch 'origin/example/tracking' into example/tracking
# Conflicts:
#	examples/tracking/README.md
2023-08-31 14:54:18 +02:00
SkalskiP c9e10b8a04 Ready fo merge 💪 2023-08-31 14:54:02 +02:00
pre-commit-ci[bot] 597e12d723 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-31 11:13:06 +00:00
SkalskiP bb2d2c9f80 Initial commit with tracking supervision example 2023-08-31 13:06:27 +02:00
Onuralp SEZER c2a29d6577 ci: 🚧 pypi_test_username added and 'v' letter removed from tags
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-30 19:24:00 +03:00
Piotr Skalski 7a6da63a69 Update package version to 0.14.0b2 in pyproject.toml 2023-08-30 18:01:45 +02:00
Onuralp SEZER 796c17f085 ci: 🔥 branch push rule removed (#340)
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-30 19:00:28 +03:00
Onuralp SEZER ddd400ed24 ci(pypi): 🚧 test(a,b,rc) release to pypi github action added (#311)
* ci(pypi): 🚧 test(a,b,rc) release to pypi github action added

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>

* ci: 🚧 pypi_test_username added

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>

* chore: 🔥 v letter removed from tag for consistency to old tags

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>

---------

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-30 18:42:31 +03:00
Piotr Skalski 38b6ac9935 Update package version to 0.14.0b1 in pyproject.toml 2023-08-30 17:40:54 +02:00
dependabot[bot] 4a450ce2ed Bump tornado from 6.3.2 to 6.3.3 (#339)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.3.2 to 6.3.3.
- [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.3.2...v6.3.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-30 18:40:20 +03:00
dependabot[bot] 9c40f6f4aa Bump certifi from 2023.5.7 to 2023.7.22 (#338)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.5.7 to 2023.7.22.
- [Commits](https://github.com/certifi/python-certifi/compare/2023.05.07...2023.07.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-30 18:39:43 +03:00
dependabot[bot] f6766cb116 Bump jupyter-server from 2.7.0 to 2.7.2 (#332)
Bumps [jupyter-server](https://github.com/jupyter-server/jupyter_server) from 2.7.0 to 2.7.2.
- [Release notes](https://github.com/jupyter-server/jupyter_server/releases)
- [Changelog](https://github.com/jupyter-server/jupyter_server/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter-server/jupyter_server/compare/v2.7.0...v2.7.2)

---
updated-dependencies:
- dependency-name: jupyter-server
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-30 18:34:45 +03:00
Piotr Skalski 52f5d9ef63 Merge pull request #336 from roboflow/slicer_refactor
slicer refactor
2023-08-30 16:16:52 +02:00
SkalskiP 02117dd8ae Small fix in InferenceSlicer.__call__ 2023-08-30 16:08:56 +02:00
SkalskiP bddd32dbb5 refactor(inference_slicer): Update class and method docstrings 2023-08-30 15:56:15 +02:00
pre-commit-ci[bot] 07c4472eac fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-30 13:17:14 +00:00
SkalskiP 11541fe271 fix crop_image circular imports 2023-08-30 15:16:25 +02:00
SkalskiP 6d7de56c25 slicer refactor 2023-08-30 15:08:07 +02:00
Piotr Skalski 85de9b255b Merge pull request #282 from hardikdava/slicer
Slicer for small object detection
2023-08-30 14:48:46 +02:00
Piotr Skalski a3a19d93bc Merge pull request #329 from roboflow/add-links
Add explore more Roboflow open source projects section
2023-08-29 18:30:09 +02:00
Piotr Skalski 1eaccd3e87 Update README.md 2023-08-29 18:28:20 +02:00
James Gallagher eb1155cc17 commit feedback 2023-08-29 12:06:22 +01:00
Onuralp SEZER beb3a2ae9f docs: 📝 cosmetic fix and remove supervision itself from list
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-29 13:44:33 +03:00
James Gallagher b4efe69d1e add open source projects links 2023-08-29 11:28:45 +01:00
pre-commit-ci[bot] 3d6b5e75a8 chore(pre_commit): ⬆ pre_commit autoupdate (#328)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.285 → v0.0.286](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.285...v0.0.286)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-28 23:45:36 +03:00
Onuralp SEZER a7edf46717 docs: 📝 code quality duplication removal and improvement in current (#318)
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-24 22:09:15 +03:00
Tiago De Gaspari cd6a7fd186 Fix SAM video URL on README (#314) 2023-08-23 17:25:14 +03:00
Onuralp SEZER 82729a61b0 ci: 💚 pre-commit autoupdate (#307)
Signed-off-by: Onuralp SEZER <thunderbirdtr@fedoraproject.org>
2023-08-23 13:28:24 +03:00
Onuralp SEZER 30a51c72d6 ci(pypi): 🚧 release pypi action automatic release per tag and main branch
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-22 23:43:13 +03:00
Piotr Skalski 6b0be9c55b Merge pull request #296 from roboflow/fix/yoloexplorer
Image Path as keys for DetectionDataset
2023-08-22 16:47:41 +02:00
SkalskiP cf2c8cb364 Merge remote-tracking branch 'origin/fix/yoloexplorer' into fix/yoloexplorer 2023-08-22 16:38:11 +02:00
pre-commit-ci[bot] 7f8d091f50 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-22 16:38:02 +02:00
pre-commit-ci[bot] 36e03f3707 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-22 14:36:06 +00:00
SkalskiP f8baad3065 small simplifications 2023-08-22 16:34:54 +02:00
SkalskiP 18f7cad081 Merge remote-tracking branch 'origin/fix/yoloexplorer' into fix/yoloexplorer 2023-08-22 16:03:29 +02:00
SkalskiP bebb427a76 Merge remote-tracking branch 'origin/develop' into fix/yoloexplorer 2023-08-22 16:03:22 +02:00
Piotr Skalski 6f03b7ffd0 Merge pull request #309 from roboflow/add-cname
Create CNAME
2023-08-22 14:10:48 +02:00
James 06f41e83dd Create CNAME 2023-08-22 13:08:10 +01:00
Piotr Skalski 2ec33dbe9a Merge pull request #300 from ShubhamKanitkar32/null-check-fillPoly
Fix issue #299
2023-08-22 12:33:01 +02:00
pre-commit-ci[bot] b86f8ccdb8 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-22 10:25:41 +00:00
SkalskiP 7de50e6b0b 🛠️ fix ready for merging 2023-08-22 12:23:39 +02:00
Shubham Kanitkar 0f889935fd filtering out invalid point key 2023-08-21 16:38:30 -07:00
pre-commit-ci[bot] 61f972db79 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-21 19:08:35 +00:00
Hardik Dava 4b419fa19e pascal voc saving updated and unified 2023-08-21 21:08:24 +02:00
Hardik Dava 738d00cc89 yolo save methods updated 2023-08-21 20:57:30 +02:00
Piotr Skalski 9c53bc16ac Merge pull request #301 from onuralpszr/docs-detections-typo-fix
fix(docs): 📚🐛 deprecated for "from_yolov8" desc fix for detection
2023-08-20 21:29:04 +02:00
Onuralp SEZER 8a84463aaa fix(docs): 📚🐛 deprecated for "from_yolov8" desc fix 2023-08-20 16:01:23 +03:00
Shubham Kanitkar 9ca1204a4c Empty numpy array check 2023-08-18 13:09:55 -07:00
Derrick Mwiti 6a2203c07a Support DeepSparse Model Detection Results (#297)
* add Deepsparse model detection results and make sure results are arrays
2023-08-18 14:08:53 +03:00
pre-commit-ci[bot] 04df220737 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-18 10:48:25 +00:00
Hardik Dava 981bcc71ea Update coco.py 2023-08-18 12:48:09 +02:00
Hardik Dava 1d42992e6b Update core.py 2023-08-18 12:44:35 +02:00
pre-commit-ci[bot] 1c0434c6d8 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-18 10:42:33 +00:00
Hardik Dava b40eb5b47f Update utils.py 2023-08-18 12:42:23 +02:00
pre-commit-ci[bot] 98f12b60ea fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-18 10:39:49 +00:00
Hardik Dava 2c988ac269 Update core.py 2023-08-18 12:39:38 +02:00
Piotr Skalski f53c51ac8d Merge pull request #294 from arjun-234/develop
refactor: multi_predict()
2023-08-18 11:40:27 +02:00
Piotr Skalski 7426d52c4a Merge pull request #288 from roboflow/update-contributing
Update CONTRIBUTING.md file
2023-08-18 09:36:49 +02:00
Hardik Dava ad26e13704 Update core.py 2023-08-17 21:35:33 +02:00
Hardik Dava d8aecc4fe7 Update pascal_voc.py 2023-08-17 21:28:35 +02:00
Hardik Dava 7dbf279477 Update pascal_voc.py 2023-08-17 21:27:47 +02:00
Hardik Dava d59f9c4c74 Update coco.py 2023-08-17 21:26:36 +02:00
Hardik Dava 0d991009f7 changing image path as keys 2023-08-17 21:21:55 +02:00
pre-commit-ci[bot] 3a6de392fc fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-17 10:28:27 +00:00
James Gallagher 5bd78d11cc add license section 2023-08-17 11:28:14 +01:00
pre-commit-ci[bot] cd8541f948 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-17 10:18:18 +00:00
arjun-234 77a1faa7de refactor: multi_predict()
In this version, I have modified the code by combining three loops into a single loop within the same method. This enhancement has resulted in improved efficiency.
2023-08-17 15:45:53 +05:30
Piotr Skalski b47bc808f7 🛠️ Add "build with supervision" section to README.md 2023-08-16 20:11:04 +02:00
Piotr Skalski 16f3a61431 Merge pull request #235 from mayankagarwals/vocXMLOffsetFix
Fix Pascal VOC Offset
2023-08-16 13:26:46 +02:00
SkalskiP a051be0920 Merge remote-tracking branch 'origin/vocXMLOffsetFix' into vocXMLOffsetFix 2023-08-16 13:24:12 +02:00
SkalskiP 7d449abee2 🛠️ small fix 2023-08-16 13:23:10 +02:00
pre-commit-ci[bot] 12ec48105b fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-16 11:21:15 +00:00
SkalskiP afc240e6be 🛠️ small fix 2023-08-16 13:20:58 +02:00
Piotr Skalski c2b9745fe5 Merge pull request #289 from rizavelioglu/develop
fixes #285
2023-08-16 12:38:47 +02:00
Mayank Agarwal 4c5c15fcf4 flake format 2023-08-16 12:48:14 +05:30
Mayank Agarwal 47268c893c merge develop 2023-08-16 12:44:55 +05:30
Piotr Skalski 512f98f074 Merge pull request #278 from mayankagarwals/removeYoloV8Support
Remove usage `from_yolov8` from documentation and `README.md`
2023-08-15 15:16:59 +02:00
Mayank Agarwal 1f0b50645c demo changes 2023-08-15 18:01:24 +05:30
Mayank Agarwal 3c5f5f7741 Merge Develop 2023-08-15 17:58:31 +05:30
rizavelioglu 01956a2735 fixes #285 2023-08-15 14:22:37 +02:00
James Gallagher a2b8cf50d6 add docs 2023-08-15 13:21:50 +01:00
Piotr Skalski bfe65013ab Merge pull request #281 from mayankagarwals/classificationUltralytics
Feature/ultralytics all models for Classification
2023-08-15 12:52:25 +02:00
SkalskiP 824b2f6dc2 🛠️ small fix 2023-08-15 12:40:30 +02:00
8f70513edc Fix citation.cff file (#287)
* fix citation.cff
* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: Onuralp SEZER <thunderbirdtr@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-15 12:21:36 +03:00
James Gallagher 9167101e92 add develop branch guidance 2023-08-15 10:05:56 +01:00
pre-commit-ci[bot] b064356280 fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-15 09:03:18 +00:00
James Gallagher 4e5f87686f update contributing file 2023-08-15 10:02:22 +01:00
Mayank Agarwal 7bd6a107ce fix pre-commit-ci flake8 test 2023-08-15 11:02:02 +05:30
Mayank Agarwal a71d8c254a fix pre-commit-ci flake8 test 2023-08-15 11:01:24 +05:30
Mayank Agarwal 755365a92d Merge remote-tracking branch 'origin/classificationUltralytics' into classificationUltralytics 2023-08-15 10:57:55 +05:30
Mayank Agarwal 8c70197734 flake tests 2023-08-15 10:57:42 +05:30
pre-commit-ci[bot] 735de7de7e fix(pre_commit): 🎨 auto format pre-commit hooks 2023-08-15 05:21:04 +00:00
Mayank Agarwal dacce7aa5f embellish comment 2023-08-15 10:50:49 +05:30
Mayank Agarwal a5c6fe57f6 embellish comment 2023-08-15 10:47:16 +05:30
Onuralp SEZER b69d264ffe Merge pull request #286 from roboflow/paddle_typo_doc
docs: 📝 typo fix for PaddleDetection in args
2023-08-14 22:29:45 +03:00
Hardik Dava 92413d3cc2 Merge pull request #277 from hardikdava/feature/trace_annotators
tracer in consistent format
2023-08-14 21:20:56 +02:00
Mayank Agarwal 5f314cf21f remove redundant imports from comments 2023-08-15 00:48:12 +05:30
Onuralp SEZER bc4ba7f67c docs: 📝 typo fix for PaddleDetection in args
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-14 22:16:52 +03:00
Mayank Agarwal 47385f5a97 Merge remote-tracking branch 'origin/classificationUltralytics' into classificationUltralytics 2023-08-12 17:37:34 +05:30
Mayank Agarwal 234f6fa43b change examples to classification models 2023-08-12 17:37:00 +05:30
Mayank Agarwal 26402386aa Merge branch 'develop' into classificationUltralytics 2023-08-12 17:31:26 +05:30
Piotr Skalski 6f427b9ca4 Merge pull request #241 from onuralpszr/pre-commit-toml-file
feat: 🚀 pre-commit and formatted files
2023-08-12 13:43:20 +02:00
Hardik Dava 6a40516371 style fixing 2023-08-11 19:42:04 +02:00
Hardik Dava 1e1ce3c060 reverting mkdocs change 2023-08-11 19:38:32 +02:00
Hardik Dava 8fb8432e8b fixed style 2023-08-11 19:33:09 +02:00
Hardik Dava 0ed404602f recommit everything 2023-08-11 19:31:33 +02:00
Piotr Skalski d81d187da6 Merge branch 'develop' into pre-commit-toml-file 2023-08-11 15:21:28 +02:00
Mayank Agarwal b90f9a1677 doc fix 2023-08-11 18:27:04 +05:30
Mayank Agarwal 67a81afcfa format fix 2023-08-11 17:55:13 +05:30
Hardik Dava 5d71ab2961 Make with black again 2023-08-11 14:18:25 +02:00
Mayank Agarwal f0bc628eaf classification ultralytics 2023-08-11 17:47:17 +05:30
Hardik Dava 5c9e529743 Make with isort 2023-08-11 14:15:47 +02:00
Hardik Dava 3f7043e780 Make with Black 2023-08-11 14:13:17 +02:00
Hardik Dava c746a7f9e4 docs added 2023-08-11 14:08:52 +02:00
Hardik Dava 04629cd7f4 initial commit 2023-08-11 14:07:14 +02:00
SkalskiP f38c438c3c 🛠️ fix update_with_detections documentation 2023-08-11 11:22:55 +02:00
Mayank Agarwal d37c62d0cf remove yolo v8 support 2023-08-10 17:35:51 +05:30
Hardik Dava 8b764218eb tracer in consistent format 2023-08-10 10:00:26 +02:00
Piotr Skalski 4f9d8c02c7 Merge pull request #275 from roboflow/main
`supervision-0.13.0` release
2023-08-08 11:09:35 +02:00
Piotr Skalski 4f79d29934 supervision-0.13.0 release
`supervision-0.13.0` release
2023-08-08 11:07:24 +02:00
Piotr Skalski d86623600a Merge pull request #274 from roboflow/v0.13.0-changelog
v0.13.0 changelog
2023-08-07 22:53:04 +02:00
SkalskiP 475b2ceeea 🪵 changelog.md update 2023-08-07 22:50:57 +02:00
Onuralp SEZER b1325132d7 fix(docs): 🐛 doc formatting fix and exclude docs from black formatting
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-07 23:49:44 +03:00
Onuralp SEZER 3d3b3e6436 Merge additonal features and changes from 'origin/develop'
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-07 23:23:23 +03:00
Piotr Skalski 95661f3bb1 Merge pull request #273 from hardikdava/feature/ultralytics_tracker
ultralytics tracker support added
2023-08-07 22:11:23 +02:00
James Gallagher e6ab865a0c add changelog updates 2023-08-07 20:55:55 +01:00
Hardik Dava 63669e587a docstrings updated 2023-08-07 21:23:28 +02:00
Hardik Dava 2166bc23bc ultralytics tracker support added 2023-08-07 21:19:29 +02:00
SkalskiP 867dd78f58 📝 small ByteTrack docs updates 2023-08-07 17:51:08 +02:00
SkalskiP 255b34a183 Merge remote-tracking branch 'origin/develop' into develop 2023-08-07 16:43:02 +02:00
SkalskiP f7b59870ed 👊 bump supervision version 2023-08-07 16:42:44 +02:00
Piotr Skalski 7948234f94 Merge pull request #256 from roboflow/tracker_latest
Tracker latest
2023-08-07 16:29:44 +02:00
Piotr Skalski b5e83fa953 Merge pull request #271 from roboflow/tracker_cleanup
🧹 tracker cleanup
2023-08-07 16:16:54 +02:00
SkalskiP de536a3f8e 🖤 make black happy 2023-08-07 16:08:44 +02:00
SkalskiP 6741ae642a 🧹 more cleanup - remove more dead code + update docstring 2023-08-07 16:04:58 +02:00
SkalskiP 3834e5cdf9 🧹 more cleanup - refactor of joint_tracks, sub_tracks and remove_duplicate_tracks functions + documentation updates 2023-08-07 15:41:36 +02:00
SkalskiP aa77c396f7 🧹 more cleanup - update docs, remove dead if branches 2023-08-07 15:18:17 +02:00
SkalskiP bee6dec22c 🧹 more cleanup - solve poetry.lock and pyproject.toml 2023-08-07 14:59:39 +02:00
SkalskiP db2eede55b 🧹 more cleanup - remove more unused code, documentation improvements 2023-08-07 14:39:25 +02:00
SkalskiP ba880402ad 🧹 more cleanup - remove unused code 2023-08-07 14:20:36 +02:00
SkalskiP 62e097b559 🧹 more cleanup - dropping example scripts, plugging in ByteTrack docs, renaming variables 2023-08-07 14:11:51 +02:00
SkalskiP 451bb5e513 🧹 more cleanup 2023-08-07 13:40:51 +02:00
SkalskiP 330167fc73 🖤 make black happy 2023-08-07 13:30:21 +02:00
SkalskiP 1c23e806ba 🧹 initial cleanup 2023-08-07 13:28:45 +02:00
Piotr Skalski 2c31f8ec94 Add mAP example to README.md 2023-08-07 12:03:38 +02:00
Piotr Skalski a6c73f70ec Merge pull request #270 from roboflow/feature/map_reformat
🧹 small cleanup
2023-08-07 11:53:34 +02:00
SkalskiP 13ee396b20 🧹 small cleanup 2023-08-07 11:51:04 +02:00
SkalskiP 0f222375e2 🧹 small cleanup 2023-08-07 11:43:08 +02:00
SkalskiP a63f65fff3 🧹 small cleanup 2023-08-07 11:31:52 +02:00
Piotr Skalski 94f15a6e50 Merge branch 'develop' into tracker_latest 2023-08-07 10:07:36 +02:00
Piotr Skalski 525e1b00ec Merge pull request #236 from hardikdava/feature/map
Feature MeanAveragePrecision 🔥
2023-08-07 10:03:33 +02:00
SkalskiP 7f4662cf3d Final changes before merge 2023-08-07 09:58:46 +02:00
SkalskiP 3a31ea2986 More small changes 2023-08-06 13:24:51 +02:00
Hardik Dava e0ca4ca5e7 added docstring and minor reformatting 2023-08-06 12:30:13 +02:00
Hardik Dava 0fcc17ee11 Docstrings updated, return types fixed. 2023-08-06 12:23:00 +02:00
SkalskiP 52cbe684d4 🖤 MAke black happy 2023-08-06 11:57:20 +02:00
SkalskiP fce6e660d9 Small fixes and improvements. 2023-08-06 11:52:48 +02:00
SkalskiP c6f2bf27c1 🖤 make black happy 2023-08-06 11:12:42 +02:00
Piotr Skalski 523d76f86d Merge pull request #262 from danigarciaoca/patch-2
Update core.py (n, W, H) -> (n, H, W)
2023-08-04 19:39:40 +02:00
Daniel M. García-Ocaña 1ba1b67b47 Update core.py (n, W, H) -> (n, H, W)
Fix mask array shape in docstrings (n, W, H) -> (n, H, W) #254
2023-08-02 18:51:49 +02:00
dbroboflow a45cc865dd lint fixed 2023-08-02 14:59:08 +00:00
dbroboflow 4942d3d817 Merge pull request #260 from hardikdava/hot_fix/tracker
tracker optimized
2023-08-02 17:31:42 +03:00
hardik 514f61b885 initial optimized working version 2023-08-02 16:04:28 +02:00
Onuralp SEZER fc154e34fe chore(merge): merge latest develop
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-02 15:46:58 +03:00
Onuralp SEZER 6f0f3bc3bc chore(cleanup): 🧹 removing old code-formatting CI
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-02 15:44:56 +03:00
SkalskiP b40e2f228a 🧹 small cleanup 2023-08-02 14:40:16 +02:00
Onuralp SEZER 21062b22a4 fix: 🐛 test_pascal_voc data format fix for pytest
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-02 15:35:34 +03:00
Onuralp SEZER f9d3aaa67e fix(formatting): 🐛 merge latest code from develop for formatting
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-08-02 15:29:29 +03:00
Mayank Agarwal 58156fe61e rename test 2023-08-02 09:44:05 +05:30
Mayank Agarwal d170cbeda0 fix tests according to offset 2023-08-02 09:42:18 +05:30
Mayank Agarwal e131f8b197 fix polygon loading as per new PR 2023-08-01 23:32:40 +05:30
Mayank Agarwal f372fb69e9 reformat according to makefile 2023-08-01 23:29:09 +05:30
Mayank Agarwal 9f8291405a Merge branch 'develop' into vocXMLOffsetFix 2023-08-01 23:07:10 +05:30
dbroboflow da9d50b4e2 docs updated 2023-08-01 16:33:39 +00:00
dbroboflow 8f437107b8 scipy fixed 2023-08-01 13:23:42 +00:00
dbroboflow 2a9a1f05bb scipy fixed 2023-08-01 13:21:39 +00:00
dbroboflow 414e0327bc scipy added to pyproject.toml 2023-08-01 13:18:22 +00:00
dbroboflow f2d2827eee lint fixed again 2023-08-01 13:13:27 +00:00
dbroboflow 8eaa42abc6 lint fixed 2023-08-01 13:10:36 +00:00
dbroboflow aa50052ff5 mot20 deleted 2023-08-01 12:59:16 +00:00
dbroboflow a59e4dfe73 updated tracker 2023-08-01 12:30:52 +00:00
Piotr Skalski d6d4760fce Merge pull request #245 from kirilllzaitsev/feature/load_voc_segmentation
Add support for loading segmentation datasets in Pascal VOC format
2023-08-01 00:33:05 +02:00
Piotr Skalski f2bbb7c38c Merge pull request #222 from hardikdava/feature/ultralytics_all_models
Feature/ultralytics all models
2023-07-31 23:23:20 +02:00
SkalskiP fa44884ea7 💬 update YOLOv8 deprecated message 2023-07-31 23:16:20 +02:00
kirilllzaitsev 6fdabf0a96 extend tests for test_detections_from_xml_obj 2023-07-31 20:10:20 +02:00
kirilllzaitsev 26289e3743 drop load_pascal_voc_annotations_v1 2023-07-31 19:43:32 +02:00
kirilllzaitsev e2d1fe7695 remove artifacts 2023-07-31 19:42:53 +02:00
Hardik Dava 36d3e94a76 removing unwanted code 2023-07-31 18:51:02 +02:00
kirilllzaitsev c0c292ef82 lint 2023-07-31 15:19:39 +02:00
kirilllzaitsev 9462a3058d cast masks to bool only when creating a Detection obj 2023-07-31 13:15:40 +02:00
kirilllzaitsev 77c2afe559 fix mask dtype 2023-07-31 13:13:32 +02:00
kirilllzaitsev 95c8576aaf fix mask shape (N, W, H) -> (N, H, W) 2023-07-31 13:09:35 +02:00
kirilllzaitsev 12b3def032 upd docstring 2023-07-29 23:42:59 +02:00
kirilllzaitsev e8f616ba2f cleanup imports 2023-07-29 23:41:02 +02:00
kirilllzaitsev f8005b268b add docstrings 2023-07-29 23:39:42 +02:00
kirilllzaitsev e4ef57ec2d add test_detections_from_xml_obj 2023-07-29 23:27:33 +02:00
kirilllzaitsev 1642432541 add test_parse_polygon_points 2023-07-29 23:08:50 +02:00
kirilllzaitsev 1febd715e2 remove test_detections_to_pascal_voc. conversion to XML is tested, and approx of masks should be in another test suite 2023-07-29 22:53:24 +02:00
kirilllzaitsev 9a9d0c5259 refactor class_id assignment to Detections in VOC 2023-07-29 00:39:11 +02:00
kirilllzaitsev 446ae23d99 fix registering of empty detection 2023-07-29 00:38:08 +02:00
kirilllzaitsev a181826527 add test_object_to_pascal_voc 2023-07-28 23:13:11 +02:00
kirilllzaitsev 0a1212f23d fix with_masks defined but not used 2023-07-28 22:22:03 +02:00
kirilllzaitsev b0e0d074af fixes patch 2023-07-28 22:21:47 +02:00
Piotr Skalski 27e8e2d91b Merge pull request #191 from hardikdava/paddledet_support
Paddledet support
2023-07-28 14:57:21 +02:00
Piotr Skalski f4c17f09c8 Merge pull request #248 from roboflow/fix/pillow_version_update
📦 update `Pillow` version
2023-07-28 14:17:02 +02:00
SkalskiP b219961f6e 📦 update Pillow version 2023-07-28 13:45:29 +02:00
Onuralp SEZER 1645ecf4bc feat(pre-commit): flake8 and flake8 toml support extension added into pre-commit config and flake8 rules added into toml
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-27 09:36:19 +03:00
Onuralp SEZER d4001a5bfa feat(pre-commit): black and isort added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-27 09:25:12 +03:00
hardik 70fe357a4e fixed docstring 2023-07-26 18:13:24 +02:00
hardik 4b621c3c8d update 2023-07-26 13:58:53 +02:00
kirilllzaitsev 277a9cb6b9 import polygon_to_mask from supervision 2023-07-26 12:36:49 +02:00
kirilllzaitsev 6dde70eb0c move fixing of class_ids to load_pascal_voc_annotations 2023-07-26 10:46:31 +02:00
kirilllzaitsev a91118322b update docstrings 2023-07-26 10:45:46 +02:00
kirilllzaitsev 86b33ed558 add test_pascal template 2023-07-26 10:39:45 +02:00
kirilllzaitsev 3723eae50c update from_pascal_voc to match v2 loader 2023-07-26 10:21:17 +02:00
kirilllzaitsev d7e08d42eb add load_pascal_voc_annotations v2 2023-07-26 10:20:47 +02:00
Onuralp SEZER af82b4e2d2 fix: 🐛 200+ E501 doc error lines are fixed.
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-25 23:09:14 +03:00
hardik 9fef53f8a3 Merge remote-tracking branch 'origin/feature/map' into feature/map 2023-07-25 18:15:33 +02:00
hardik 8d8703a1c6 update 2023-07-25 18:15:26 +02:00
Hardik Dava 84a59a7e01 Merge branch 'roboflow:develop' into feature/map 2023-07-25 17:59:32 +02:00
hardik dec982200b update 2023-07-25 17:53:07 +02:00
hardik da6a324c1f update 2023-07-25 14:32:19 +02:00
Mayank Agarwal 7e391d0861 reformat 2023-07-25 16:35:36 +05:30
Mayank Agarwal c2018fa1ba increment polygon 2023-07-25 16:31:26 +05:30
Mayank Agarwal 6f7a50f41b increment polygon 2023-07-25 16:29:27 +05:30
Mayank Agarwal 204a04a85f refactor 2023-07-25 16:16:11 +05:30
Mayank Agarwal 5727fb5cb2 refactor 2023-07-25 16:15:20 +05:30
Mayank Agarwal a9dce586c2 refactor 2023-07-25 16:12:50 +05:30
Onuralp SEZER 57e17c34a0 feat: 🚀 pre-commit and initial formatted files
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-25 13:02:26 +03:00
Piotr Skalski 36cdcf1b12 Merge pull request #229 from hardikdava/docs/colab_request
Contributing guidelines added for colab request
2023-07-25 11:48:03 +02:00
Mayank Agarwal 3c64871c87 move increment within object_to_pascal_voc 2023-07-25 15:08:15 +05:30
Piotr Skalski 7ec6bcd916 Merge pull request #237 from onuralpszr/toml-license-classifer-fix
docs: 📝 MIT license classifier added
2023-07-25 11:08:34 +02:00
Hardik Dava da13366d0a Merge branch 'roboflow:develop' into feature/map 2023-07-25 10:46:58 +02:00
Onuralp SEZER 771a47e15d docs: 📝 MIT license classifier added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-25 08:30:37 +03:00
Mayank Agarwal fe00da70bf remove separate function 2023-07-25 09:29:32 +05:30
Piotr Skalski 7067741310 © bring back MIT as license in pyproject.toml 2023-07-24 23:31:55 +02:00
SkalskiP 117ba2fcc0 🧪 initial API tests 2023-07-24 23:17:21 +02:00
hardik f4dc515df1 update 2023-07-24 18:20:01 +02:00
hardik 0754cb75bd update 2023-07-24 17:53:19 +02:00
hardik 44a174f889 removed extra blank lines 2023-07-24 16:33:42 +02:00
hardik e9fbcf5dcb updated docstrings 2023-07-24 16:33:10 +02:00
hardik 9b6b74dc77 updated docstrings 2023-07-24 16:30:54 +02:00
hardik e249ea8644 update 2023-07-24 16:11:16 +02:00
Mayank Agarwal 64c446ae44 format code 2023-07-24 19:20:53 +05:30
Mayank Agarwal dfd26545f8 remove redundant comment 2023-07-24 19:16:52 +05:30
Mayank Agarwal a28755bbdb modularize 2023-07-24 19:16:24 +05:30
Mayank Agarwal 06d0646fb8 fix bbox for pascal voc loading and saving 2023-07-24 19:01:21 +05:30
Mayank Agarwal 8afd54a1cc format code 2023-07-24 18:23:06 +05:30
Mayank Agarwal cddab1cbe6 fix pascal voc offset 2023-07-24 18:08:39 +05:30
Piotr Skalski 8662671e6b 🛠️ fix coco segmentation data for polygon should be [polygon,] not polygon
Format coco output data as per standard
2023-07-24 14:07:10 +02:00
Hardik Dava c7c8e67ffd Update CONTRIBUTING.md 2023-07-23 22:56:57 +02:00
Hardik Dava b2b75f1105 Update CONTRIBUTING.md 2023-07-23 22:56:20 +02:00
hd 2f726ca795 Initial working version 2023-07-23 22:37:44 +02:00
hd 40a6fe1b5f update 2023-07-23 22:35:51 +02:00
hd 0cfad8407c update 2023-07-23 22:27:47 +02:00
Piotr Skalski 1d28b8ae26 Merge pull request #171 from hardikdava/advanced_box_annotator
cornered box annotator
2023-07-23 20:44:29 +02:00
SkalskiP cc2e08ac72 🖤 make black happy 2023-07-23 20:40:46 +02:00
SkalskiP ba03fefbd8 🧹‍💨 cleanup in progress 2023-07-23 20:39:12 +02:00
SkalskiP 4cd9f05fab 🧪 reformatted tests 2023-07-23 20:08:34 +02:00
SkalskiP 8066125f03 🧹‍💨 clean up 2023-07-23 19:43:51 +02:00
SkalskiP aa772dde8d 🧹‍💨 clean up 2023-07-23 19:42:47 +02:00
Mayank Agarwal 6587f39b0f accomodate for no polygon indices 2023-07-23 20:52:07 +05:30
hd 0f95c5d8b3 update 2023-07-23 16:44:14 +02:00
Piotr Skalski 800e39d64c 🪵 supervision-0.12.0 changelog updates
Add 0.12.0 changelog
2023-07-23 15:34:44 +02:00
SkalskiP 926f935bad 🪵 supervision-0.12.0 changelog updates 2023-07-23 15:31:45 +02:00
Piotr Skalski d3da6afa07 Merge pull request #223 from roboflow/fix/confusion_matrix_plot_bug
🛠️ #212 bug fix
2023-07-23 15:00:58 +02:00
Piotr Skalski 97ae155b1e Merge pull request #226 from hardikdava/fix/yolo_loading_bug
Fix yolo dataset background instance loading
2023-07-23 14:51:11 +02:00
Piotr Skalski 24f013757f Update README.md 2023-07-23 14:12:16 +02:00
SkalskiP 532245640b ⚡ Make convert_detections_to_tensor static method 2023-07-23 13:41:57 +02:00
Hardik Dava 864764e665 Update core.py 2023-07-23 13:37:50 +02:00
hd ca348401d5 Fix for loading background image loading 2023-07-23 13:33:01 +02:00
hd 6def59444d initial commit 2023-07-23 13:27:21 +02:00
Mayank Agarwal bc0a38500d Format coco output data as per standard 2023-07-23 15:15:09 +05:30
hd 300ebea8ec Merge branch 'main' of github.com:hardikdava/supervision into main 2023-07-23 11:37:43 +02:00
SkalskiP 5cc7da289e 🛠️ #212 bug fix 2023-07-23 01:22:03 +02:00
hd 5edd58d6de ready for review 2023-07-22 20:45:41 +02:00
Hardik Dava 5729f8ffc3 Merge branch 'roboflow:main' into feature/ultralytics_all_models 2023-07-22 20:09:07 +02:00
Piotr Skalski 7a07517750 🧮 Add confusion matrix to README.md 2023-07-22 18:12:26 +02:00
Piotr Skalski 699fc01a0c Merge pull request #218 from onuralpszr/devtools
feat: 🛠  adding python dev tools into toml file
2023-07-22 17:42:58 +02:00
Piotr Skalski 43339e380a Merge pull request #220 from onuralpszr/docs-py-ver-fix
docs: 📝 Update python version for supervision version 0.12.0
2023-07-22 17:37:11 +02:00
Piotr Skalski 89a33cda8a Merge pull request #219 from onuralpszr/development-version-213
fix: 🐛 package version control if PackageNotFoundError occured
2023-07-22 17:26:55 +02:00
Piotr Skalski e84302b43f Merge pull request #217 from roboflow/fix/confusion_matrix_errors
🛠️ fix `detections_to_tensor`
2023-07-22 17:22:55 +02:00
Hardik Dava 10d3e3d48f Merge branch 'roboflow:main' into feature/ultralytics_all_models 2023-07-22 16:14:07 +02:00
hd 3c59618152 ready for review 2023-07-22 16:12:31 +02:00
hd e882c398b7 Added method for complete ultralytics model usage 2023-07-22 16:10:31 +02:00
Onuralp SEZER 93ab21dda7 docs: 📝 python 3.11 pypi classifier added
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 16:16:57 +03:00
Onuralp SEZER effa1415b5 docs: 📝 Update python version for supervision version 0.12.0
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 16:14:03 +03:00
hd e9e075acaf Merge branch 'main' of github.com:hardikdava/supervision into main 2023-07-22 10:44:12 +02:00
Onuralp SEZER 61e9584f5a fix: 🐛 package version control if PackageNotFoundError occured
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 10:40:17 +03:00
Onuralp SEZER 2a640d3ab6 feat: 🛠 adding develop branch into CIs
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 09:31:07 +03:00
Onuralp SEZER 2f478f4d06 feat: 🛠 adding python dev tools into toml file
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-22 09:24:28 +03:00
SkalskiP 0c20830be5 🖤 make black happy 2023-07-22 01:05:07 +02:00
SkalskiP 6c51c4a1b4 🛠️ fix detections_to_tensor 2023-07-22 00:42:26 +02:00
Piotr Skalski d6883caadc Merge pull request #214 from onuralpszr/readme-installation-docs
docs:  📝 update installation and source installation methods
2023-07-21 23:52:51 +02:00
SkalskiP f1f2728e0d 📝 updated install guide 2023-07-21 23:11:03 +02:00
Onuralp SEZER b6b7663ea7 docs: 📝 update installation and source installation methods
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-21 23:46:59 +03:00
SkalskiP 3d074fa213 🧮 Confusion Matrix docs update 2023-07-21 19:51:19 +02:00
James Gallagher 9fd267acb2 add changelog updates 2023-07-21 13:30:56 +01:00
Piotr Skalski 727451b382 Merge pull request #207 from onuralpszr/docs-ci-action-version-update
chore: ✨ update github action versions (actions/checkout@v3, actions/setup-python@v4)
2023-07-21 14:17:09 +02:00
Piotr Skalski 1b5e6db196 Merge pull request #159 from Ucag/feature/unicode-label-support
Add unicode label support
2023-07-21 14:13:40 +02:00
Onuralp SEZER 5f432fbc93 chore: ✨ update github action versions (actions/checkout@v3, actions/setup-python@v4)
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-21 14:46:20 +03:00
Piotr Skalski ea4638f5f5 Merge pull request #177 from kirilllzaitsev/feature/conf_matrix
Add ConfusionMatrix to EvaluationAPI
2023-07-21 13:31:26 +02:00
hd fc4bb27aa8 removing extra spacing 2023-07-21 12:54:44 +02:00
hd ace6ce363f Tests added for anchor fn 2023-07-21 12:54:19 +02:00
kirilllzaitsev 4b38634c63 add comments to _drop_extra_matches 2023-07-21 12:34:43 +02:00
hd 985608146c Docstrings updated 2023-07-21 12:13:46 +02:00
hd f0cef99de7 ready for review 2023-07-21 11:53:44 +02:00
kirilllzaitsev 04b7badcbe remove mock detection dataset 2023-07-21 11:44:28 +02:00
kirilllzaitsev 54524a4a1f drop docstring in _drop_extra_matches 2023-07-21 11:43:57 +02:00
kirilllzaitsev 3f55e7a79a updates patch 2023-07-21 11:42:50 +02:00
kirilllzaitsev 7a782b1ac3 cleanup test_detection 2023-07-20 15:59:50 +02:00
kirilllzaitsev 6016338a4f remove test_from_detections 2023-07-20 15:48:38 +02:00
kirilllzaitsev 8d131ad3e8 add convert_detections_to_tensor 2023-07-20 15:48:23 +02:00
kirilllzaitsev ae55e9c908 add test_convert_detections_to_tensor 2023-07-20 15:48:08 +02:00
kirilllzaitsev 5cb3c603cd remove test_benchmark 2023-07-20 15:39:55 +02:00
kirilllzaitsev 56e0b77211 add _validate_input_tensors 2023-07-20 15:35:59 +02:00
kirilllzaitsev bc9fe6f690 fix CM plot colors 2023-07-20 13:24:25 +02:00
kirilllzaitsev fb211a6160 add IoU tests 2023-07-20 10:14:06 +02:00
kirilllzaitsev 37b7224380 black 2023-07-20 10:13:58 +02:00
kirilllzaitsev 314454c7a7 add test cases to test_from_tensors 2023-07-20 09:57:44 +02:00
kirilllzaitsev ac39bdd487 add empty (preds,targets) test 2023-07-20 09:27:29 +02:00
kirilllzaitsev fc1e899575 fix failing tests (reduce threshold to 0.2) 2023-07-20 09:17:47 +02:00
kirilllzaitsev fccbe6baa2 add test_from_tensors 2023-07-20 00:22:12 +02:00
kirilllzaitsev d21154e882 fix _evaluate_detection_batch. use np.ndarray 2023-07-20 00:21:06 +02:00
kirilllzaitsev 1b3af74756 add ConfusionMatrix.from_tensors 2023-07-19 23:57:05 +02:00
kirilllzaitsev dc1083711a remove ConfusionMatrix.from_matrix 2023-07-19 23:13:19 +02:00
kirilllzaitsev 8df5c5b0c4 rm sans-serif from CM plot 2023-07-19 23:07:28 +02:00
kirilllzaitsev abbc0e44b0 fixes patch 2023-07-19 23:01:50 +02:00
kirilllzaitsev 950744111a fix type annotations 2023-07-19 22:54:39 +02:00
hd c4d296fbc6 Ready for review 2023-07-19 10:58:39 +02:00
hd 9c4555a800 Added extra anchor points calculation 2023-07-19 09:47:49 +02:00
Piotr Skalski 20954a2996 Merge pull request #180 from onuralpszr/python-toml-file-convert
Introduce pyproject toml file with optional install method and dropping python 3.7 support
2023-07-18 22:59:55 +02:00
Piotr Skalski 22d82c306f Merge pull request #173 from hardikdava/mmdetection_support
Mmdetection support
2023-07-18 15:27:30 +02:00
Onuralp SEZER 399121b65e fix: 🐛 lower pillow version for google-colab environment
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-18 16:24:17 +03:00
Piotr Skalski 1b9c65bdf8 🛠️ Merge pull request #192 from hardikdava/fix/coco_empty_fix
🛠️ Fixing empty detection coco dataset
2023-07-14 15:26:28 +02:00
SkalskiP 3a8c4d63ea 🧪 tests added 2023-07-14 15:06:55 +02:00
hardik bfe058c307 Fixing empty dataset 2023-07-13 20:19:55 +02:00
hd 12ce5feab7 paddledet refactored 2023-07-10 18:13:50 +02:00
hd 186bfc44fa paddledet initial support added 2023-07-10 18:03:56 +02:00
kirilllzaitsev 2c0905ba28 reformat 2023-07-08 12:29:50 +02:00
kirilllzaitsev fd717ea749 extend docs 2023-07-08 12:29:16 +02:00
kirilllzaitsev 40853350fd make plot() return figure 2023-07-08 12:11:24 +02:00
kirilllzaitsev fdd8127293 add source 2023-07-08 11:58:51 +02:00
kirilllzaitsev 21a480b483 refactor, remove duplicated 2023-07-08 11:54:25 +02:00
kirilllzaitsev 1b013f85e5 remove num_classes attr of ConfusionMatrix 2023-07-08 11:40:35 +02:00
kirilllzaitsev 36b48def89 rename vars, refactor ternary 2023-07-08 11:40:07 +02:00
kirilllzaitsev b7f72b79de add test_benchmark 2023-07-08 10:06:14 +02:00
kirilllzaitsev 141428310a add dummy_detection_dataset to utils 2023-07-08 10:05:52 +02:00
kirilllzaitsev 8f39df2fc0 add test_from_matrix 2023-07-07 22:54:22 +02:00
kirilllzaitsev 5107dd359f add test_drop_extra_matches 2023-07-07 22:49:47 +02:00
hd 4b423fd402 Fixed typo in annotator 2023-07-07 22:40:30 +02:00
kirilllzaitsev d480ff5be2 add test_evaluate_detection_batch 2023-07-07 22:34:23 +02:00
kirilllzaitsev 2b876bdd64 add from_matrix() 2023-07-07 22:29:17 +02:00
kirilllzaitsev 476be527e5 add test_from_detections 2023-07-07 22:28:21 +02:00
Onuralp SEZER f78ac90464 feat(package): 📦 opencv-python-headless set as default
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-07 20:53:58 +03:00
hd 27efbc044d Adding EllipseAnotator and added backward compatibility for box and mask annotator 2023-07-07 19:18:12 +02:00
hd 2a2c259964 🔥 Consistent code style 2023-07-07 09:37:03 +02:00
kirilllzaitsev d4ea295f09 add empty test templates 2023-07-06 22:46:45 +02:00
kirilllzaitsev 9c5c296fb6 docs 2023-07-06 22:43:43 +02:00
kirilllzaitsev 85c4d5a644 rm extra colorbar configs 2023-07-06 22:30:37 +02:00
kirilllzaitsev ad06937653 set figsize as plot() arg 2023-07-06 22:29:22 +02:00
kirilllzaitsev e01e495c92 rm test setup from plot() 2023-07-06 22:27:53 +02:00
kirilllzaitsev 6784c78b4f refine plot() 2023-07-06 22:27:21 +02:00
kirilllzaitsev 4375e19245 add benchmark() 2023-07-06 09:37:00 +02:00
Onuralp SEZER a75a92708c feat(package): 📦 pillow library added into toml file as deps
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-06 09:38:55 +03:00
Onuralp SEZER c83e57c638 fix: 📝 rename extras group into desktop and headless
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-06 09:35:27 +03:00
hd fcde1cea9c 🔥 working 2023-07-05 18:37:05 +02:00
hd 8614984f8f Initial commit for Detection Dataset Accumulator 2023-07-05 18:33:17 +02:00
hd c705d50e74 Marked as ready to review 2023-07-05 15:39:22 +02:00
hd d5b8a3aa1a Adding cornered box annotator and pillow label annotator 2023-07-05 15:01:16 +02:00
Onuralp SEZER cc81ae723b feat(version): ✨ dynamic version from __package__
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 15:37:50 +03:00
Onuralp SEZER f7a4a788ed docs: 📝 removing python 3.7 metadata pyproject
docs: 📝 change min python 3.7 to 3.8 in readme

Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 15:21:35 +03:00
kirilllzaitsev 35d9ee7bad non-int ticks 2023-07-05 14:08:39 +02:00
Piotr Skalski abbde887dc Merge pull request #176 from roboflow/fix/coco_incorrect_class_id_processing
🛠️ fix incorrect COCO class id loading
2023-07-05 11:40:34 +02:00
SkalskiP 4f5f87acb8 1️⃣ changing logic of to_coco to start image_id and annotation_id from 1 2023-07-05 11:37:44 +02:00
hd 3f78150a1d Fixing docstrings and model instructions 2023-07-05 10:11:55 +02:00
hd 183ba1e809 Fixing conversion 2023-07-05 10:09:04 +02:00
hd 597c776c39 Fixing typo in variable 2023-07-05 10:06:23 +02:00
hd 5291706bc1 revert to original method 2023-07-05 09:59:16 +02:00
hd a50e47e7c0 tracked detection method added along with trackstorage 2023-07-05 09:20:50 +02:00
Onuralp SEZER c9d85477f1 ci(pypa-action): introduce pypa github action for uploading to pypi and pypi-test
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 01:39:47 +03:00
Onuralp SEZER 19271f362e fix: toml file version fix and removal of setup.py and adjusment on makefile
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 00:33:15 +03:00
Onuralp SEZER 9e1e9fc2fc ci(removal): 🧪 removing python 3.7 from test and update other action versions
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 00:04:41 +03:00
Onuralp SEZER 629744dee1 feat(pyproject): 🐍 introduce pyproject toml file
Signed-off-by: Onuralp SEZER <thunderbirdtr@gmail.com>
2023-07-05 00:04:04 +03:00
kirilllzaitsev c9d2b52900 minor doc update 2023-07-04 22:07:18 +02:00
kirilllzaitsev 43cca52a58 add _drop_extra_matches from onemetric 2023-07-04 22:04:38 +02:00
kirilllzaitsev e1ee80cb37 add docstring to from_detections 2023-07-04 22:03:10 +02:00
kirilllzaitsev 6a8f3fc03b add detection module 2023-07-04 21:47:56 +02:00
hd d9f6c5c73c tracked detection method added along with trackstorage 2023-07-04 20:42:33 +02:00
SkalskiP c4b33fe1d6 ✅ ready for test and review 2023-07-04 17:31:33 +02:00
Hardik Dava 65a93f8287 Merge pull request #2 from hardikdava/redesigned_annotators
updated code
2023-07-04 16:16:53 +02:00
hd 1bcf8346dc updated code 2023-07-04 16:12:30 +02:00
Hardik Dava aba6ff57a9 Merge pull request #1 from hardikdava/redesigned_annotators
Redesigned annotators
2023-07-04 13:47:15 +02:00
hd b5e1342038 Updating refactored annotators 2023-07-04 13:38:10 +02:00
hd ebfaee22eb updated codebase 2023-07-03 17:30:53 +02:00
hd 9c062aac29 Support for openmmlab detections 2023-07-03 17:28:40 +02:00
SkalskiP 64d223b76d 🖤 make black happy 2023-07-02 23:28:28 +02:00
SkalskiP 21968e53c6 ✏️initial PR for redesigned annotators 2023-07-02 23:25:43 +02:00
hd 9b77b2a1c6 initial commit for advanced annotator 2023-06-30 18:25:13 +02:00
SkalskiP 3e4cb6df42 🪵 updating change log before 0.11.1 release 2023-06-29 15:01:35 +02:00
Piotr Skalski 5fdbb70c60 Merge pull request #165 from roboflow/fix-classification
Fix top_k processing in as_folder_structure
2023-06-29 14:53:43 +02:00
James Gallagher 9aee232d04 fix top_k processing in as_folder_structure 2023-06-29 13:50:47 +01:00
SkalskiP 9adc953780 📚 small documentation update 2023-06-28 23:01:32 +02:00
Piotr Skalski 4ace93f9ab Merge pull request #161 from roboflow/0.11.0-release
`0.11.0` release
2023-06-28 22:50:05 +02:00
SkalskiP c1d3ab2944 🪵 update changelog.md 2023-06-28 22:43:08 +02:00
SkalskiP 2072eeac12 Merge branch 'main' into 0.11.0-release 2023-06-28 22:35:31 +02:00
Piotr Skalski c74efc4908 Merge pull request #162 from hardikdava/video_generator_extended
video generator with start and end position feature
2023-06-28 22:34:14 +02:00
SkalskiP 08f399cf19 🖤 make black happy 2023-06-28 20:07:16 +02:00
SkalskiP 17020b23de try alternative implementation to make it faster 2023-06-28 20:02:03 +02:00
SkalskiP cd929525f6 fixed code break when end was None 2023-06-28 19:56:40 +02:00
SkalskiP bb18de8dce ready for tests 2023-06-28 19:47:23 +02:00
SkalskiP dec96aec4d ready for tests 2023-06-28 19:45:31 +02:00
hd ee45c684d0 video generator with start and end position feature 2023-06-28 18:58:46 +02:00
SkalskiP ea517642f0 🖤 make black happy 2023-06-28 17:12:24 +02:00
SkalskiP cdc08c0455 0.11.0 release 2023-06-28 17:07:02 +02:00
SkalskiP a0c808cfdf 📚 documentation cleanup 2023-06-28 14:52:13 +02:00
Piotr Skalski f146e88ceb Merge pull request #158 from hardikdava/dataset_merge
Dataset merge
2023-06-28 14:19:15 +02:00
SkalskiP 66d29015d6 🖤 make black happy 2023-06-28 14:17:35 +02:00
SkalskiP 46cb82797e ready for merge 2023-06-28 14:16:59 +02:00
SkalskiP 194ff0aaad small fix 2023-06-28 14:07:27 +02:00
SkalskiP a579ed46c6 ready for final tests 2023-06-28 13:36:10 +02:00
dong.li ddabe04409 add unicode label support 2023-06-28 09:56:53 +08:00
Piotr Skalski 8918783702 Update Development Status in setup.py 2023-06-28 01:14:05 +02:00
Piotr Skalski 6adb0f019d 🪪 fix incorrect license in setup.py 2023-06-28 01:04:18 +02:00
Piotr Skalski 84ae12768a Merge pull request #157 from hardikdava/yolo_class_name_fix
Yolo class name fix
2023-06-28 00:54:25 +02:00
SkalskiP 9cbec9fc94 ready for final tests 2023-06-28 00:39:21 +02:00
hd ed12daf77d Updated docstrings 2023-06-27 22:27:16 +02:00
hd 84cfa9609b Initial dataset merging code 2023-06-27 21:58:14 +02:00
hd f2eccae88e Fixed docstring 2023-06-27 17:45:43 +02:00
hd 55c8295755 Fixed data.yaml store and format issue 2023-06-27 17:26:07 +02:00
hd 7849033da3 Fixed yaml file reading code 2023-06-27 16:46:28 +02:00
Piotr Skalski 2e443c80c7 Merge pull request #154 from roboflow/fix/149_typo_in_filename
🛠️ fix #149 - typo in filename
2023-06-27 15:28:40 +02:00
SkalskiP 006caa0b9b 🛠️ fix #149 - typo in filename 2023-06-27 15:25:24 +02:00
Piotr Skalski 34e45bed5e 🥥 add from_coco and as_coco to README.md 2023-06-27 14:06:29 +02:00
Piotr Skalski 0d39de898a Merge pull request #150 from hardikdava/main
🥥 COCO dataset import / export support
2023-06-27 13:58:34 +02:00
SkalskiP 782e7e5a39 ready for final tests 2023-06-27 13:53:27 +02:00
SkalskiP b40265db9e small fix 2023-06-27 03:02:57 +02:00
SkalskiP de1ceff065 small fix 2023-06-27 03:02:24 +02:00
SkalskiP 5bf78e5505 naming refactor 2023-06-27 02:52:20 +02:00
SkalskiP f63336a824 save_coco_annotations logic simplification 2023-06-27 02:44:40 +02:00
SkalskiP 4d82460358 naming refactor 2023-06-27 02:09:56 +02:00
SkalskiP 540efc80ce classes_to_coco_categories, coco_categories_to_classes refactor + tests 2023-06-27 01:50:58 +02:00
hd beea21a97d Resolved issue, code quality checked, ready for review 2023-06-26 20:05:15 +02:00
hd 484e3d4afd COCO segmentation support added 2023-06-26 13:08:33 +02:00
hd ab9728cca2 Updated doc-strings 2023-06-25 13:40:41 +02:00
hd 769b6b967c Tested and working version 2023-06-25 13:31:55 +02:00
hd 253113fa88 refactored code 2023-06-24 18:33:18 +02:00
hd 3b2902c87c COCO dataset import export interface,
working for object detection
2023-06-24 15:48:57 +02:00
Piotr Skalski 9cff624e15 Merge pull request #136 from roboflow/0.10.0-release
`0.10.0` release
2023-06-14 16:50:24 +02:00
SkalskiP 584c4fef82 🪵 changelog.md updates before 0.10.0 release 2023-06-14 16:49:10 +02:00
Piotr Skalski 282aa2c5d3 Merge pull request #110 from roboflow/rf-polygon-loading
Add semantic segmentation polygon loading from Roboflow
2023-06-14 16:40:49 +02:00
SkalskiP 453d4df068 🧪 refactor + some tests 2023-06-14 16:38:08 +02:00
SkalskiP 4d4b38b292 🖤 make black happy 2023-06-14 15:54:40 +02:00
SkalskiP 13e8afa996 👌 final changes before 0.10.0 release 2023-06-14 15:52:20 +02:00
James Gallagher dd4cae328b add missing import from merge, run linters 2023-06-14 14:21:15 +01:00
James ed7103851a Merge branch 'main' into rf-polygon-loading 2023-06-14 14:14:03 +01:00
James Gallagher ca40b001dc fix bug in mask loading 2023-06-14 14:08:53 +01:00
Piotr Skalski de3ccc5c5d Merge pull request #125 from roboflow/add-classification
Add ClassificationDataset API
2023-06-14 14:11:27 +02:00
SkalskiP 1b6d02898c 8️⃣ Classifications.from_yolov8 added 2023-06-14 14:07:14 +02:00
SkalskiP 5ac92bc43b 🧪 ready for final tests 2023-06-14 13:15:28 +02:00
Piotr Skalski 86d9293770 Merge pull request #135 from dankresio/patch-1
Update stride definition
2023-06-14 11:08:11 +02:00
Daniel M. García-Ocaña 0e943e673e Update stride definition 2023-06-14 10:53:06 +02:00
James Gallagher 234e376fca respond to feedback 2023-06-14 09:48:00 +01:00
SkalskiP 54b7cbeb70 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	README.md
2023-06-13 17:47:25 +02:00
SkalskiP d000292eb2 ✨ new demo notebook 2023-06-13 17:45:22 +02:00
SkalskiP b551e6d410 ✨ new demo notebook 2023-06-13 17:34:37 +02:00
James Gallagher 138c271af0 update as_multiclass logic 2023-06-13 14:39:32 +01:00
James Gallagher d22940ce30 add classifications to table of contents 2023-06-13 14:36:31 +01:00
James Gallagher 06da0e70d2 remove unused files 2023-06-13 14:35:52 +01:00
James Gallagher 5d8b378f35 add test cases for get_top_k, add docs for Classifications and ClassificationsDataset 2023-06-13 14:35:32 +01:00
James Gallagher a4830389de fix import 2023-06-13 13:24:11 +01:00
James Gallagher d9a2efc64b remove unused imports, fix top k bug, fix import paths 2023-06-13 13:16:48 +01:00
James Gallagher f91b844b7d fix ClassificationDataset import 2023-06-13 13:12:04 +01:00
James Gallagher dc45d47e7e run linter 2023-06-13 12:48:34 +01:00
James Gallagher b5dedf09cd refactor code, respond to feedback 2023-06-13 12:48:07 +01:00
James Gallagher eb1ae1b790 respond to feedback 2023-06-13 12:00:46 +01:00
James Gallagher f2f8a564e0 run linter 2023-06-13 11:38:19 +01:00
James Gallagher 140a219091 generalise as_multiclass_folder_structure 2023-06-13 11:38:02 +01:00
James Gallagher 86af9b9dce continue work on classifications 2023-06-13 11:13:38 +01:00
Piotr Skalski 4366e4480f 💜Built with supervision added to README.md 2023-06-12 19:06:08 +02:00
James Gallagher b66e9c8c32 change val to valid, remove example code 2023-06-08 11:51:41 +01:00
James Gallagher d0d5626156 run linter 2023-06-07 16:29:52 +01:00
James Gallagher c661bf250d add classification dataset modules 2023-06-07 16:25:22 +01:00
SkalskiP ccb32f8aa1 🪵 Update changelog. 2023-06-07 12:49:53 +02:00
Piotr Skalski 97b9edcbec Merge pull request #122 from roboflow/feature/75_crop_bounding_boxes_as_separate_image
feature/75_crop_bounding_boxes_as_separate_image
2023-06-07 12:20:00 +02:00
SkalskiP 6b52863c9f 📚 Documentation update 2023-06-07 12:15:14 +02:00
SkalskiP 2fcc035413 🖼️ Add utility to crop image based on xyxy box 2023-06-07 12:12:39 +02:00
Piotr Skalski 7c8f16bf0f Merge pull request #121 from roboflow/fix/106_better_handling_of_wrong_data_types_of_mask
🛠️ Fix #106 - now you can pass polygon with int and float vertices.
2023-06-07 11:58:43 +02:00
SkalskiP 1870670c77 🛠️ Fix #106 - now you can pass polygon with int and float vertices. 2023-06-07 11:54:50 +02:00
SkalskiP 802347f4d8 🧹 cleanup before supervision 0.9.0 release 2023-06-07 11:47:34 +02:00
Piotr Skalski 920019c887 Merge pull request #120 from roboflow/feature/new_video_utils
feature/new_video_utils
2023-06-07 00:26:05 +02:00
SkalskiP 477b9a8f38 🛠️ fix ImageSink.__enter__ 2023-06-06 21:58:26 +02:00
SkalskiP 14f62deacf 📚 ImageSink documentation updated 2023-06-06 20:13:33 +02:00
SkalskiP 08eb513594 📚 ImageSink documentation added 2023-06-06 19:58:30 +02:00
SkalskiP a1615fd275 🎬 small VideoSink documentation update. + 🖼️ ImageSink added 2023-06-06 19:54:18 +02:00
SkalskiP ce979216f2 🐍 small setup.py and README.md updates 2023-06-06 18:01:06 +02:00
SkalskiP 95f94319f8 😷 Move YOLOv8 mask processing code to separate extract_yolov8_masks util 2023-06-06 17:50:14 +02:00
SkalskiP 98f60c24d3 😷 Add stride to get_video_frames_generator function. Initial from_yolov8 with masks refactor. 2023-06-06 17:35:40 +02:00
Piotr Skalski 1c043d6cc7 Merge pull request #101 from hardikdava/main
Adding yolov8 masks to detections
2023-06-06 10:52:40 +02:00
Piotr Skalski 86aee5ed98 Merge pull request #118 from roboflow/feature/115_how_to_select_detections_by_index
feature/115_how_to_select_detections_by_index
2023-06-05 11:34:35 +02:00
SkalskiP 0d38b95da7 📚update documentation + 🖤 make black happy 2023-06-05 11:31:52 +02:00
SkalskiP 97892e3138 ⛏️pick Detections by index 2023-06-05 11:01:14 +02:00
Piotr Skalski 17a42bc728 Merge pull request #117 from lomnes-atlast-food/update-readme-loop-dataset
Update README.md example for looping through dataset
2023-06-02 08:25:17 +02:00
Stephen Lomnes be65ba7370 Update README.md
Example in README documentation out of date for v.0.8.0.  __iter__() yields a length 3 tuple.  Example shows a length 2 tuple.

Change proposed: add image_name to example.

Reference: From API docs

```
__iter__() ¶
Iterate over the images and annotations in the dataset.

Yields:

Type	Description
Iterator[Tuple[str, np.ndarray, Detections]]	
Iterator[Tuple[str, np.ndarray, Detections]]: An iterator that yields tuples containing the image name, the image data, and its corresponding annotation.
```
2023-06-01 09:08:33 -04:00
Hardik Dava 72d86ba180 Fixing for uneven image size 2023-05-24 17:27:35 +02:00
Hardik Dava fba83ec371 Fixed minor typo bug 2023-05-24 17:22:16 +02:00
Hardik Dava 4a81a17ddb yolov8 masks plot using with letterbox concept
- gain and pads are calculated using yolov8 results.
- resize masks to input image size
2023-05-24 15:02:00 +02:00
Hardik Dava 30d253d5cc Fixed import error 2023-05-23 18:40:48 +02:00
James Gallagher 544f9cb0e2 incorporate feedback 2023-05-23 13:02:58 +01:00
James Gallagher 84928a2da9 add rf segmentation polygon loading 2023-05-23 12:02:51 +01:00
Hardik Dava 08ae0b2fc7 using basic functions from cv2 2023-05-20 19:22:30 +02:00
Piotr Skalski 0261bec2ce Merge pull request #104 from roboflow/SkalskiP-patch-1
📚 Update README.md
2023-05-18 01:10:05 +02:00
Piotr Skalski 5a4cb1b302 Update README.md 2023-05-18 01:08:49 +02:00
Piotr Skalski 91655f9609 🖌️ final README.md updates 2023-05-18 01:07:36 +02:00
Piotr Skalski 42d4079b23 🎬 video clap section 2023-05-18 01:04:12 +02:00
Piotr Skalski f03a47a5df 🔥 quick start section 2023-05-18 00:59:01 +02:00
Piotr Skalski c01efeaaca 📚 Update README.md 2023-05-17 23:08:28 +02:00
SkalskiP 5489d405c6 📚 DetectionDataset documentation update 2023-05-17 21:46:45 +02:00
SkalskiP 5f97eac9a9 📚 DetectionDataset documentation update 2023-05-17 21:35:33 +02:00
SkalskiP 30f105f267 🛡️ add badges to README.md 2023-05-17 20:47:42 +02:00
SkalskiP a34e64866e 📚 add DetectionDataset.split documentation 2023-05-17 20:39:36 +02:00
Piotr Skalski c02f69428e Merge pull request #102 from roboflow/feature/dataset_split
feature/dataset_split
2023-05-17 20:22:30 +02:00
SkalskiP 55f4f00c0c 🧪 tests for train_test_split 2023-05-17 20:20:28 +02:00
SkalskiP 91100e8393 🚀 initial commit 2023-05-17 19:53:21 +02:00
Hardik Dava b474a47065 Adding yolov8 masks to detections 2023-05-17 18:38:40 +02:00
Piotr Skalski 83d357d973 Merge pull request #100 from roboflow/feature/support_for_yolo_dataset_export
feature/support for yolo dataset export
2023-05-17 18:14:55 +02:00
SkalskiP b536c6c889 📚 small documentation updates 2023-05-17 16:27:31 +02:00
SkalskiP df579c866c 🌱 change default value of approximation_percentage parameter from 0.75 to 0.0 in DetectionDataset.as_yolo and DetectionDataset.as_pascal_voc. 2023-05-17 16:13:32 +02:00
SkalskiP f224bceb6f 🛠️ Bug fixing 2023-05-17 16:05:02 +02:00
SkalskiP bd3503e7d5 🪵 0.8.0 release changelog 2023-05-17 14:53:51 +02:00
SkalskiP f63f5acb2f ⭐ PR ready for review 2023-05-17 02:38:14 +02:00
SkalskiP 2f5b1b0f0d 🛠️ small fix 2023-05-17 02:21:14 +02:00
SkalskiP 3af32800d3 😷 initial support for masks 2023-05-17 02:13:08 +02:00
SkalskiP 8222bee9dd 🛠️ Small fix 2023-05-17 01:52:18 +02:00
SkalskiP 06ea737336 📦 YOLO box save ready for tests 2023-05-17 01:35:11 +02:00
SkalskiP f616bda40f 🐚 shell of YOLO format processing added 2023-05-17 00:58:17 +02:00
SkalskiP 0e835f1b0a 🐚 shell of YOLO format processing added 2023-05-17 00:40:33 +02:00
SkalskiP 4c024214b2 🐚 shell of YOLO format processing added 2023-05-16 23:51:08 +02:00
SkalskiP 6a15093277 👨‍👩‍👦 basic support for Dataset inheritance + adding pyyaml to dependencies 2023-05-16 23:25:58 +02:00
SkalskiP abcf453119 🎉 initial commit 2023-05-16 16:35:59 +02:00
SkalskiP ba2926688f 🪵 0.7.0 release changelog 2023-05-11 00:39:07 +02:00
SkalskiP 3da433f90f 🪵 0.7.0 release changelog 2023-05-11 00:31:46 +02:00
Piotr Skalski a911cbd554 Merge pull request #44 from iPoe/feature/custom_msg_for_line_zone
Add custom text for the in & out tags
2023-05-10 18:44:52 +02:00
SkalskiP d10618a8b3 🛠️ add __len__ and __iter__ to Dataset 2023-05-10 14:02:08 +02:00
Piotr Skalski eaf5b7e95f Merge pull request #86 from roboflow/feature/support_yolo_dataset_loading
support for yolo dataset loading and saving
2023-05-09 23:02:26 +02:00
Piotr Skalski 4ae8ac1db0 Merge pull request #91 from roboflow/feature/add_yolo_nas_support
💜 Initial YOLO-NAS support
2023-05-09 19:31:56 +02:00
SkalskiP 74efca7c22 💜 Initial YOLO-NAS support 2023-05-09 17:19:40 +02:00
SkalskiP 3063dfb1f5 📚 Improve BoxAnnotator.annotate documentation and add example 2023-05-09 12:14:37 +02:00
SkalskiP 6c81d21a03 Unit tests for yolo loader added 2023-05-08 21:34:36 +02:00
SkalskiP f4b91d0175 Fix load_yolo_annotations 2023-05-08 19:46:13 +02:00
SkalskiP 45fdb1e805 Fix load_yolo_annotations 2023-05-08 19:33:44 +02:00
SkalskiP 8c0a294ca1 First fully working version of from_yolo. Ready for test. 2023-05-08 17:13:59 +02:00
SkalskiP cf7f003133 Merge remote-tracking branch 'origin/feature/support_yolo_dataset_loading' into feature/support_yolo_dataset_loading
# Conflicts:
#	supervision/dataset/formats/yolo.py
2023-05-08 14:08:00 +02:00
SkalskiP 1bebbb14a6 yolo_annotations_to_detections function full implementation 2023-05-08 14:07:43 +02:00
SkalskiP 3d0aec03e0 yolo_annotations_to_detections function full implementation 2023-05-08 13:51:49 +02:00
SkalskiP 306d7bd27e Merge remote-tracking branch 'origin/feature/support_yolo_dataset_loading' into feature/support_yolo_dataset_loading
# Conflicts:
#	supervision/dataset/formats/yolo.py
2023-05-08 10:57:28 +02:00
SkalskiP 43fc5d6205 yolo annotations box_to_polygon initial implementation 2023-05-08 10:55:58 +02:00
SkalskiP d13238bc42 yolo annotations box_to_polygon initial implementation 2023-05-07 16:38:53 +02:00
SkalskiP 77243f1752 initial yolo annotations file parsing 2023-05-07 13:16:42 +02:00
SkalskiP e16cca4349 🚀 - add quick start section to Supervision docs page with first topic - advanced detections filtering 2023-05-04 17:48:39 +02:00
Piotr Skalski e592a44bea Merge pull request #84 from roboflow/feature/20230504_add_merge_detections_method
➕ Add `Detections.merge` method
2023-05-04 15:55:46 +02:00
SkalskiP c2833bc8ca 🤍 / 🖤 - light mode / datk mode switch added 2023-05-04 10:13:11 +02:00
SkalskiP feb08d10b2 ➕ Add Detections.merge method 2023-05-04 09:30:48 +02:00
Piotr Skalski 74bd403dc0 Merge pull request #83 from roboflow/feature/20230504_major_documentation_improvements
major documentation improvements
2023-05-04 00:37:42 +02:00
SkalskiP 6a013382d3 - 📚 Detections documentation improvements
- `list_files_with_extensions` documentation improvements
- internal `deprecated` function added
- `plot_image` and `plot_images_grid` documentation improvements
2023-05-04 00:33:17 +02:00
Piotr Skalski 92290979c7 Merge pull request #81 from hardikdava/patch-2
minor bug fixed
2023-05-03 16:06:00 +02:00
Hardik Dava 696b6f9276 minor bug fixed
annotation function should return frame but it was not. So added return value
2023-05-01 12:24:30 +02:00
Piotr Skalski 29450139f9 Merge pull request #72 from roboflow/feature/add_pascal_voc_xml_load_support
feature/add_pascal_voc_xml_load_support
2023-04-19 23:02:07 +02:00
SkalskiP ea400feb6a 🪵 update changelog.md and 🖤 make black happy 2023-04-19 22:12:27 +02:00
SkalskiP 05495aca75 Initial version of from_pascal_voc 2023-04-19 21:15:23 +02:00
SkalskiP a3bea9ae37 🛠️ small fix 2023-04-19 20:36:57 +02:00
SkalskiP 651690f838 load_pascal_voc_annotations function added 2023-04-19 20:34:17 +02:00
SkalskiP d87761f75d 🎉 initial commit 2023-04-19 16:19:19 +02:00
Piotr Skalski b20e4a6c19 Merge pull request #71 from roboflow/feature/initial_dataset_support
feature/initial dataset support
2023-04-19 14:56:10 +02:00
SkalskiP 0ef2ab7273 🧪 test new approximate_polygon function 2023-04-19 14:44:43 +02:00
SkalskiP d1bb169593 🛠️ small bug fixing 2023-04-19 14:17:56 +02:00
SkalskiP c8f3f59a45 🛠️ small bug fixing 2023-04-19 14:09:36 +02:00
SkalskiP 12523afe9a 🧪 PoC of approximate_polygon 2023-04-19 13:49:38 +02:00
SkalskiP 68412500fa 📄 docs update 2023-04-19 13:09:53 +02:00
SkalskiP 49d4441419 📄 docs update 2023-04-19 12:59:29 +02:00
SkalskiP 0394a3f545 small update in detections_to_pascal_voc function 2023-04-18 22:25:26 +02:00
SkalskiP 2bcd1e1dfd 🛠️small fixes 2023-04-18 20:36:52 +02:00
SkalskiP e62937c8ce list_files_with_extensions utility added 2023-04-18 20:08:55 +02:00
SkalskiP b37aca52e2 ⬇️Initial Dataset implementation 2023-04-18 19:31:36 +02:00
SkalskiP 790d2f6c5b polygon_to_xyxy function added 2023-04-18 17:09:37 +02:00
SkalskiP 0cb722e91e mask_to_polygons and filter_polygons_by_area added 2023-04-18 14:48:25 +02:00
SkalskiP 55cd04d0aa 🛠️ small fixes 2023-04-18 12:26:51 +02:00
SkalskiP 58181e6a7a 🛠️ small fixes 2023-04-18 12:22:35 +02:00
SkalskiP ab00d2db0d 🛠️ small fixes 2023-04-18 12:02:58 +02:00
SkalskiP 6a76c96df5 🛠️ small fixes 2023-04-18 11:59:41 +02:00
SkalskiP d4a6a1a85b 🛠️ small fixes 2023-04-18 11:55:11 +02:00
SkalskiP 7c78feb4b8 Merge branch 'main' into feature/initial_dataset_support
# Conflicts:
#	supervision/detection/core.py
2023-04-18 11:08:06 +02:00
Piotr Skalski 73a7983719 Merge pull request #70 from alexandercarruthers/feature/add-detections-consistancy
Make Detection docs consistent with __iter__
2023-04-18 10:59:21 +02:00
SkalskiP 1d6c2bc080 ⬇️ initial version of Pascal VOC XML sink 2023-04-18 10:56:41 +02:00
alexandercarruthers 889461574f Make Detection docs consistent with __iter__ 2023-04-17 21:26:04 +01:00
SkalskiP f95426540b 🎭 mask annotator updates 2023-04-17 17:31:48 +02:00
SkalskiP c16599269c 🛠️ fix imports 2023-04-17 16:58:25 +02:00
SkalskiP 888edfbc67 🎉 Initial commit 2023-04-17 16:36:27 +02:00
iPoe ac61598ad4 Add custom text for the in & out tags. 2023-03-29 14:01:35 -05:00
118 changed files with 16350 additions and 1001 deletions
+1
View File
@@ -0,0 +1 @@
*.ipynb linguist-vendored
+1 -1
View File
@@ -62,4 +62,4 @@ body:
description: >
(Optional) We encourage you to submit a [Pull Request](https://github.com/roboflow/supervision/pulls) (PR) to help improve Supervision for everyone, especially if you have a good understanding of how to implement a fix or feature.
options:
- label: Yes I'd like to help by submitting a PR!
- label: Yes I'd like to help by submitting a PR!
+1 -1
View File
@@ -46,4 +46,4 @@ body:
description: >
(Optional) We encourage you to submit a [Pull Request](https://github.com/roboflow/supervision/pulls) (PR) to help improve Supervision for everyone, especially if you have a good understanding of how to implement a fix or feature.
options:
- label: Yes I'd like to help by submitting a PR!
- label: Yes I'd like to help by submitting a PR!
+1 -1
View File
@@ -30,4 +30,4 @@ body:
- type: textarea
attributes:
label: Additional
description: Anything else you would like to share?
description: Anything else you would like to share?
+17
View File
@@ -0,0 +1,17 @@
contourpy==1.1.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
cycler==0.11.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
fonttools==4.41.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
importlib-resources==6.0.0 ; python_version >= "3.8" and python_version < "3.10"
kiwisolver==1.4.4 ; python_version >= "3.8" and python_full_version < "3.12.0"
matplotlib==3.7.2 ; python_version >= "3.8" and python_full_version < "3.12.0"
numpy==1.24.4 ; python_version >= "3.8" and python_version < "3.12"
opencv-python-headless==4.8.0.74 ; python_version >= "3.8" and python_full_version < "3.12.0"
packaging==23.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
pillow==10.0.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
pyparsing==3.0.9 ; python_version >= "3.8" and python_full_version < "3.12.0"
python-dateutil==2.8.2 ; python_version >= "3.8" and python_full_version < "3.12.0"
pyyaml==6.0.1 ; python_version >= "3.8" and python_full_version < "3.12.0"
scipy==1.10.1 ; python_version >= "3.8" and python_version < "3.12"
six==1.16.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
zipp==3.16.2 ; python_version >= "3.8" and python_version < "3.10"
pytest==7.4.0 ; python_version >= "3.8" and python_full_version < "3.12.0"
+33
View File
@@ -0,0 +1,33 @@
name: Clear cache
on:
schedule:
- cron: '0 0 1 * *'
workflow_dispatch:
permissions:
actions: write
jobs:
clear-cache:
runs-on: ubuntu-latest
steps:
- name: Clear cache
uses: actions/github-script@v6
with:
script: |
console.log("About to clear")
const caches = await github.rest.actions.getActionsCacheList({
owner: context.repo.owner,
repo: context.repo.repo,
})
for (const cache of caches.data.actions_caches) {
console.log(cache)
github.rest.actions.deleteActionsCacheById({
owner: context.repo.owner,
repo: context.repo.repo,
cache_id: cache.id,
})
}
console.log("Clear completed")
+4 -3
View File
@@ -5,14 +5,15 @@ on:
branches:
- master
- main
- develop
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: pip install mkdocs-material
- run: pip install "mkdocstrings[python]"
- run: mkdocs gh-deploy --force
- run: mkdocs gh-deploy --force
+41
View File
@@ -0,0 +1,41 @@
name: Supervision Test Releases to PyPi
on:
push:
tags:
- '[0-9]+.[0-9]+[0-9]+.[0-9]+a[0-9]'
- '[0-9]+.[0-9]+[0-9]+.[0-9]+b[0-9]'
- '[0-9]+.[0-9]+[0-9]+.[0-9]+rc[0-9]'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build-n-publish:
name: Build and publish to PyPI
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: 🐍 Set up Python 3.8 environment for build
uses: actions/setup-python@v4
with:
python-version: "3.8"
- name: 🏗️ Build source and wheel distributions
run: |
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: 🚀 Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_PASSWORD }}
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
user: ${{ secrets.PYPI_TEST_USERNAME }}
password: ${{ secrets.PYPI_TEST_PASSWORD }}
+25 -14
View File
@@ -1,8 +1,13 @@
name: Publish WorkFlow
name: Supervision Releases to PyPi
on:
release:
types: [created]
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:
jobs:
build:
@@ -16,17 +21,23 @@ jobs:
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: 🦾 Install dependencies
- name: 🏗️ Build source and wheel distributions
run: |
python -m pip install --upgrade pip
pip install ".[dev]"
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: 🚀 Publish to PyPi
env:
PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }}
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
PYPI_TEST_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }}
run: |
make publish -e PYPI_USERNAME=$PYPI_USERNAME -e PYPI_PASSWORD=$PYPI_PASSWORD -e PYPI_TEST_PASSWORD=$PYPI_TEST_PASSWORD
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_PASSWORD }}
- name: 🚀 Publish to Test-PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
user: ${{ secrets.PYPI_TEST_USERNAME }}
password: ${{ secrets.PYPI_TEST_PASSWORD }}
+13 -15
View File
@@ -2,33 +2,31 @@ name: Test WorkFlow
on:
pull_request:
branches: [main]
branches: [main, develop]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, '3.10']
python-version: ["3.8", "3.9", "3.10","3.11"]
steps:
- name: 🛎️ Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
# id based on python version
id: python-setup
with:
python-version: ${{ matrix.python-version }}
- name: 🦾 Install dependencies
check-latest: true
cache: 'pip'
cache-dependency-path: '**/requirements-dev.txt'
- name: 📦 Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest
pip install isort
pip install flake8
pip install "black==22.3.0"
- name: 🧹 Lint with flake8
run: |
make check_code_quality
pip install -r .github/requirements-dev.txt
- name: 🧪 Test
run: "python -m pytest ./test"
run: "python -m pytest ./test"
+1 -1
View File
@@ -15,4 +15,4 @@ jobs:
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: "Hello there, thank you for opening an Issue ! 🙏🏻 The team was notified and they will get back to you asap."
pr-message: "Hello there, thank you for opening an PR ! 🙏🏻 The team was notified and they will get back to you asap."
pr-message: "Hello there, thank you for opening an PR ! 🙏🏻 The team was notified and they will get back to you asap."
+1
View File
@@ -54,6 +54,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
.ruff_cache/
# Translations
*.mo
+76
View File
@@ -0,0 +1,76 @@
ci:
autofix_prs: true
autoupdate_schedule: weekly
autofix_commit_msg: "fix(pre_commit): 🎨 auto format pre-commit hooks"
autoupdate_commit_msg: "chore(pre_commit): ⬆ pre_commit autoupdate"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-yaml
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-toml
- id: check-case-conflict
- id: check-added-large-files
args: ['--maxkb=2048']
exclude: ^logo/
- id: detect-private-key
- id: forbid-new-submodules
- id: pretty-format-json
exclude: demo.ipynb
args: ['--autofix', '--no-sort-keys', '--indent=4']
- id: end-of-file-fixer
- id: mixed-line-ending
# - repo: https://github.com/asottile/pyupgrade
# rev: v3.9.0
# hooks:
# - id: pyupgrade
# name: Upgrade code
# args: [--py38-plus]
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
name: Flake8 Checks
entry: flake8
additional_dependencies: [Flake8-pyproject]
- repo: https://github.com/PyCQA/bandit
rev: '1.7.5'
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
- id: isort
name: isort (cython)
types: [cython]
- id: isort
name: isort (pyi)
types: [pyi]
- repo: https://github.com/psf/black
rev: 23.9.1
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.292
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+19 -8
View File
@@ -1,12 +1,23 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
author: Roboflow
message: If you use this software, please cite it as below.
title: supervision
version: 0.1.0
date-released: 2023-01-19
license: MIT
repository-code: https://github.com/roboflow/supervision
title: Supervision
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Roboflow
email: support@roboflow.com
repository-code: 'https://github.com/roboflow/supervision'
url: 'https://roboflow.github.io/supervision/'
abstract: >-
supervision features a range of utilities for use in
computer vision projects, from detections processing and
filtering to confusion matrix calcuation.
keywords:
- computer vision
- image processing
- video processing
- video processing
license: MIT
+134
View File
@@ -0,0 +1,134 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
community-reports@roboflow.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+80 -9
View File
@@ -14,7 +14,7 @@ We welcome contributions to:
4. Submit a request for a new feature.
5. Improve our test coverage.
### Contributing Features
### Contributing Features ✨
Supervision is designed to provide generic utilities to solve problems. Thus, we focus on contributions that can have an impact on a wide range of projects.
@@ -24,7 +24,72 @@ Before you contribute a new feature, consider submitting an Issue to discuss the
## How to Contribute Changes
First, fork this repository to your own GitHub account. Create a new branch that describes your changes (i.e. `line-counter-docs`). Push your changes to the branch on your fork and then submit a pull request to this repository.
First, fork this repository to your own GitHub account. Click "fork" in the top corner of the `supervision` repository to get started:
![Forking the repository](https://media.roboflow.com/fork.png)
![Creating a repository fork](https://media.roboflow.com/create_fork.png)
Then, run `git clone` to download the project code to your computer.
Move to a new branch using the `git checkout` command:
```bash
git checkout -b <your_branch_name>
```
The name you choose for your branch should describe the change you want to make (i.e. `line-counter-docs`).
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
```
## 🎨 Code quality
### Pre-commit tool
This project utilizes 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 execute `pre-commit install`. Every time you made `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).
Then, go back to your fork of the `supervision` repository, click "Pull Requests", and click "New Pull Request".
![Opening a pull request](https://media.roboflow.com/open_pr.png)
Make sure the `base` branch is `develop` before submitting your PR.
On the next page, review your changes then click "Create pull request":
![Configuring a pull request](https://media.roboflow.com/create_pr_submit.png)
Next, write a description for your pull request, and click "Create pull request" again to submit it for review:
![Submitting a pull request](https://media.roboflow.com/write_pr.png)
When creating new functions, please ensure you have the following:
@@ -32,20 +97,26 @@ When creating new functions, please ensure you have the following:
2. Unit tests for the function.
3. Examples in the documentation for the function.
4. Created an entry in our docs to autogenerate the documentation for the function.
5. Please share a Google Colab with minimal code to test new feature or reproduce PR whenever it is possible. Please ensure that Google Colab can be accessed without any issue.
When you submit your Pull Request, you will be asked to sign a Contributor License Agreement (CLA) by the `cla-assistant` GitHub bot. We can only respond to PRs from contributors who have signed the project CLA.
All pull requests will be reviewed by the maintainers of the project. We will provide feedback and ask for changes if necessary.
PRs must pass all tests and linting requirements before they can be merged.
## 🧹 code quality
## 📝 documentation
We provide two handy commands inside the `Makefile`, namely:
The `supervision` documentation is stored in a folder called `docs`. The project documentation is built using `mkdocs`.
- `make style` to format the code
- `make check_code_quality` to check code quality (PEP8 basically)
To run the documentation, install the project requirements with `poetry install dev`. Then, run `mkdocs serve` to start the documentation server.
So far, **there is no types checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4).
You can learn more about mkdocs on the [mkdocs website](https://www.mkdocs.org/).
## 🧪 tests
## 🧪 tests
[`pytests`](https://docs.pytest.org/en/7.1.x/) is used to run our tests.
[`pytests`](https://docs.pytest.org/en/7.1.x/) is used to run our tests.
## 📄 license
By contributing, you agree that your contributions will be licensed under an [MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md).
-22
View File
@@ -1,22 +0,0 @@
.PHONY: style check_code_quality
export PYTHONPATH = .
check_dirs := supervision
style:
black $(check_dirs)
isort --profile black $(check_dirs)
check_code_quality:
black --check $(check_dirs)
isort --check-only --profile black $(check_dirs)
# stop the build if there are Python syntax errors or undefined names
flake8 $(check_dirs) --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. E203 for black, E501 for docstring, W503 for line breaks before logical operators
flake8 $(check_dirs) --count --max-line-length=88 --exit-zero --ignore=D --extend-ignore=E203,E501,W503 --statistics
publish:
python setup.py sdist bdist_wheel
twine upload -r testpypi dist/* -u ${PYPI_USERNAME} -p ${PYPI_TEST_PASSWORD} --verbose
twine check dist/*
twine upload dist/* -u ${PYPI_USERNAME} -p ${PYPI_PASSWORD} --verbose
+311 -70
View File
@@ -10,6 +10,317 @@
<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)
<br>
[![version](https://badge.fury.io/py/supervision.svg)](https://badge.fury.io/py/supervision)
[![downloads](https://img.shields.io/pypi/dm/supervision)](https://pypistats.org/packages/supervision)
[![license](https://img.shields.io/pypi/l/supervision)](https://github.com/roboflow/supervision/blob/main/LICENSE.md)
[![python-version](https://img.shields.io/pypi/pyversions/supervision)](https://badge.fury.io/py/supervision)
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb)
[![Gradio](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/Roboflow/Annotators)
[![Discord](https://img.shields.io/discord/1159501506232451173)](https://discord.gg/GbfgXGJ8Bk)
</div>
<a href="https://github.com/roboflow/supervision/issues?q=is%3Aissue+label%3Ahacktoberfest+">
<img width="100%" src="https://media.roboflow.com/open-source/supervision/hacktoberfest-banner-3.png">
</a>
## 👋 hello
**We write your reusable computer vision tools.** Whether you need to load your dataset from your hard drive, draw detections on an image or video, or count how many detections are in a zone. You can count on us! 🤝
## 💻 install
Pip install the supervision package in a
[**3.11>=Python>=3.8**](https://www.python.org/) environment.
```bash
pip install supervision[desktop]
```
Read more about desktop, headless, and local installation in our [guide](https://roboflow.github.io/supervision/).
## 🔥 quickstart
### [detections processing](https://roboflow.github.io/supervision/detection/core/)
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> model = YOLO('yolov8s.pt')
>>> result = model(IMAGE)[0]
>>> detections = sv.Detections.from_ultralytics(result)
>>> len(detections)
5
```
<details close>
<summary>👉 more detections utils</summary>
- Easily switch inference pipeline between supported object detection/instance segmentation models
```python
>>> import supervision as sv
>>> from segment_anything import sam_model_registry, SamAutomaticMaskGenerator
>>> sam = sam_model_registry[MODEL_TYPE](checkpoint=CHECKPOINT_PATH).to(device=DEVICE)
>>> mask_generator = SamAutomaticMaskGenerator(sam)
>>> sam_result = mask_generator.generate(IMAGE)
>>> detections = sv.Detections.from_sam(sam_result=sam_result)
```
- [Advanced filtering](https://roboflow.github.io/supervision/quickstart/detections/)
```python
>>> detections = detections[detections.class_id == 0]
>>> detections = detections[detections.confidence > 0.5]
>>> detections = detections[detections.area > 1000]
```
- Image annotation
```python
>>> import supervision as sv
>>> box_annotator = sv.BoxAnnotator()
>>> annotated_frame = box_annotator.annotate(
... scene=IMAGE,
... detections=detections
... )
```
</details>
### [datasets processing](https://roboflow.github.io/supervision/dataset/core/)
```python
>>> import supervision as sv
>>> dataset = sv.DetectionDataset.from_yolo(
... images_directory_path='...',
... annotations_directory_path='...',
... data_yaml_path='...'
... )
>>> dataset.classes
['dog', 'person']
>>> len(dataset)
1000
```
<details close>
<summary>👉 more dataset utils</summary>
- Load object detection/instance segmentation datasets in one of the supported formats
```python
>>> dataset = sv.DetectionDataset.from_yolo(
... images_directory_path='...',
... annotations_directory_path='...',
... data_yaml_path='...'
... )
>>> dataset = sv.DetectionDataset.from_pascal_voc(
... images_directory_path='...',
... annotations_directory_path='...'
... )
>>> dataset = sv.DetectionDataset.from_coco(
... images_directory_path='...',
... annotations_path='...'
... )
```
- Loop over dataset entries
```python
>>> for name, image, labels in dataset:
... print(labels.xyxy)
array([[404. , 719. , 538. , 884.5 ],
[155. , 497. , 404. , 833.5 ],
[ 20.154999, 347.825 , 416.125 , 915.895 ]], dtype=float32)
```
- Split dataset for training, testing, and validation
```python
>>> train_dataset, test_dataset = dataset.split(split_ratio=0.7)
>>> test_dataset, valid_dataset = test_dataset.split(split_ratio=0.5)
>>> len(train_dataset), len(test_dataset), len(valid_dataset)
(700, 150, 150)
```
- Merge multiple datasets
```python
>>> 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']
```
- Save object detection/instance segmentation datasets in one of the supported formats
```python
>>> dataset.as_yolo(
... images_directory_path='...',
... annotations_directory_path='...',
... data_yaml_path='...'
... )
>>> dataset.as_pascal_voc(
... images_directory_path='...',
... annotations_directory_path='...'
... )
>>> dataset.as_coco(
... images_directory_path='...',
... annotations_path='...'
... )
```
- Convert labels between supported formats
```python
>>> sv.DetectionDataset.from_yolo(
... images_directory_path='...',
... annotations_directory_path='...',
... data_yaml_path='...'
... ).as_pascal_voc(
... images_directory_path='...',
... annotations_directory_path='...'
... )
```
- Load classification datasets in one of the supported formats
```python
>>> cs = sv.ClassificationDataset.from_folder_structure(
... root_directory_path='...'
... )
```
- Save classification datasets in one of the supported formats
```python
>>> cs.as_folder_structure(
... root_directory_path='...'
... )
```
</details>
### [model evaluation](https://roboflow.github.io/supervision/metrics/detection/)
```python
>>> import supervision as sv
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... ...
>>> confusion_matrix = sv.ConfusionMatrix.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
<details close>
<summary>👉 more metrics</summary>
- Mean average precision (mAP) for object detection tasks.
```python
>>> import supervision as sv
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... ...
>>> mean_average_precision = sv.MeanAveragePrecision.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> mean_average_precision.map50_95
0.433
```
</details>
## 🎬 tutorials
<p align="left">
<a href="https://youtu.be/4Q3ut7vqD5o" title="Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking"><img src="https://github.com/roboflow/supervision/assets/26109316/54afdf1c-218c-4451-8f12-627fb85f1682" alt="Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking" width="300px" align="left" /></a>
<a href="https://youtu.be/4Q3ut7vqD5o" title="Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking"><strong>Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking</strong></a>
<div><strong>Created: 6 Sep 2023</strong> | <strong>Updated: 6 Sep 2023</strong></div>
<br/> In this video, we explore real-time traffic analysis using YOLOv8 and ByteTrack to detect and track vehicles on aerial images. Harnessing the power of Python and Supervision, we delve deep into assigning cars to specific entry zones and understanding their direction of movement. By visualizing their paths, we gain insights into traffic flow across bustling roundabouts... </p>
<br/>
<p align="left">
<a href="https://youtu.be/D-D6ZmadzPE" title="SAM - Segment Anything Model by Meta AI: Complete Guide"><img src="https://github.com/SkalskiP/SkalskiP/assets/26109316/6913ff11-53c6-4341-8d90-eaff3023c3fd" alt="SAM - Segment Anything Model by Meta AI: Complete Guide" width="300px" align="left" /></a>
<a href="https://youtu.be/D-D6ZmadzPE" title="SAM - Segment Anything Model by Meta AI: Complete Guide"><strong>SAM - Segment Anything Model by Meta AI: Complete Guide</strong></a>
<div><strong>Created: 11 Apr 2023</strong> | <strong>Updated: 11 Apr 2023</strong></div>
<br/> Discover the incredible potential of Meta AI's Segment Anything Model (SAM)! We dive into SAM, an efficient and promptable model for image segmentation, which has revolutionized computer vision tasks. With over 1 billion masks on 11M licensed and privacy-respecting images, SAM's zero-shot performance is often competitive with or even superior to prior fully supervised results... </p>
## 💜 built with supervision
Did you build something cool using supervision? [Let us know!](https://github.com/roboflow/supervision/discussions/categories/built-with-supervision)
https://user-images.githubusercontent.com/26109316/207858600-ee862b22-0353-440b-ad85-caa0c4777904.mp4
https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-60e9c81b3900
## 📚 documentation
Visit our [documentation](https://roboflow.github.io/supervision) page to learn how supervision can help you build computer vision applications faster and more reliably.
## 🏆 contribution
We love your input! Please see our [contributing guide](https://github.com/roboflow/supervision/blob/main/CONTRIBUTING.md) to get started. Thank you 🙏 to all our contributors!
<p align="center">
<a href="https://github.com/roboflow/supervision/graphs/contributors">
<img src="https://contrib.rocks/image?repo=roboflow/supervision" />
</a>
</p>
<br>
<div align="center">
<div align="center">
<a href="https://youtube.com/roboflow">
<img
@@ -53,74 +364,4 @@
</a>
</a>
</div>
<br>
[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-detect-and-count-objects-in-polygon-zone.ipynb)
</div>
## 👋 hello
A set of easy-to-use utils that will come in handy in any computer vision project. **Supervision is still in
pre-release stage. 🚧 Keep your eyes open for potential bugs and be aware that at this stage our API is still fluid
and may change.**
## 💻 install
Pip install the supervision package in a
[**3.10>=Python>=3.7**](https://www.python.org/) environment.
```bash
pip install supervision
```
<details close>
<summary>Install from source</summary>
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
python3 -m venv venv
source venv/bin/activate
# install
pip install -e ".[dev]"
```
</details>
## 📖 documentation
Curious how Supervision can help you solve problems on your project? Visit our [documentation](https://roboflow.github.io/supervision) page!
## 🎬 tutorials
🔥 [Subscribe](https://www.youtube.com/@Roboflow) and stay up to date with the latest changes to the Supervision library.
<p align="center">
<a href="https://youtu.be/l_kf9CfZ_8M">
<img
width="90%"
src="https://user-images.githubusercontent.com/26109316/217950212-311de186-1862-4b4c-a86e-89cafd68b233.jpg"
alt="count-objects-in-zone"
>
</a>
</p>
<p align="center">
<a href="https://youtu.be/OS5qI9YBkfk">
<img
width="90%"
src="https://user-images.githubusercontent.com/26109316/213702005-ddd568f0-b902-46c2-9af9-e6ff33db23bf.jpg"
alt="count-objects-crossing-line"
>
</a>
</p>
## 🏆 contribution
We love your input! Please see our [contributing guide](https://github.com/roboflow/supervision/blob/main/CONTRIBUTING.md) to get started. Thank you 🙏 to all our contributors!
Vendored
+1325
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
supervision.roboflow.com
-3
View File
@@ -1,3 +0,0 @@
## detections_to_voc_xml
:::supervision.annotation.voc.detections_to_voc_xml
+321
View File
@@ -0,0 +1,321 @@
=== "BoundingBox"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> bounding_box_annotator = sv.BoundingBoxAnnotator()
>>> annotated_frame = bounding_box_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![bounding-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/bounding-box-annotator-example-purple.png){ align=center width="800" }
</div>
=== "BoxCorner"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> corner_annotator = sv.BoxCornerAnnotator()
>>> annotated_frame = corner_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![box-corner-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-corner-annotator-example-purple.png){ align=center width="800" }
</div>
=== "BoxMask"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> box_mask_annotator = sv.BoxMaskAnnotator()
>>> annotated_frame = box_mask_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![box-mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Circle"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> circle_annotator = sv.CircleAnnotator()
>>> annotated_frame = circle_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![circle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/circle-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Dot"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> dot_annotator = sv.DotAnnotator()
>>> annotated_frame = dot_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![circle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/dot-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Ellipse"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> ellipse_annotator = sv.EllipseAnnotator()
>>> annotated_frame = ellipse_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![ellipse-annotator-example](https://media.roboflow.com/supervision-annotator-examples/ellipse-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Halo"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> halo_annotator = sv.HaloAnnotator()
>>> annotated_frame = halo_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![ellipse-annotator-example](https://media.roboflow.com/supervision-annotator-examples/halo-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Mask"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> mask_annotator = sv.MaskAnnotator()
>>> annotated_frame = mask_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Label"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
>>> annotated_frame = label_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Blur"
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> blur_annotator = sv.BlurAnnotator()
>>> annotated_frame = blur_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
<div class="result" markdown>
![blur-annotator-example](https://media.roboflow.com/supervision-annotator-examples/blur-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Trace"
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> model = YOLO('yolov8x.pt')
>>> trace_annotator = sv.TraceAnnotator()
>>> video_info = sv.VideoInfo.from_video_path(video_path='...')
>>> frames_generator = get_video_frames_generator(source_path='...')
>>> tracker = sv.ByteTrack()
>>> with sv.VideoSink(target_path='...', video_info=video_info) as sink:
... for frame in frames_generator:
... result = model(frame)[0]
... detections = sv.Detections.from_ultralytics(result)
... detections = tracker.update_with_detections(detections)
... annotated_frame = trace_annotator.annotate(
... scene=frame.copy(),
... detections=detections)
... sink.write_frame(frame=annotated_frame)
```
<div class="result" markdown>
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/trace-annotator-example-purple.png){ align=center width="800" }
</div>
=== "HeatMap"
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> model = YOLO('yolov8x.pt')
>>> heat_map_annotator = sv.HeatMapAnnotator()
>>> video_info = sv.VideoInfo.from_video_path(video_path='...')
>>> frames_generator = get_video_frames_generator(source_path='...')
>>> with sv.VideoSink(target_path='...', video_info=video_info) as sink:
... for frame in frames_generator:
... result = model(frame)[0]
... detections = sv.Detections.from_ultralytics(result)
... annotated_frame = heat_map_annotator.annotate(
... scene=frame.copy(),
... detections=detections)
... sink.write_frame(frame=annotated_frame)
```
<div class="result" markdown>
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/heat-map-annotator-example-purple.png){ align=center width="800" }
</div>
## BoundingBoxAnnotator
:::supervision.annotators.core.BoundingBoxAnnotator
## BoxCornerAnnotator
:::supervision.annotators.core.BoxCornerAnnotator
## BoxMaskAnnotator
:::supervision.annotators.core.BoxMaskAnnotator
## CircleAnnotator
:::supervision.annotators.core.CircleAnnotator
## DotAnnotator
:::supervision.annotators.core.DotAnnotator
## EllipseAnnotator
:::supervision.annotators.core.EllipseAnnotator
## HaloAnnotator
:::supervision.annotators.core.HaloAnnotator
## HeatMapAnnotator
:::supervision.annotators.core.HeatMapAnnotator
## MaskAnnotator
:::supervision.annotators.core.MaskAnnotator
## LabelAnnotator
:::supervision.annotators.core.LabelAnnotator
## BlurAnnotator
:::supervision.annotators.core.BlurAnnotator
## TraceAnnotator
:::supervision.annotators.core.TraceAnnotator
## ColorLookup
:::supervision.annotators.utils.ColorLookup
+382 -22
View File
@@ -1,6 +1,366 @@
### 0.16.0 <small>October 19, 2023</small>
- Added [#422](https://github.com/roboflow/supervision/pull/422): [`sv.BoxMaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoxMaskAnnotator) allowing to annotate images and videos with mox masks.
- Added [#433](https://github.com/roboflow/supervision/pull/433): [`sv.HaloAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.HaloAnnotator) allowing to annotate images and videos with halo effect.
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> halo_annotator = sv.HaloAnnotator()
>>> annotated_frame = halo_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
- Added [#466](https://github.com/roboflow/supervision/pull/466): [`sv.HeatMapAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.HeatMapAnnotator) allowing to annotate videos with heat maps.
- Added [#492](https://github.com/roboflow/supervision/pull/492): [`sv.DotAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.DotAnnotator) allowing to annotate images and videos with dots.
- Added [#449](https://github.com/roboflow/supervision/pull/449): [`sv.draw_image`](https://supervision.roboflow.com/draw/utils/#supervision.draw.utils.draw_image) allowing to draw an image onto a given scene with specified opacity and dimensions.
- Added [#280](https://github.com/roboflow/supervision/pull/280): [`sv.FPSMonitor`](https://supervision.roboflow.com/utils/video/#supervision.utils.video.FPSMonitor) for monitoring frames per second (FPS) to benchmark latency.
- Added [#454](https://github.com/roboflow/supervision/pull/454): 🤗 Hugging Face Annotators [space](https://huggingface.co/spaces/Roboflow/Annotators).
- Changed [#482](https://github.com/roboflow/supervision/pull/482): [`sv.LineZone.tigger`](https://supervision.roboflow.com/detection/tools/line_zone/#supervision.detection.line_counter.LineZone.trigger) now return `Tuple[np.ndarray, np.ndarray]`. The first array indicates which detections have crossed the line from outside to inside. The second array indicates which detections have crossed the line from inside to outside.
- Changed [#465](https://github.com/roboflow/supervision/pull/465): Annotator argument name from `color_map: str` to `color_lookup: ColorLookup` enum to increase type safety.
- Changed [#426](https://github.com/roboflow/supervision/pull/426): [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) allowing 2x faster annotation.
- Fixed [#477](https://github.com/roboflow/supervision/pull/477): Poetry env definition allowing proper local installation.
- Fixed [#430](https://github.com/roboflow/supervision/pull/430): [`sv.ByteTrack`](https://supervision.roboflow.com/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) to return `np.array([], dtype=int)` when `svDetections` is empty.
!!! warning
`sv.Detections.from_yolov8` and `sv.Classifications.from_yolov8` as those are now replaced by [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_ultralytics) and [`sv.Classifications.from_ultralytics`](https://supervision.roboflow.com/classification/core/#supervision.classification.core.Classifications.from_ultralytics).
### 0.15.0 <small>October 5, 2023</small>
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) allowing to annotate images and videos with bounding boxes.
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.BoxCornerAnnotator `](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoxCornerAnnotator) allowing to annotate images and videos with just bounding box corners.
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) allowing to annotate images and videos with segmentation masks.
- Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.EllipseAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.EllipseAnnotator) allowing to annotate images and videos with ellipses (sports game style).
- Added [#386](https://github.com/roboflow/supervision/pull/386): [`sv.CircleAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.CircleAnnotator) allowing to annotate images and videos with circles.
- Added [#354](https://github.com/roboflow/supervision/pull/354): [`sv.TraceAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.TraceAnnotator) allowing to draw path of moving objects on videos.
- Added [#405](https://github.com/roboflow/supervision/pull/405): [`sv.BlurAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BlurAnnotator) allowing to blur objects on images and videos.
```python
>>> import supervision as sv
>>> image = ...
>>> detections = sv.Detections(...)
>>> bounding_box_annotator = sv.BoundingBoxAnnotator()
>>> annotated_frame = bounding_box_annotator.annotate(
... scene=image.copy(),
... detections=detections
... )
```
- Added [#354](https://github.com/roboflow/supervision/pull/354): Supervision usage [example](https://github.com/roboflow/supervision/tree/develop/examples/traffic_analysis). You can now learn how to perform traffic flow analysis with Supervision.
- Changed [#399](https://github.com/roboflow/supervision/pull/399): [`sv.Detections.from_roboflow`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_roboflow) now does not require `class_list` to be specified. The `class_id` value can be extracted directly from the [inference](https://github.com/roboflow/inference) response.
- Changed [#381](https://github.com/roboflow/supervision/pull/381): [`sv.VideoSink`](https://supervision.roboflow.com/utils/video/#videosink) now allows to customize the output codec.
- Changed [#361](https://github.com/roboflow/supervision/pull/361): [`sv.InferenceSlicer`](https://supervision.roboflow.com/detection/tools/inference_slicer/#supervision.detection.tools.inference_slicer.InferenceSlicer) can now operate in multithreading mode.
- Fixed [#348](https://github.com/roboflow/supervision/pull/348): [`sv.Detections.from_deepsparse`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_deepsparse) to allow processing empty [deepsparse](https://github.com/neuralmagic/deepsparse) result object.
### 0.14.0 <small>August 31, 2023</small>
- Added [#282](https://github.com/roboflow/supervision/pull/282): support for SAHI inference technique with [`sv.InferenceSlicer`](https://supervision.roboflow.com/detection/tools/inference_slicer).
```python
>>> import cv2
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> model = YOLO(...)
>>> def callback(image_slice: np.ndarray) -> sv.Detections:
... result = model(image_slice)[0]
... return sv.Detections.from_ultralytics(result)
>>> slicer = sv.InferenceSlicer(callback = callback)
>>> detections = slicer(image)
```
- Added [#297](https://github.com/roboflow/supervision/pull/297): [`Detections.from_deepsparse`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_deepsparse) to enable seamless integration with [DeepSparse](https://github.com/neuralmagic/deepsparse) framework.
- Added [#281](https://github.com/roboflow/supervision/pull/281): [`sv.Classifications.from_ultralytics`](https://supervision.roboflow.com/classification/core/#supervision.classification.core.Classifications.from_ultralytics) to enable seamless integration with [Ultralytics](https://github.com/ultralytics/ultralytics) framework. This will enable you to use supervision with all [models](https://docs.ultralytics.com/models/) that Ultralytics supports.
!!! warning
[sv.Detections.from_yolov8](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_yolov8) and [sv.Classifications.from_yolov8](https://supervision.roboflow.com/classification/core/#supervision.classification.core.Classifications.from_yolov8) are now deprecated and will be removed with supervision-0.16.0 release.
- Added [#341](https://github.com/roboflow/supervision/pull/341): First supervision usage example script showing how to detect and track objects on video using YOLOv8 + Supervision.
- Changed [#296](https://github.com/roboflow/supervision/pull/296): [`sv.ClassificationDataset`](https://supervision.roboflow.com/dataset/core/#supervision.dataset.core.ClassificationDataset) and [`sv.DetectionDataset`](https://supervision.roboflow.com/dataset/core/#supervision.dataset.core.DetectionDataset) now use image path (not image name) as dataset keys.
- Fixed [#300](https://github.com/roboflow/supervision/pull/300): [`Detections.from_roboflow`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_roboflow) to filter out polygons with less than 3 points.
### 0.13.0 <small>August 8, 2023</small>
- Added [#236](https://github.com/roboflow/supervision/pull/236): support for mean average precision (mAP) for object detection models with [`sv.MeanAveragePrecision`](https://roboflow.github.io/supervision/metrics/detection/#meanaverageprecision).
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... result = model(image)[0]
... return sv.Detections.from_yolov8(result)
>>> mean_average_precision = sv.MeanAveragePrecision.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> mean_average_precision.map50_95
0.433
```
- Added [#256](https://github.com/roboflow/supervision/pull/256): support for ByteTrack for object tracking with [`sv.ByteTrack`](https://roboflow.github.io/supervision/tracker/core/#bytetrack).
- Added [#222](https://github.com/roboflow/supervision/pull/222): [`sv.Detections.from_ultralytics`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_ultralytics) to enable seamless integration with [Ultralytics](https://github.com/ultralytics/ultralytics) framework. This will enable you to use `supervision` with all [models](https://docs.ultralytics.com/models/) that Ultralytics supports.
!!! warning
[`sv.Detections.from_yolov8`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_yolov8) is now deprecated and will be removed with `supervision-0.15.0` release.
- Added [#191](https://github.com/roboflow/supervision/pull/191): [`sv.Detections.from_paddledet`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_paddledet) to enable seamless integration with [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) framework.
- Added [#245](https://github.com/roboflow/supervision/pull/245): support for loading PASCAL VOC segmentation datasets with [`sv.DetectionDataset.`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_pascal_voc).
### 0.12.0 <small>July 24, 2023</small>
!!! warning
With the `supervision-0.12.0` release, we are terminating official support for Python 3.7.
- Added [#177](https://github.com/roboflow/supervision/pull/177): initial support for object detection model benchmarking with [`sv.ConfusionMatrix`](https://roboflow.github.io/supervision/metrics/detection/#confusionmatrix).
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... result = model(image)[0]
... return sv.Detections.from_yolov8(result)
>>> confusion_matrix = sv.ConfusionMatrix.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
- Added [#173](https://github.com/roboflow/supervision/pull/173): [`Detections.from_mmdetection`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_mmdetection) to enable seamless integration with [MMDetection](https://github.com/open-mmlab/mmdetection) framework.
- Added [#130](https://github.com/roboflow/supervision/issues/130): ability to [install](https://roboflow.github.io/supervision/) package in `headless` or `desktop` mode.
- Changed [#180](https://github.com/roboflow/supervision/pull/180): packing method from `setup.py` to `pyproject.toml`.
- Fixed [#188](https://github.com/roboflow/supervision/issues/188): [`sv.DetectionDataset.from_cooc`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_coco) can't be loaded when there are images without annotations.
- Fixed [#226](https://github.com/roboflow/supervision/issues/226): [`sv.DetectionDataset.from_yolo`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_yolo) can't load background instances.
### 0.11.1 <small>June 29, 2023</small>
- Fix [#165](https://github.com/roboflow/supervision/pull/165): [`as_folder_structure`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.ClassificationDataset.as_folder_structure) fails to save [`sv.ClassificationDataset`](https://roboflow.github.io/supervision/dataset/core/#classificationdataset) when it is result of inference.
### 0.11.0 <small>June 28, 2023</small>
- Added [#150](https://github.com/roboflow/supervision/pull/150): ability to load and save [`sv.DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) in COCO format using [`as_coco`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.as_coco) and [`from_coco`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.from_coco) methods.
```python
>>> import supervision as sv
>>> ds = sv.DetectionDataset.from_coco(
... images_directory_path='...',
... annotations_path='...'
... )
>>> ds.as_coco(
... images_directory_path='...',
... annotations_path='...'
... )
```
- Added [#158](https://github.com/roboflow/supervision/pull/158): ability to marge multiple [`sv.DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) together using [`merge`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.merge) method.
```python
>>> import supervision as sv
>>> 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']
```
- Added [#162](https://github.com/roboflow/supervision/pull/162): additional `start` and `end` arguments to [`sv.get_video_frames_generator`](https://roboflow.github.io/supervision/utils/video/#get_video_frames_generator) allowing to generate frames only for a selected part of the video.
- Fix [#157](https://github.com/roboflow/supervision/pull/157): incorrect loading of YOLO dataset class names from `data.yaml`.
### 0.10.0 <small>June 14, 2023</small>
- Added [#125](https://github.com/roboflow/supervision/pull/125): ability to load and save [`sv.ClassificationDataset`](https://roboflow.github.io/supervision/dataset/core/#classificationdataset) in a folder structure format.
```python
>>> import supervision as sv
>>> cs = sv.ClassificationDataset.from_folder_structure(
... root_directory_path='...'
... )
>>> cs.as_folder_structure(
... root_directory_path='...'
... )
```
- Added [#125](https://github.com/roboflow/supervision/pull/125): support for [`sv.ClassificationDataset.split`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.ClassificationDataset.split) allowing to divide `sv.ClassificationDataset` into two parts.
- Added [#110](https://github.com/roboflow/supervision/pull/110): ability to extract masks from Roboflow API results using [`sv.Detections.from_roboflow`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_roboflow).
- Added [commit hash](https://github.com/roboflow/supervision/commit/d000292eb2f2342544e0947b65528082e60fb8d6): Supervision Quickstart [notebook](https://colab.research.google.com/github/roboflow/supervision/blob/main/demo.ipynb) where you can learn more about Detection, Dataset and Video APIs.
- Changed [#135](https://github.com/roboflow/supervision/pull/135): `sv.get_video_frames_generator` documentation to better describe actual behavior.
### 0.9.0 <small>June 7, 2023</small>
- Added [#118](https://github.com/roboflow/supervision/pull/118): ability to select [`sv.Detections`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.__getitem__) by index, list of indexes or slice. Here is an example illustrating the new selection methods.
```python
>>> import supervision as sv
>>> detections = sv.Detections(...)
>>> len(detections[0])
1
>>> len(detections[[0, 1]])
2
>>> len(detections[0:2])
2
```
- Added [#101](https://github.com/roboflow/supervision/pull/101): ability to extract masks from YOLOv8 result using [`sv.Detections.from_yolov8`](https://roboflow.github.io/supervision/detection/core/#supervision.detection.core.Detections.from_yolov8). Here is an example illustrating how to extract boolean masks from the result of the YOLOv8 model inference.
- Added [#122](https://github.com/roboflow/supervision/pull/122): ability to crop image using [`sv.crop`](https://roboflow.github.io/supervision/utils/image/#crop). Here is an example showing how to get a separate crop for each detection in `sv.Detections`.
- Added [#120](https://github.com/roboflow/supervision/pull/120): ability to conveniently save multiple images into directory using [`sv.ImageSink`](https://roboflow.github.io/supervision/utils/image/#imagesink). Here is an example showing how to save every tenth video frame as a separate image.
```python
>>> import supervision as sv
>>> with sv.ImageSink(target_dir_path='target/directory/path') as sink:
... for image in sv.get_video_frames_generator(source_path='source_video.mp4', stride=10):
... sink.save_image(image=image)
```
- Fixed [#106](https://github.com/roboflow/supervision/issues/106): inconvenient handling of [`sv.PolygonZone`](https://roboflow.github.io/supervision/detection/tools/polygon_zone/#polygonzone) coordinates. Now `sv.PolygonZone` accepts coordinates in the form of `[[x1, y1], [x2, y2], ...]` that can be both integers and floats.
### 0.8.0 <small>May 17, 2023</small>
- Added [#100](https://github.com/roboflow/supervision/pull/100): support for dataset inheritance. The current `Dataset` got renamed to `DetectionDataset`. Now [`DetectionDataset`](https://roboflow.github.io/supervision/dataset/core/#detectiondataset) inherits from `BaseDataset`. This change was made to enforce the future consistency of APIs of different types of computer vision datasets.
- Added [#100](https://github.com/roboflow/supervision/pull/100): ability to save datasets in YOLO format using [`DetectionDataset.as_yolo`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.as_yolo).
```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("yolov5")
>>> ds = sv.DetectionDataset.from_yolo(
... images_directory_path=f"{dataset.location}/train/images",
... annotations_directory_path=f"{dataset.location}/train/labels",
... data_yaml_path=f"{dataset.location}/data.yaml"
... )
>>> ds.classes
['dog', 'person']
```
- Added [#102](https://github.com/roboflow/supervision/pull/103): support for [`DetectionDataset.split`](https://roboflow.github.io/supervision/dataset/core/#supervision.dataset.core.DetectionDataset.split) allowing to divide `DetectionDataset` into two parts.
```python
>>> import supervision as sv
>>> ds = sv.DetectionDataset(...)
>>> train_ds, test_ds = ds.split(split_ratio=0.7, random_state=42, shuffle=True)
>>> len(train_ds), len(test_ds)
(700, 300)
```
- Changed [#100](https://github.com/roboflow/supervision/pull/100): default value of `approximation_percentage` parameter from `0.75` to `0.0` in `DetectionDataset.as_yolo` and `DetectionDataset.as_pascal_voc`.
### 0.7.0 <small>May 11, 2023</small>
- Added [#91](https://github.com/roboflow/supervision/pull/91): `Detections.from_yolo_nas` to enable seamless integration with [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) model.
- Added [#86](https://github.com/roboflow/supervision/pull/86): ability to load datasets in YOLO format using `Dataset.from_yolo`.
- Added [#84](https://github.com/roboflow/supervision/pull/84): `Detections.merge` to merge multiple `Detections` objects together.
- Fixed [#81](https://github.com/roboflow/supervision/pull/81): `LineZoneAnnotator.annotate` does not return annotated frame.
- Changed [#44](https://github.com/roboflow/supervision/pull/44): `LineZoneAnnotator.annotate` to allow for custom text for the in and out tags.
### 0.6.0 <small>April 19, 2023</small>
- Added [#71](https://github.com/roboflow/supervision/pull/71): initial `Dataset` support and ability to save `Detections` in Pascal VOC XML format.
- Added [#71](https://github.com/roboflow/supervision/pull/71): new `mask_to_polygons`, `filter_polygons_by_area`, `polygon_to_xyxy` and `approximate_polygon` utilities.
- Added [#72](https://github.com/roboflow/supervision/pull/72): ability to load Pascal VOC XML **object detections** dataset as `Dataset`.
- Changed [#70](https://github.com/roboflow/supervision/pull/70): order of `Detections` attributes to make it consistent with order of objects in `__iter__` tuple.
- Changed [#71](https://github.com/roboflow/supervision/pull/71): `generate_2d_mask` to `polygon_to_mask`.
### 0.5.2 <small>April 13, 2023</small>
- Fixed [[#63](https://github.com/roboflow/supervision/pull/63)]: `LineZone.trigger` function expects 4 values instead of 5.
- Fixed [#63](https://github.com/roboflow/supervision/pull/63): `LineZone.trigger` function expects 4 values instead of 5.
### 0.5.1 <small>April 12, 2023</small>
@@ -9,41 +369,41 @@
### 0.5.0 <small>April 10, 2023</small>
- Added [[#58](https://github.com/roboflow/supervision/pull/58)]: `Detections.mask` to enable segmentation support.
- Added [[#58](https://github.com/roboflow/supervision/pull/58)]: `MaskAnnotator` to allow easy `Detections.mask` annotation.
- Added [[#58](https://github.com/roboflow/supervision/pull/58)]: `Detections.from_sam` to enable native Segment Anything Model (SAM) support.
- Changed [[#58](https://github.com/roboflow/supervision/pull/58)]: `Detections.area` behaviour to work not only with boxes but also with masks.
- Added [#58](https://github.com/roboflow/supervision/pull/58): `Detections.mask` to enable segmentation support.
- Added [#58](https://github.com/roboflow/supervision/pull/58): `MaskAnnotator` to allow easy `Detections.mask` annotation.
- Added [#58](https://github.com/roboflow/supervision/pull/58): `Detections.from_sam` to enable native Segment Anything Model (SAM) support.
- Changed [#58](https://github.com/roboflow/supervision/pull/58): `Detections.area` behaviour to work not only with boxes but also with masks.
### 0.4.0 <small>April 5, 2023</small>
### 0.4.0 <small>April 5, 2023</small>
- Added [[#46](https://github.com/roboflow/supervision/discussions/48)]: `Detections.empty` to allow easy creation of empty `Detections` objects.
- Added [[#56](https://github.com/roboflow/supervision/pull/56)]: `Detections.from_roboflow` to allow easy creation of `Detections` objects from Roboflow API inference results.
- Added [[#56](https://github.com/roboflow/supervision/pull/56)]: `plot_images_grid` to allow easy plotting of multiple images on single plot.
- Added [[#56](https://github.com/roboflow/supervision/pull/56)]: initial support for Pascal VOC XML format with `detections_to_voc_xml` method.
- Changed [[#56](https://github.com/roboflow/supervision/pull/56)]: `show_frame_in_notebook` refactored and renamed to `plot_image`.
- Added [#46](https://github.com/roboflow/supervision/discussions/48): `Detections.empty` to allow easy creation of empty `Detections` objects.
- Added [#56](https://github.com/roboflow/supervision/pull/56): `Detections.from_roboflow` to allow easy creation of `Detections` objects from Roboflow API inference results.
- Added [#56](https://github.com/roboflow/supervision/pull/56): `plot_images_grid` to allow easy plotting of multiple images on single plot.
- Added [#56](https://github.com/roboflow/supervision/pull/56): initial support for Pascal VOC XML format with `detections_to_voc_xml` method.
- Changed [#56](https://github.com/roboflow/supervision/pull/56): `show_frame_in_notebook` refactored and renamed to `plot_image`.
### 0.3.2 <small>March 23, 2023</small>
### 0.3.2 <small>March 23, 2023</small>
- Changed [[#50](https://github.com/roboflow/supervision/issues/50)]: Allow `Detections.class_id` to be `None`.
- Changed [#50](https://github.com/roboflow/supervision/issues/50): Allow `Detections.class_id` to be `None`.
### 0.3.1 <small>March 6, 2023</small>
### 0.3.1 <small>March 6, 2023</small>
- Fixed [[#41](https://github.com/roboflow/supervision/issues/41)]: `PolygonZone` throws an exception when the object touches the bottom edge of the image.
- Fixed [[#42](https://github.com/roboflow/supervision/issues/42)]: `Detections.wth_nms` method throws an exception when `Detections` is empty.
- Changed [[#36](https://github.com/roboflow/supervision/pull/36)]: `Detections.wth_nms` support class agnostic and non-class agnostic case.
- Fixed [#41](https://github.com/roboflow/supervision/issues/41): `PolygonZone` throws an exception when the object touches the bottom edge of the image.
- Fixed [#42](https://github.com/roboflow/supervision/issues/42): `Detections.wth_nms` method throws an exception when `Detections` is empty.
- Changed [#36](https://github.com/roboflow/supervision/pull/36): `Detections.wth_nms` support class agnostic and non-class agnostic case.
### 0.3.0 <small>March 6, 2023</small>
### 0.3.0 <small>March 6, 2023</small>
- Changed: Allow `Detections.confidence` to be `None`.
- Added: `Detections.from_transformers` and `Detections.from_detectron2` to enable seamless integration with Transformers and Detectron2 models.
- Added: `Detections.from_transformers` and `Detections.from_detectron2` to enable seamless integration with Transformers and Detectron2 models.
- Added: `Detections.area` to dynamically calculate bounding box area.
- Added: `Detections.wth_nms` to filter out double detections with NMS. Initial - only class agnostic - implementation.
- Added: `Detections.wth_nms` to filter out double detections with NMS. Initial - only class agnostic - implementation.
### 0.2.0 <small>February 2, 2023</small>
### 0.2.0 <small>February 2, 2023</small>
- Added: Advanced `Detections` filtering with pandas-like API.
- Added: `Detections.from_yolov5` and `Detections.from_yolov8` to enable seamless integration with YOLOv5 and YOLOv8 models.
### 0.1.0 <small>January 19, 2023</small>
### 0.1.0 <small>January 19, 2023</small>
Say hello to Supervision 👋
+3
View File
@@ -0,0 +1,3 @@
## Classifications
:::supervision.classification.core.Classifications
+12
View File
@@ -0,0 +1,12 @@
!!! warning
Dataset API is still fluid and may change. If you use Dataset API in your project until further notice, freeze the
`supervision` version in your `requirements.txt` or `setup.py`.
## DetectionDataset
:::supervision.dataset.core.DetectionDataset
## ClassificationDataset
:::supervision.dataset.core.ClassificationDataset
-7
View File
@@ -1,7 +0,0 @@
## BoxAnnotator
:::supervision.detection.annotate.BoxAnnotator
## MaskAnnotator
:::supervision.detection.annotate.MaskAnnotator
+3
View File
@@ -0,0 +1,3 @@
## InferenceSlicer
:::supervision.detection.tools.inference_slicer.InferenceSlicer
+3
View File
@@ -0,0 +1,3 @@
## LineZone
:::supervision.detection.line_counter.LineZone
+1 -1
View File
@@ -4,4 +4,4 @@
## PolygonZoneAnnotator
:::supervision.detection.tools.polygon_zone.PolygonZoneAnnotator
:::supervision.detection.tools.polygon_zone.PolygonZoneAnnotator
+17 -5
View File
@@ -1,7 +1,3 @@
## generate_2d_mask
:::supervision.detection.utils.generate_2d_mask
## box_iou_batch
:::supervision.detection.utils.box_iou_batch
@@ -10,6 +6,22 @@
:::supervision.detection.utils.non_max_suppression
## polygon_to_mask
:::supervision.detection.utils.polygon_to_mask
## mask_to_xyxy
:::supervision.detection.utils.mask_to_xyxy
:::supervision.detection.utils.mask_to_xyxy
## mask_to_polygons
:::supervision.detection.utils.mask_to_polygons
## polygon_to_xyxy
:::supervision.detection.utils.polygon_to_xyxy
## filter_polygons_by_area
:::supervision.detection.utils.filter_polygons_by_area
+7
View File
@@ -0,0 +1,7 @@
## Color
:::supervision.draw.color.Color
## ColorPalette
:::supervision.draw.color.ColorPalette
+5 -1
View File
@@ -16,4 +16,8 @@
## draw_text
:::supervision.draw.utils.draw_text
:::supervision.draw.utils.draw_text
## draw_image
:::supervision.draw.utils.draw_image
+3
View File
@@ -0,0 +1,3 @@
## Position
:::supervision.geometry.core.Position
+78
View File
@@ -0,0 +1,78 @@
With Supervision, you can easily [annotate](https://supervision.roboflow.com/annotators/) predictions obtained from a variety of object detection and segmentation models. This document outlines how to run inference using the [Ultralytics](https://github.com/ultralytics/ultralytics) YOLOv8 model, load these predictions into Supervision, and annotate the image.
## Run Inference
First, you'll need to obtain predictions from your object detection or segmentation model.
```python
import cv2
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread("image.jpg")
results = model(image)[0]
```
## Load Predictions into Supervision
Now that we have predictions from a model, we can load them into Supervision. We can do so using the [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models.
```python
import cv2
from ultralytics import YOLO
import supervision as sv
model = YOLO("yolov8n.pt")
image = cv2.imread("image.jpg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
```
You can conveniently load predictions from other computer vision frameworks and libraries using:
- [`from_deepsparse`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_deepsparse) ([Deepsparse](https://github.com/neuralmagic/deepsparse))
- [`from_detectron2`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_detectron2) ([Detectron2](https://github.com/facebookresearch/detectron2))
- [`from_mmdetection`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_mmdetection) ([MMDetection](https://github.com/open-mmlab/mmdetection))
- [`from_roboflow`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_roboflow) ([Roboflow Inference](https://github.com/roboflow/inference))
- [`from_sam`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_sam) ([Segment Anything Model](https://github.com/facebookresearch/segment-anything))
- [`from_transformers`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_transformers) ([HuggingFace Transformers](https://github.com/huggingface/transformers))
- [`from_yolo_nas`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_yolo_nas) ([YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md))
## Annotate Image
Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) and [`sv.LabelAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.LabelAnnotator) classes. If you are running the segmentation model [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) is a drop-in replacement for [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) that will allow you to draw masks instead of boxes.
```python
import cv2
from ultralytics import YOLO
import supervision as sv
model = YOLO("yolov8n.pt")
image = cv2.imread("image.jpg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
bounding_box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator()
labels = [
results.names[class_id]
for class_id
in detections.class_id
]
annotated_image = bounding_box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections, labels=labels)
```
![Predictions plotted on an image](https://media.roboflow.com/supervision_annotate_example.png)
## Display Annotated Image
To display the annotated image in Jupyter Notebook or Google Colab, use the [`sv.plot_image`](https://supervision.roboflow.com/utils/notebook/#supervision.utils.notebook.plot_image) function.
```python
sv.plot_image(annotated_image)
```
+1
View File
@@ -0,0 +1 @@
🚧 Page under construction.
+309
View File
@@ -0,0 +1,309 @@
The advanced filtering capabilities of the `Detections` class offer users a versatile and efficient way to narrow down
and refine object detections. This section outlines various filtering methods, including filtering by specific class
or a set of classes, confidence, object area, bounding box area, relative area, box dimensions, and designated zones.
Each method is demonstrated with concise code examples to provide users with a clear understanding of how to implement
the filters in their applications.
### by specific class
Allows you to select detections that belong only to one selected class.
=== "After"
```python
import supervision as sv
detections = sv.Detections(...)
detections = detections[detections.class_id == 0]
```
<div class="result" markdown>
![by-specific-class](https://media.roboflow.com/open-source/supervision/supervision-detection-by-specific-class.png){ align=center width="800" }
</div>
=== "Before"
```python
import supervision as sv
detections = sv.Detections(...)
detections = detections[detections.class_id == 0]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
### by set of classes
Allows you to select detections that belong only to selected set of classes.
=== "After"
```python
import numpy as np
import supervision as sv
selected_classes = [0, 2, 3]
detections = sv.Detections(...)
detections = detections[np.isin(detections.class_id, selected_classes)]
```
<div class="result" markdown>
![by-set-of-classes](https://media.roboflow.com/open-source/supervision/supervision-detection-by-set-of-classes.png){ align=center width="800" }
</div>
=== "Before"
```python
import numpy as np
import supervision as sv
class_id = [0, 2, 3]
detections = sv.Detections(...)
detections = detections[np.isin(detections.class_id, class_id)]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
### by confidence
Allows you to select detections with specific confidence value, for example higher than selected threshold.
=== "After"
```python
import supervision as sv
detections = sv.Detections(...)
detections = detections[detections.confidence > 0.5]
```
<div class="result" markdown>
![by-set-of-classes](https://media.roboflow.com/open-source/supervision/supervision-detection-by-confidence.png){ align=center width="800" }
</div>
=== "Before"
```python
import supervision as sv
detections = sv.Detections(...)
detections = detections[detections.confidence > 0.5]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
### by area
Allows you to select detections based on their size. We define the area as the number of pixels occupied by the
detection in the image. In the example below, we have sifted out the detections that are too small.
=== "After"
```python
import supervision as sv
detections = sv.Detections(...)
detections = detections[detections.area > 1000]
```
<div class="result" markdown>
![by-area](https://media.roboflow.com/open-source/supervision/supervision-detection-by-area.png){ align=center width="800" }
</div>
=== "Before"
```python
import supervision as sv
detections = sv.Detections(...)
detections = detections[detections.area > 1000]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
### by relative area
Allows you to select detections based on their size in relation to the size of whole image. Sometimes the concept of
detection size changes depending on the image. Detection occupying 10000 square px can be large on a 1280x720 image
but small on a 3840x2160 image. In such cases, we can filter out detections based on the percentage of the image area
occupied by them. In the example below, we remove too large detections.
=== "After"
```python
import supervision as sv
image = ...
height, width, channels = image.shape
image_area = height * width
detections = sv.Detections(...)
detections = detections[(detections.area / image_area) < 0.8]
```
<div class="result" markdown>
![by-relative-area](https://media.roboflow.com/open-source/supervision/supervision-detection-by-relative-area.png?updatedAt=1683207183434){ align=center width="800" }
</div>
=== "Before"
```python
import supervision as sv
image = ...
height, width, channels = image.shape
image_area = height * width
detections = sv.Detections(...)
detections = detections[(detections.area / image_area) < 0.8]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
### by box dimensions
Allows you to select detections based on their dimensions. The size of the bounding box, as well as its coordinates,
can be criteria for rejecting detection. Implementing such filtering requires a bit of custom code but is relatively
simple and fast.
=== "After"
```python
import supervision as sv
detections = sv.Detections(...)
w = detections.xyxy[:, 2] - detections.xyxy[:, 0]
h = detections.xyxy[:, 3] - detections.xyxy[:, 1]
detections = detections[(w > 200) & (h > 200)]
```
<div class="result" markdown>
![by-box-dimensions](https://media.roboflow.com/open-source/supervision/supervision-detection-by-box-dimensions.png){ align=center width="800" }
</div>
=== "Before"
```python
import supervision as sv
detections = sv.Detections(...)
w = detections.xyxy[:, 2] - detections.xyxy[:, 0]
h = detections.xyxy[:, 3] - detections.xyxy[:, 1]
detections = detections[(w > 200) & (h > 200)]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
### by `PolygonZone`
Allows you to use `Detections` in combination with `PolygonZone` to weed out bounding boxes that are in and out of the
zone. In the example below you can see how to filter out all detections located in the lower part of the image.
=== "After"
```python
import supervision as sv
zone = sv.PolygonZone(...)
detections = sv.Detections(...)
mask = zone.trigger(detections=detections)
detections = detections[mask]
```
<div class="result" markdown>
![by-polygon-zone](https://media.roboflow.com/open-source/supervision/supervision-detection-by-polygon-zone.png?updatedAt=1683211380445){ align=center width="800" }
</div>
=== "Before"
```python
import supervision as sv
zone = sv.PolygonZone(...)
detections = sv.Detections(...)
mask = zone.trigger(detections=detections)
detections = detections[mask]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
### by mixed conditions
`Detections`' greatest strength, however, is that you can build arbitrarily complex logical conditions by simply combining separate conditions using `&` or `|`.
=== "After"
```python
import supervision as sv
zone = sv.PolygonZone(...)
detections = sv.Detections(...)
mask = zone.trigger(detections=detections)
detections = detections[(detections.confidence > 0.7) & mask]
```
<div class="result" markdown>
![by-mixed-conditions](https://media.roboflow.com/open-source/supervision/supervision-detection-by-mixed-conditions.png){ align=center width="800" }
</div>
=== "Before"
```python
import supervision as sv
zone = sv.PolygonZone(...)
detections = sv.Detections(...)
mask = zone.trigger(detections=detections)
detections = detections[mask]
```
<div class="result" markdown>
![original](https://media.roboflow.com/open-source/supervision/supervision-detection-original.png){ align=center width="800" }
</div>
+1
View File
@@ -0,0 +1 @@
🚧 Page under construction.
+1
View File
@@ -0,0 +1 @@
🚧 Page under construction.
+55 -18
View File
@@ -9,29 +9,66 @@
</p>
</div>
## 👋 Welcome
## 👋 Hello
Supervision is a set of easy-to-use utilities that will come in handy in any computer vision project.
We write your reusable computer vision tools. Whether you need to load your dataset from your hard drive, draw detections on an image or video, or count how many detections are in a zone. You can count on us!
**Supervision** is still in
pre-release stage 🚧 Keep your eyes open for potential bugs and be aware that at this stage our API is still fluid and may change.
## 💻 Install
## 💻 How to Install
You can install `supervision` with pip in a
[**3.11>=Python>=3.8**](https://www.python.org/) environment.
You can install `supervision` with pip in a
[**3.10>=Python>=3.7**](https://www.python.org/) environment.
!!! example "pip install (recommended)"
!!! example "Pip install method (recommended)"
=== "headless"
The headless installation of `supervision` is designed for environments where graphical user interfaces (GUI) are not needed, making it more lightweight and suitable for server-side applications.
```bash
pip install supervision
```
```bash
pip install supervision
```
!!! example "Git clone method (for development)"
=== "desktop"
If you require the full version of `supervision` with GUI support you can install the desktop version. This version includes the GUI components of OpenCV, allowing you to display images and videos on the screen.
```bash
git https://github.com/roboflow/supervision.git
cd supervision
pip install -e '.[dev]'
```
See contributing section to know more about contributing to the project
```bash
pip install supervision[desktop]
```
!!! example "git clone (for development)"
=== "virtualenv"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
# headless install
pip install -e "."
# desktop install
pip install -e ".[desktop]"
```
=== "poetry"
```bash
# clone repository and navigate to root directory
git clone https://github.com/roboflow/supervision.git
cd supervision
# setup python environment and activate it
poetry env use python3.10
poetry shell
# headless install
poetry install
# desktop install
poetry install --extras "desktop"
```
+12
View File
@@ -0,0 +1,12 @@
!!! warning
Evaluation API is still fluid and may change. If you use Evaluation API in your project until further notice, freeze the
`supervision` version in your `requirements.txt` or `setup.py`.
## ConfusionMatrix
:::supervision.metrics.detection.ConfusionMatrix
## MeanAveragePrecision
:::supervision.metrics.detection.MeanAveragePrecision
-7
View File
@@ -1,7 +0,0 @@
## plot_image
:::supervision.notebook.utils.plot_image
## plot_images_grid
:::supervision.notebook.utils.plot_images_grid
+4
View File
@@ -0,0 +1,4 @@
:root {
--md-primary-fg-color: #8315F9;
--md-accent-fg-color: #00FFCE;
}
+3
View File
@@ -0,0 +1,3 @@
## ByteTrack
:::supervision.tracker.byte_tracker.core.ByteTrack
+3
View File
@@ -0,0 +1,3 @@
## list_files_with_extensions
:::supervision.utils.file.list_files_with_extensions
+7
View File
@@ -0,0 +1,7 @@
## ImageSink
:::supervision.utils.image.ImageSink
## crop
:::supervision.utils.image.crop_image
+7
View File
@@ -0,0 +1,7 @@
## plot_image
:::supervision.utils.notebook.plot_image
## plot_images_grid
:::supervision.utils.notebook.plot_images_grid
+19
View File
@@ -0,0 +1,19 @@
## VideoInfo
:::supervision.utils.video.VideoInfo
## VideoSink
:::supervision.utils.video.VideoSink
## FPSMonitor
:::supervision.utils.video.FPSMonitor
## get_video_frames_generator
:::supervision.utils.video.get_video_frames_generator
## process_video
:::supervision.utils.video.process_video
-15
View File
@@ -1,15 +0,0 @@
## VideoInfo
:::supervision.video.VideoInfo
## VideoSink
:::supervision.video.VideoSink
## get_video_frames_generator
:::supervision.video.get_video_frames_generator
## process_video
:::supervision.video.process_video
+31
View File
@@ -0,0 +1,31 @@
## Supervision Examples
This repository is packed with real-world use-cases, provided through Python scripts or
interactive notebooks. Browse through to understand how the Supervision library
interfaces with diverse applications.
| **Title** | **Contributor** |
|:--------------------------------------:|:----------------------------------------:|
| [Tracking](./tracking) | [@SkalskiP](https://github.com/SkalskiP) |
| [Traffic Analysis](./traffic_analysis) | [@SkalskiP](https://github.com/SkalskiP) |
## Guidelines for Contributing
We welcome contributions from the community in the form of examples, applications, and
guides. To contribute, please follow these steps:
1. Create a pull request (PR) with the `[Example]` prefix in the title, adding your
project folder to the `examples/` directory in the repository.
2. Confirm your project aligns with the following standards:
- Incorporates the `supervision` package.
- Provides a `README.md` file, detailing the instructions to execute the project.
- Showcases visual results, demonstrating the app's functionality.
- Avoids adding large assets or dependencies unless absolutely necessary.
- The contributor is expected to provide support for issues related to their
examples.
- In case the presented model has licensing complications, kindly specify them to
circumvent potential misunderstandings.
For inquiries or concerns about these prerequisites, feel free to raise a PR. We are
committed to assist and guide you.
+20
View File
@@ -0,0 +1,20 @@
## 👋 hello
This script provides functionality for processing videos using YOLOv8 for object
detection and Supervision for tracking and annotation.
## 💻 install
```bash
pip install -r requirements.txt
```
## ⚙️ parameters
| parameter | required | description |
|:-------------------------|:--------:|:----------------------------------------------------------------------------------|
| `--source_weights_path` | ✓ | Path to the source weights file for YOLOv8. |
| `--source_video_path` | ✓ | Path to the source video file to be processed. |
| `--target_video_path` | ✓ | Path to the target video file (output). |
| `--confidence_threshold` | ✗ | Confidence threshold for YOLO model detection. Default is 0.3. |
| `--iou_threshold` | ✗ | IOU (Intersection over Union) threshold for YOLO model detection. Default is 0.7. |
+3
View File
@@ -0,0 +1,3 @@
supervision
tqdm
ultralytics
+83
View File
@@ -0,0 +1,83 @@
import argparse
from tqdm import tqdm
from ultralytics import YOLO
import supervision as sv
def process_video(
source_weights_path: str,
source_video_path: str,
target_video_path: str,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
) -> None:
model = YOLO(source_weights_path)
tracker = sv.ByteTrack()
box_annotator = sv.BoundingBoxAnnotator()
label_annotator = sv.LabelAnnotator(classes=model.names)
frame_generator = sv.get_video_frames_generator(source_path=source_video_path)
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
with sv.VideoSink(target_path=target_video_path, video_info=video_info) as sink:
for frame in tqdm(frame_generator, total=video_info.total_frames):
results = model(
frame, verbose=False, conf=confidence_threshold, iou=iou_threshold
)[0]
detections = sv.Detections.from_ultralytics(results)
detections = tracker.update_with_detections(detections)
annotated_frame = box_annotator.annotate(
scene=frame.copy(), detections=detections
)
annotated_labeled_frame = label_annotator.annotate(
scene=annotated_frame, detections=detections
)
sink.write_frame(frame=annotated_labeled_frame)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Video Processing with YOLO and ByteTrack"
)
parser.add_argument(
"--source_weights_path",
required=True,
help="Path to the source weights file",
type=str,
)
parser.add_argument(
"--source_video_path",
required=True,
help="Path to the source video file",
type=str,
)
parser.add_argument(
"--target_video_path",
required=True,
help="Path to the target video file (output)",
type=str,
)
parser.add_argument(
"--confidence_threshold",
default=0.3,
help="Confidence threshold for the model",
type=float,
)
parser.add_argument(
"--iou_threshold", default=0.7, help="IOU threshold for the model", type=float
)
args = parser.parse_args()
process_video(
source_weights_path=args.source_weights_path,
source_video_path=args.source_video_path,
target_video_path=args.target_video_path,
confidence_threshold=args.confidence_threshold,
iou_threshold=args.iou_threshold,
)
+1
View File
@@ -0,0 +1 @@
data/
+44
View File
@@ -0,0 +1,44 @@
## 👋 hello
This script performs traffic flow analysis using YOLOv8, an object-detection method and ByteTrack, a simple yet effective online multi-object tracking method. It uses the supervision package for multiple tasks such as tracking, annotations, etc.
https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-60e9c81b3900
## 💻 install
- clone repository and navigate to example directory
```bash
git clone https://github.com/roboflow/supervision.git
cd supervision/examples/traffic_analysis
```
- setup python environment and activate it [optional]
```bash
python3 -m venv venv
source venv/bin/activate
```
- install required dependencies
```bash
pip install -r requirements.txt
```
- download `traffic_analysis.pt` and `traffic_analysis.mov` files
```bash
./setup.sh
```
## ⚙️ run
```bash
python script.py \
--source_weights_path data/traffic_analysis.pt \
--source_video_path data/traffic_analysis.mov \
--confidence_threshold 0.3 \
--iou_threshold 0.5 \
--target_video_path data/traffic_analysis_result.mov
```
@@ -0,0 +1,4 @@
supervision>=0.15.0rc1
tqdm
ultralytics
gdown
+219
View File
@@ -0,0 +1,219 @@
import argparse
from typing import Dict, List, Set, Tuple
import cv2
import numpy as np
from tqdm import tqdm
from ultralytics import YOLO
import supervision as sv
COLORS = sv.ColorPalette.default()
ZONE_IN_POLYGONS = [
np.array([[592, 282], [900, 282], [900, 82], [592, 82]]),
np.array([[950, 860], [1250, 860], [1250, 1060], [950, 1060]]),
np.array([[592, 582], [592, 860], [392, 860], [392, 582]]),
np.array([[1250, 282], [1250, 530], [1450, 530], [1450, 282]]),
]
ZONE_OUT_POLYGONS = [
np.array([[950, 282], [1250, 282], [1250, 82], [950, 82]]),
np.array([[592, 860], [900, 860], [900, 1060], [592, 1060]]),
np.array([[592, 282], [592, 550], [392, 550], [392, 282]]),
np.array([[1250, 860], [1250, 560], [1450, 560], [1450, 860]]),
]
class DetectionsManager:
def __init__(self) -> None:
self.tracker_id_to_zone_id: Dict[int, int] = {}
self.counts: Dict[int, Dict[int, Set[int]]] = {}
def update(
self,
detections_all: sv.Detections,
detections_in_zones: List[sv.Detections],
detections_out_zones: List[sv.Detections],
) -> sv.Detections:
for zone_in_id, detections_in_zone in enumerate(detections_in_zones):
for tracker_id in detections_in_zone.tracker_id:
self.tracker_id_to_zone_id.setdefault(tracker_id, zone_in_id)
for zone_out_id, detections_out_zone in enumerate(detections_out_zones):
for tracker_id in detections_out_zone.tracker_id:
if tracker_id in self.tracker_id_to_zone_id:
zone_in_id = self.tracker_id_to_zone_id[tracker_id]
self.counts.setdefault(zone_out_id, {})
self.counts[zone_out_id].setdefault(zone_in_id, set())
self.counts[zone_out_id][zone_in_id].add(tracker_id)
detections_all.class_id = np.vectorize(
lambda x: self.tracker_id_to_zone_id.get(x, -1)
)(detections_all.tracker_id)
return detections_all[detections_all.class_id != -1]
def initiate_polygon_zones(
polygons: List[np.ndarray],
frame_resolution_wh: Tuple[int, int],
triggering_position: sv.Position = sv.Position.CENTER,
) -> List[sv.PolygonZone]:
return [
sv.PolygonZone(
polygon=polygon,
frame_resolution_wh=frame_resolution_wh,
triggering_position=triggering_position,
)
for polygon in polygons
]
class VideoProcessor:
def __init__(
self,
source_weights_path: str,
source_video_path: str,
target_video_path: str = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
) -> None:
self.conf_threshold = confidence_threshold
self.iou_threshold = iou_threshold
self.source_video_path = source_video_path
self.target_video_path = target_video_path
self.model = YOLO(source_weights_path)
self.tracker = sv.ByteTrack()
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
)
self.zones_out = initiate_polygon_zones(
ZONE_OUT_POLYGONS, self.video_info.resolution_wh, sv.Position.CENTER
)
self.box_annotator = sv.BoxAnnotator(color=COLORS)
self.trace_annotator = sv.TraceAnnotator(
color=COLORS, position=sv.Position.CENTER, trace_length=100, thickness=2
)
self.detections_manager = DetectionsManager()
def process_video(self):
frame_generator = sv.get_video_frames_generator(
source_path=self.source_video_path
)
if self.target_video_path:
with sv.VideoSink(self.target_video_path, self.video_info) as sink:
for frame in tqdm(frame_generator, total=self.video_info.total_frames):
annotated_frame = self.process_frame(frame)
sink.write_frame(annotated_frame)
else:
for frame in tqdm(frame_generator, total=self.video_info.total_frames):
annotated_frame = self.process_frame(frame)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
def annotate_frame(
self, frame: np.ndarray, detections: sv.Detections
) -> np.ndarray:
annotated_frame = frame.copy()
for i, (zone_in, zone_out) in enumerate(zip(self.zones_in, self.zones_out)):
annotated_frame = sv.draw_polygon(
annotated_frame, zone_in.polygon, COLORS.colors[i]
)
annotated_frame = sv.draw_polygon(
annotated_frame, zone_out.polygon, COLORS.colors[i]
)
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, detections, labels
)
for zone_out_id, zone_out in enumerate(self.zones_out):
zone_center = sv.get_polygon_center(polygon=zone_out.polygon)
if zone_out_id in self.detections_manager.counts:
counts = self.detections_manager.counts[zone_out_id]
for i, zone_in_id in enumerate(counts):
count = len(self.detections_manager.counts[zone_out_id][zone_in_id])
text_anchor = sv.Point(x=zone_center.x, y=zone_center.y + 40 * i)
annotated_frame = sv.draw_text(
scene=annotated_frame,
text=str(count),
text_anchor=text_anchor,
background_color=COLORS.colors[zone_in_id],
)
return annotated_frame
def process_frame(self, frame: np.ndarray) -> np.ndarray:
results = self.model(
frame, verbose=False, conf=self.conf_threshold, iou=self.iou_threshold
)[0]
detections = sv.Detections.from_ultralytics(results)
detections.class_id = np.zeros(len(detections))
detections = self.tracker.update_with_detections(detections)
detections_in_zones = []
detections_out_zones = []
for i, (zone_in, zone_out) in enumerate(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)]
detections_out_zones.append(detections_out_zone)
detections = self.detections_manager.update(
detections, detections_in_zones, detections_out_zones
)
return self.annotate_frame(frame, detections)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Traffic Flow Analysis with YOLO and ByteTrack"
)
parser.add_argument(
"--source_weights_path",
required=True,
help="Path to the source weights file",
type=str,
)
parser.add_argument(
"--source_video_path",
required=True,
help="Path to the source video file",
type=str,
)
parser.add_argument(
"--target_video_path",
default=None,
help="Path to the target video file (output)",
type=str,
)
parser.add_argument(
"--confidence_threshold",
default=0.3,
help="Confidence threshold for the model",
type=float,
)
parser.add_argument(
"--iou_threshold", default=0.7, help="IOU threshold for the model", type=float
)
args = parser.parse_args()
processor = VideoProcessor(
source_weights_path=args.source_weights_path,
source_video_path=args.source_video_path,
target_video_path=args.target_video_path,
confidence_threshold=args.confidence_threshold,
iou_threshold=args.iou_threshold,
)
processor.process_video()
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# Get the directory where the script is located
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Check if 'data' directory does not exist and then create it
if [[ ! -e $DIR/data ]]; then
mkdir "$DIR/data"
else
echo "'data' directory already exists."
fi
# Download the traffic_analysis.mov file from Google Drive
gdown -O "$DIR/data/traffic_analysis.mov" "https://drive.google.com/uc?id=1qadBd7lgpediafCpL_yedGjQPk-FLK-W"
# Download the traffic_analysis.pt file from Google Drive
gdown -O "$DIR/data/traffic_analysis.pt" "https://drive.google.com/uc?id=1y-IfToCjRXa3ZdC1JpnKRopC7mcQW-5z"
+47 -14
View File
@@ -1,5 +1,5 @@
site_name: Supervision
site_url: https://roboflow.github.io/supervision
site_url: https://supervision.roboflow.com/
site_author: Roboflow
site_description: A set of easy-to-use utils that will come in handy in any Computer Vision project
repo_name: roboflow/supervision
@@ -21,22 +21,40 @@ extra:
provider: google
property: G-P7ZG0Y19G5
extra_css:
- stylesheets/extra.css
nav:
- Home: index.md
- API reference:
- Video: video.md
- Detection:
- How to:
- Detect and Annotate: how_to/detect_and_annotate.md
- Process Video: how_to/process_video.md
- Track Objects: how_to/track_objects.md
- Filter Detections: how_to/filter_detections.md
- Evaluate Model: how_to/evaluate_model.md
- API:
- Classifications:
- Core: classification/core.md
- Detections:
- Core: detection/core.md
- Annotate: detection/annotate.md
- Utils: detection/utils.md
- Tools:
- Polygon Zone: detection/tools/polygon_zone.md
- Tools:
- Line Zone: detection/tools/line_zone.md
- Polygon Zone: detection/tools/polygon_zone.md
- Inference Slicer: detection/tools/inference_slicer.md
- Annotators: annotators.md
- Trackers: trackers.md
- Datasets: datasets.md
- Metrics:
- Object Detection: metrics/detection.md
- Draw:
- Color: draw/color.md
- Utils: draw/utils.md
- Annotations:
- Pascal VOC XML: annotation/voc.md
- Notebook:
- Utils: notebook/utils.md
- Utils:
- Video: utils/video.md
- Image: utils/image.md
- Notebook: utils/notebook.md
- File: utils/file.md
- Changelog: changelog.md
theme:
@@ -44,8 +62,19 @@ theme:
logo: https://media.roboflow.com/open-source/supervision/supervision-lenny.png
favicon: https://media.roboflow.com/open-source/supervision/supervision-lenny.png
palette:
primary: 'deep purple'
accent: 'teal'
# Palette for light mode
- scheme: default
primary: 'custom'
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- scheme: slate
primary: 'custom'
toggle:
icon: material/brightness-4
name: Switch to light mode
font:
text: Roboto
code: Roboto Mono
@@ -58,5 +87,9 @@ markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- attr_list
- md_in_html
- pymdownx.tabbed:
alternate_style: true
- toc:
permalink: true
permalink: true
Generated
+3523
View File
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
[tool.poetry]
name = "supervision"
version = "0.16.0"
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
authors = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
maintainers = ["Piotr Skalski <piotr.skalski92@gmail.com>"]
readme = "README.md"
license = "MIT"
packages = [{include = "supervision"}]
homepage = "https://github.com/roboflow/supervision"
repository = "https://github.com/roboflow/supervision"
documentation = "https://github.com/roboflow/supervision/blob/main/README.md"
keywords = ["machine-learning", "deep-learning", "vision", "ML", "DL", "AI", "YOLOv5", "YOLOv8", "Roboflow"]
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development',
'Topic :: Scientific/Engineering',
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
'Typing :: Typed',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux',
'Operating System :: MacOS'
]
[tool.poetry.dependencies]
python = ">=3.8,<3.12.0"
numpy = "^1.20.0"
matplotlib = "^3.7.1"
pyyaml = "^6.0"
pillow = ">=9.4,<11.0"
opencv-python = { version = "^4.8.0.74", optional = true }
opencv-python-headless = "^4.8.0.74"
scipy = "^1.9.0"
[tool.poetry.extras]
desktop = ["opencv-python"]
[tool.poetry.group.dev.dependencies]
twine = "^4.0.2"
pytest = "^7.2.2"
wheel = "^0.40.0"
notebook = "^6.5.3"
build = "^0.10.0"
ruff = "^0.0.280"
isort = "^5.12.0"
black = "^23.7.0"
mypy = "^1.4.1"
pre-commit = "^3.3.3"
flake8 = { version = "*", python = ">=3.8.1,<3.12.0" }
[tool.poetry.group.docs.dependencies]
mkdocs-material = "^9.1.4"
mkdocstrings = {extras = ["python"], version = "^0.20.0"}
[tool.flake8]
exclude = ".venv"
max-complexity = 10
max-line-length = 88
extend-ignore = """
W503,
E203,
E701,
C901,
"""
per-file-ignores = """
__init__.py: F401
"""
[tool.isort]
line_length = 88
profile = "black"
[tool.bandit]
target = ["test", "supervision"]
tests = ["B201", "B301"]
[tool.autoflake]
check = true
imports = ["cv2", "supervision"]
[tool.black]
target-version = ["py38"]
line-length = 88
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| docs
)/
'''
[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 = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
"yarn-error.log",
"yarn.lock",
"docs",
]
# Same as Black.
line-length = 88
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.flake8-quotes]
inline-quotes = "double"
multiline-quotes = "double"
docstring-quotes = "double"
[tool.ruff.pydocstyle]
convention = "google"
[tool.ruff.per-file-ignores]
"__init__.py" = ["E402","F401"]
[tool.ruff.pylint]
max-args = 20
[tool.setuptools]
include-package-data = false
[tool.setuptools.packages.find]
exclude = ["docs*", "test*","examples*"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
-67
View File
@@ -1,67 +0,0 @@
import setuptools
from setuptools import find_packages
import re
from pathlib import Path
FILE = Path(__file__).resolve()
PARENT = FILE.parent # root directory
README = (PARENT / "README.md").read_text(encoding="utf-8")
def get_version():
file = PARENT / 'supervision/__init__.py'
return re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', file.read_text(encoding="utf-8"), re.M)[1]
setuptools.setup(
name='supervision',
version=get_version(),
author='Piotr Skalski',
author_email='piotr.skalski92@gmail.com',
license='MIT',
description='A set of easy-to-use utils that will come in handy in any Computer Vision project',
long_description=README,
long_description_content_type='text/markdown',
url='https://github.com/roboflow/supervision',
install_requires=[
'numpy>=1.20.0',
'opencv-python',
'matplotlib'
],
packages=find_packages(exclude=("tests",)),
extras_require={
'dev': [
'flake8',
'black==22.3.0',
'isort',
'twine',
'pytest',
'wheel',
'notebook',
'mkdocs-material',
'mkdocstrings[python]'
],
},
classifiers=[
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development',
'Topic :: Scientific/Engineering',
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
'Typing :: Typed',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Operating System :: Unix',
'Operating System :: MacOS'
],
keywords="machine-learning, deep-learning, vision, ML, DL, AI, YOLOv5, YOLOv8, Roboflow",
python_requires='>=3.7',
)
+54 -7
View File
@@ -1,17 +1,64 @@
__version__ = "0.5.2"
import importlib.metadata as importlib_metadata
from supervision.annotation.voc import detections_to_voc_xml
from supervision.detection.annotate import BoxAnnotator, MaskAnnotator
try:
# This will read version from pyproject.toml
__version__ = importlib_metadata.version(__package__ or __name__)
except importlib_metadata.PackageNotFoundError:
__version__ = "development"
from supervision.annotators.core import (
BlurAnnotator,
BoundingBoxAnnotator,
BoxCornerAnnotator,
BoxMaskAnnotator,
CircleAnnotator,
DotAnnotator,
EllipseAnnotator,
HaloAnnotator,
HeatMapAnnotator,
LabelAnnotator,
MaskAnnotator,
TraceAnnotator,
)
from supervision.annotators.utils import ColorLookup
from supervision.classification.core import Classifications
from supervision.dataset.core import (
BaseDataset,
ClassificationDataset,
DetectionDataset,
)
from supervision.detection.annotate import BoxAnnotator
from supervision.detection.core import Detections
from supervision.detection.line_counter import LineZone, LineZoneAnnotator
from supervision.detection.tools.inference_slicer import InferenceSlicer
from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator
from supervision.detection.utils import generate_2d_mask, mask_to_xyxy
from supervision.detection.utils import (
box_iou_batch,
filter_polygons_by_area,
mask_to_polygons,
mask_to_xyxy,
non_max_suppression,
polygon_to_mask,
polygon_to_xyxy,
)
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import draw_filled_rectangle, draw_polygon, draw_text
from supervision.draw.utils import (
draw_filled_rectangle,
draw_image,
draw_line,
draw_polygon,
draw_rectangle,
draw_text,
)
from supervision.geometry.core import Point, Position, Rect
from supervision.geometry.utils import get_polygon_center
from supervision.notebook.utils import plot_image, plot_images_grid
from supervision.video import (
from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision
from supervision.tracker.byte_tracker.core import ByteTrack
from supervision.utils.file import list_files_with_extensions
from supervision.utils.image import ImageSink, crop_image
from supervision.utils.notebook import plot_image, plot_images_grid
from supervision.utils.video import (
FPSMonitor,
VideoInfo,
VideoSink,
get_video_frames_generator,
-104
View File
@@ -1,104 +0,0 @@
from typing import List
from xml.dom.minidom import parseString
from xml.etree.ElementTree import Element, SubElement, tostring
from supervision.detection.core import Detections
def detections_to_voc_xml(
detections: Detections,
classes: List[str],
filename: str,
width: int,
height: int,
depth: int = 3,
) -> str:
"""
Converts Detections object to Pascal VOC XML format.
Args:
detections (Detections): A Detections object containing bounding boxes, class ids, and other relevant information.
classes (List[str]): A list of class names corresponding to the class ids in the Detections object.
filename (str): The name of the image file associated with the detections.
width (int): The width of the image in pixels.
height (int): The height of the image in pixels.
depth (int, optional): The number of color channels in the image. Defaults to 3 for RGB images.
Returns:
str: An XML string in Pascal VOC format representing the detections.
Examples:
```python
>>> import numpy as np
>>> import supervision as sv
>>> xyxy = np.array([
... [50, 30, 200, 180],
... [20, 40, 150, 190]
... ])
>>> class_id = np.array([1, 0])
>>> detections = Detections(xyxy=xyxy, class_id=class_id)
>>> classes = ["dog", "cat"]
>>> voc_xml = detections_to_voc_xml(
... detections=detections,
... classes=classes,
... filename="image1.jpg",
... width=500,
... height=400
... )
```
"""
# Create root element
annotation = Element("annotation")
# Add folder element
folder = SubElement(annotation, "folder")
folder.text = "VOC"
# Add filename element
fname = SubElement(annotation, "filename")
fname.text = filename
# Add source element
source = SubElement(annotation, "source")
database = SubElement(source, "database")
database.text = "roboflow.ai"
# Add size element
size = SubElement(annotation, "size")
w = SubElement(size, "width")
w.text = str(width)
h = SubElement(size, "height")
h.text = str(height)
d = SubElement(size, "depth")
d.text = str(depth)
# Add segmented element
segmented = SubElement(annotation, "segmented")
segmented.text = "0"
# Add object elements
for i in range(detections.xyxy.shape[0]):
obj = SubElement(annotation, "object")
class_id = detections.class_id[i] if detections.class_id is not None else None
name = SubElement(obj, "name")
name.text = classes[class_id] if class_id is not None else "unknown"
bndbox = SubElement(obj, "bndbox")
xmin = SubElement(bndbox, "xmin")
xmin.text = str(int(detections.xyxy[i, 0]))
ymin = SubElement(bndbox, "ymin")
ymin.text = str(int(detections.xyxy[i, 1]))
xmax = SubElement(bndbox, "xmax")
xmax.text = str(int(detections.xyxy[i, 2]))
ymax = SubElement(bndbox, "ymax")
ymax.text = str(int(detections.xyxy[i, 3]))
# Generate XML string
xml_string = parseString(tostring(annotation)).toprettyxml(indent=" ")
return xml_string
+11
View File
@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod
import numpy as np
from supervision.detection.core import Detections
class BaseAnnotator(ABC):
@abstractmethod
def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
pass
File diff suppressed because it is too large Load Diff
+121
View File
@@ -0,0 +1,121 @@
from enum import Enum
from typing import Optional, Union
import numpy as np
from supervision.detection.core import Detections
from supervision.draw.color import Color, ColorPalette
from supervision.geometry.core import Position
class ColorLookup(Enum):
"""
Enumeration class to define strategies for mapping colors to annotations.
This enum supports three different lookup strategies:
- `INDEX`: Colors are determined by the index of the detection within the scene.
- `CLASS`: Colors are determined by the class label of the detected object.
- `TRACK`: Colors are determined by the tracking identifier of the object.
"""
INDEX = "index"
CLASS = "class"
TRACK = "track"
@classmethod
def list(cls):
return list(map(lambda c: c.value, cls))
def resolve_color_idx(
detections: Detections,
detection_idx: int,
color_lookup: Union[ColorLookup, np.ndarray] = ColorLookup.CLASS,
) -> int:
if detection_idx >= len(detections):
raise ValueError(
f"Detection index {detection_idx}"
f"is out of bounds for detections of length {len(detections)}"
)
if isinstance(color_lookup, np.ndarray):
if len(color_lookup) != len(detections):
raise ValueError(
f"Length of color lookup {len(color_lookup)}"
f"does not match length of detections {len(detections)}"
)
return color_lookup[detection_idx]
elif color_lookup == ColorLookup.INDEX:
return detection_idx
elif color_lookup == ColorLookup.CLASS:
if detections.class_id is None:
raise ValueError(
"Could not resolve color by class because"
"Detections do not have class_id"
)
return detections.class_id[detection_idx]
elif color_lookup == ColorLookup.TRACK:
if detections.tracker_id is None:
raise ValueError(
"Could not resolve color by track because"
"Detections do not have tracker_id"
)
return detections.tracker_id[detection_idx]
def get_color_by_index(color: Union[Color, ColorPalette], idx: int) -> Color:
if isinstance(color, ColorPalette):
return color.by_idx(idx)
return color
def resolve_color(
color: Union[Color, ColorPalette],
detections: Detections,
detection_idx: int,
color_lookup: Union[ColorLookup, np.ndarray] = ColorLookup.CLASS,
) -> Color:
idx = resolve_color_idx(
detections=detections,
detection_idx=detection_idx,
color_lookup=color_lookup,
)
return get_color_by_index(color=color, idx=idx)
class Trace:
def __init__(
self,
max_size: Optional[int] = None,
start_frame_id: int = 0,
anchor: Position = Position.CENTER,
) -> None:
self.current_frame_id = start_frame_id
self.max_size = max_size
self.anchor = anchor
self.frame_id = np.array([], dtype=int)
self.xy = np.empty((0, 2), dtype=np.float32)
self.tracker_id = np.array([], dtype=int)
def put(self, detections: Detections) -> None:
frame_id = np.full(len(detections), self.current_frame_id, dtype=int)
self.frame_id = np.concatenate([self.frame_id, frame_id])
self.xy = np.concatenate(
[self.xy, detections.get_anchor_coordinates(self.anchor)]
)
self.tracker_id = np.concatenate([self.tracker_id, detections.tracker_id])
unique_frame_id = np.unique(self.frame_id)
if 0 < self.max_size < len(unique_frame_id):
max_allowed_frame_id = self.current_frame_id - self.max_size + 1
filtering_mask = self.frame_id >= max_allowed_frame_id
self.frame_id = self.frame_id[filtering_mask]
self.xy = self.xy[filtering_mask]
self.tracker_id = self.tracker_id[filtering_mask]
self.current_frame_id += 1
def get(self, tracker_id: int) -> np.ndarray:
return self.xy[self.tracker_id == tracker_id]
+103
View File
@@ -0,0 +1,103 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Optional, Tuple
import numpy as np
def _validate_class_ids(class_id: Any, n: int) -> None:
"""
Ensure that class_id is a 1d np.ndarray with (n, ) shape.
"""
is_valid = isinstance(class_id, np.ndarray) and class_id.shape == (n,)
if not is_valid:
raise ValueError("class_id must be 1d np.ndarray with (n, ) shape")
def _validate_confidence(confidence: Any, n: int) -> None:
"""
Ensure that confidence is a 1d np.ndarray with (n, ) shape.
"""
if confidence is not None:
is_valid = isinstance(confidence, np.ndarray) and confidence.shape == (n,)
if not is_valid:
raise ValueError("confidence must be 1d np.ndarray with (n, ) shape")
@dataclass
class Classifications:
class_id: np.ndarray
confidence: Optional[np.ndarray] = None
def __post_init__(self) -> None:
"""
Validate the classification inputs.
"""
n = len(self.class_id)
_validate_class_ids(self.class_id, n)
_validate_confidence(self.confidence, n)
@classmethod
def from_ultralytics(cls, ultralytics_results) -> Classifications:
"""
Creates a Classifications instance from a
(https://github.com/ultralytics/ultralytics) inference result.
Args:
ultralytics_results (ultralytics.engine.results.Results):
The output Results instance from ultralytics model
Returns:
Classifications: A new Classifications object.
Example:
```python
>>> import cv2
>>> from ultralytics import YOLO
>>> import supervision as sv
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> model = YOLO('yolov8n-cls.pt')
>>> model = YOLO('yolov8s-cls.pt')
>>> result = model(image)[0]
>>> classifications = sv.Classifications.from_ultralytics(result)
```
"""
confidence = ultralytics_results.probs.data.cpu().numpy()
return cls(class_id=np.arange(confidence.shape[0]), confidence=confidence)
def get_top_k(self, k: int) -> Tuple[np.ndarray, np.ndarray]:
"""
Retrieve the top k class IDs and confidences,
ordered in descending order by confidence.
Args:
k (int): The number of top class IDs and confidences to retrieve.
Returns:
Tuple[np.ndarray, np.ndarray]: A tuple containing
the top k class IDs and confidences.
Example:
```python
>>> import supervision as sv
>>> classifications = sv.Classifications(...)
>>> classifications.get_top_k(1)
(array([1]), array([0.9]))
```
"""
if self.confidence is None:
raise ValueError("top_k could not be calculated, confidence is None")
order = np.argsort(self.confidence)[::-1]
top_k_order = order[:k]
top_k_class_id = self.class_id[top_k_order]
top_k_confidence = self.confidence[top_k_order]
return top_k_class_id, top_k_confidence
View File
+678
View File
@@ -0,0 +1,678 @@
from __future__ import annotations
import os
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Iterator, List, Optional, Tuple
import cv2
import numpy as np
from supervision.classification.core import Classifications
from supervision.dataset.formats.coco import (
load_coco_annotations,
save_coco_annotations,
)
from supervision.dataset.formats.pascal_voc import (
detections_to_pascal_voc,
load_pascal_voc_annotations,
)
from supervision.dataset.formats.yolo import (
load_yolo_annotations,
save_data_yaml,
save_yolo_annotations,
)
from supervision.dataset.utils import (
build_class_index_mapping,
map_detections_class_id,
merge_class_lists,
save_dataset_images,
train_test_split,
)
from supervision.detection.core import Detections
@dataclass
class BaseDataset(ABC):
@abstractmethod
def __len__(self) -> int:
pass
@abstractmethod
def split(
self, split_ratio=0.8, random_state=None, shuffle: bool = True
) -> Tuple[BaseDataset, BaseDataset]:
pass
@dataclass
class DetectionDataset(BaseDataset):
"""
Dataclass containing information about object detection dataset.
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
image name to annotations.
"""
classes: List[str]
images: Dict[str, np.ndarray]
annotations: Dict[str, Detections]
def __len__(self) -> int:
"""
Return the number of images in the dataset.
Returns:
int: The number of images.
"""
return len(self.images)
def __iter__(self) -> Iterator[Tuple[str, np.ndarray, Detections]]:
"""
Iterate over the images and annotations in the dataset.
Yields:
Iterator[Tuple[str, np.ndarray, Detections]]:
An iterator that yields tuples containing the image name,
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)
def __eq__(self, other):
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]:
return False
return True
def split(
self, split_ratio=0.8, random_state=None, shuffle: bool = True
) -> Tuple[DetectionDataset, DetectionDataset]:
"""
Splits the dataset into two parts (training and testing)
using the provided split_ratio.
Args:
split_ratio (float, optional): The ratio of the training
set to the entire dataset.
random_state (int, optional): The seed for the random number generator.
This is used for reproducibility.
shuffle (bool, optional): Whether to shuffle the data before splitting.
Returns:
Tuple[DetectionDataset, DetectionDataset]: A tuple containing
the training and testing datasets.
Example:
```python
>>> import supervision as sv
>>> ds = sv.DetectionDataset(...)
>>> train_ds, test_ds = ds.split(split_ratio=0.7,
... random_state=42, shuffle=True)
>>> len(train_ds), len(test_ds)
(700, 300)
```
"""
image_names = list(self.images.keys())
train_names, test_names = train_test_split(
data=image_names,
train_ratio=split_ratio,
random_state=random_state,
shuffle=shuffle,
)
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},
)
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},
)
return train_dataset, test_dataset
def as_pascal_voc(
self,
images_directory_path: Optional[str] = None,
annotations_directory_path: Optional[str] = None,
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.0,
) -> None:
"""
Exports the dataset to PASCAL VOC format. This method saves the images
and their corresponding annotations in PASCAL VOC format.
Args:
images_directory_path (Optional[str]): The path to the directory
where the images should be saved.
If not provided, images will not be saved.
annotations_directory_path (Optional[str]): The path to
the directory where the annotations in PASCAL VOC format should be
saved. If not provided, annotations will not be saved.
min_image_area_percentage (float): The minimum percentage of
detection area relative to
the image area for a detection to be included.
Argument is used only for segmentation datasets.
max_image_area_percentage (float): The maximum percentage
of detection area relative to
the image area for a detection to be included.
Argument is used only for segmentation datasets.
approximation_percentage (float): The percentage of
polygon points to be removed from the input polygon,
in the range [0, 1). Argument is used only for segmentation datasets.
"""
if images_directory_path:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
)
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:
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,
classes=self.classes,
filename=image_name,
image_shape=image.shape,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)
with open(annotations_path, "w") as f:
f.write(pascal_voc_xml)
@classmethod
def from_pascal_voc(
cls,
images_directory_path: str,
annotations_directory_path: str,
force_masks: bool = False,
) -> DetectionDataset:
"""
Creates a Dataset instance from PASCAL VOC formatted data.
Args:
images_directory_path (str): Path to the directory containing the images.
annotations_directory_path (str): Path to the directory
containing the PASCAL VOC XML annotations.
force_masks (bool, optional): If True, forces masks to
be loaded for all annotations, regardless of whether they are present.
Returns:
DetectionDataset: A DetectionDataset instance containing
the loaded images and annotations.
Example:
```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("voc")
>>> ds = sv.DetectionDataset.from_pascal_voc(
... images_directory_path=f"{dataset.location}/train/images",
... annotations_directory_path=f"{dataset.location}/train/labels"
... )
>>> ds.classes
['dog', 'person']
```
"""
classes, images, 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)
@classmethod
def from_yolo(
cls,
images_directory_path: str,
annotations_directory_path: str,
data_yaml_path: str,
force_masks: bool = False,
) -> DetectionDataset:
"""
Creates a Dataset instance from YOLO formatted data.
Args:
images_directory_path (str): The path to the
directory containing the images.
annotations_directory_path (str): The path to the directory
containing the YOLO annotation files.
data_yaml_path (str): The path to the data
YAML file containing class information.
force_masks (bool, optional): If True, forces
masks to be loaded for all annotations,
regardless of whether they are present.
Returns:
DetectionDataset: A DetectionDataset instance
containing the loaded images and annotations.
Example:
```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("yolov5")
>>> ds = sv.DetectionDataset.from_yolo(
... images_directory_path=f"{dataset.location}/train/images",
... annotations_directory_path=f"{dataset.location}/train/labels",
... data_yaml_path=f"{dataset.location}/data.yaml"
... )
>>> ds.classes
['dog', 'person']
```
"""
classes, images, 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,
)
return DetectionDataset(classes=classes, images=images, annotations=annotations)
def as_yolo(
self,
images_directory_path: Optional[str] = None,
annotations_directory_path: Optional[str] = None,
data_yaml_path: Optional[str] = None,
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.0,
) -> None:
"""
Exports the dataset to YOLO format. This method saves the
images and their corresponding annotations in YOLO format.
Args:
images_directory_path (Optional[str]): The path to the
directory where the images should be saved.
If not provided, images will not be saved.
annotations_directory_path (Optional[str]): The path to the
directory where the annotations in
YOLO format should be saved. If not provided,
annotations will not be saved.
data_yaml_path (Optional[str]): The path where the data.yaml
file should be saved.
If not provided, the file will not be saved.
min_image_area_percentage (float): The minimum percentage of
detection area relative to
the image area for a detection to be included.
Argument is used only for segmentation datasets.
max_image_area_percentage (float): The maximum percentage
of detection area relative to
the image area for a detection to be included.
Argument is used only for segmentation datasets.
approximation_percentage (float): The percentage of polygon points to
be removed from the input polygon, in the range [0, 1).
This is useful for simplifying the annotations.
Argument is used only for segmentation datasets.
"""
if images_directory_path is not None:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
)
if annotations_directory_path is not None:
save_yolo_annotations(
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,
)
if data_yaml_path is not None:
save_data_yaml(data_yaml_path=data_yaml_path, classes=self.classes)
@classmethod
def from_coco(
cls,
images_directory_path: str,
annotations_path: str,
force_masks: bool = False,
) -> DetectionDataset:
"""
Creates a Dataset instance from COCO formatted data.
Args:
images_directory_path (str): The path to the
directory containing the images.
annotations_path (str): The path to the json annotation files.
force_masks (bool, optional): If True,
forces masks to be loaded for all annotations,
regardless of whether they are present.
Returns:
DetectionDataset: A DetectionDataset instance containing
the loaded images and annotations.
Example:
```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 = sv.DetectionDataset.from_coco(
... images_directory_path=f"{dataset.location}/train",
... annotations_path=f"{dataset.location}/train/_annotations.coco.json",
... )
>>> ds.classes
['dog', 'person']
```
"""
classes, images, annotations = load_coco_annotations(
images_directory_path=images_directory_path,
annotations_path=annotations_path,
force_masks=force_masks,
)
return DetectionDataset(classes=classes, images=images, annotations=annotations)
def as_coco(
self,
images_directory_path: Optional[str] = None,
annotations_path: Optional[str] = None,
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.0,
) -> None:
"""
Exports the dataset to COCO format. This method saves the
images and their corresponding annotations in COCO format.
Args:
images_directory_path (Optional[str]): The path to the directory
where the images should be saved.
If not provided, images will not be saved.
annotations_path (Optional[str]): The path to COCO annotation file.
min_image_area_percentage (float): The minimum percentage of
detection area relative to
the image area for a detection to be included.
Argument is used only for segmentation datasets.
max_image_area_percentage (float): The maximum percentage of
detection area relative to
the image area for a detection to be included.
Argument is used only for segmentation datasets.
approximation_percentage (float): The percentage of polygon points
to be removed from the input polygon,
in the range [0, 1). This is useful for simplifying the annotations.
Argument is used only for segmentation datasets.
"""
if images_directory_path is not None:
save_dataset_images(
images_directory_path=images_directory_path, images=self.images
)
if annotations_path is not None:
save_coco_annotations(
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.
Example:
```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.
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
image name to annotations.
"""
classes: List[str]
images: Dict[str, np.ndarray]
annotations: Dict[str, Classifications]
def __len__(self) -> int:
return len(self.images)
def split(
self, split_ratio=0.8, random_state=None, shuffle: bool = True
) -> Tuple[ClassificationDataset, ClassificationDataset]:
"""
Splits the dataset into two parts (training and testing)
using the provided split_ratio.
Args:
split_ratio (float, optional): The ratio of the training
set to the entire dataset.
random_state (int, optional): The seed for the
random number generator. This is used for reproducibility.
shuffle (bool, optional): Whether to shuffle the data before splitting.
Returns:
Tuple[ClassificationDataset, ClassificationDataset]: A tuple containing
the training and testing datasets.
Example:
```python
>>> import supervision as sv
>>> cd = sv.ClassificationDataset(...)
>>> train_cd,test_cd = cd.split(split_ratio=0.7,
... random_state=42,shuffle=True)
>>> len(train_cd), len(test_cd)
(700, 300)
```
"""
image_names = list(self.images.keys())
train_names, test_names = train_test_split(
data=image_names,
train_ratio=split_ratio,
random_state=random_state,
shuffle=shuffle,
)
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},
)
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},
)
return train_dataset, test_dataset
def as_folder_structure(self, root_directory_path: str) -> None:
"""
Saves the dataset as a multi-class folder structure.
Args:
root_directory_path (str): The path to the directory
where the dataset will be saved.
"""
os.makedirs(root_directory_path, exist_ok=True)
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
class_id = (
classification.class_id[0]
if classification.confidence is None
else classification.get_top_k(1)[0][0]
)
class_name = self.classes[class_id]
image_path = os.path.join(root_directory_path, class_name, image_name)
cv2.imwrite(image_path, image)
@classmethod
def from_folder_structure(cls, root_directory_path: str) -> ClassificationDataset:
"""
Load data from a multiclass folder structure into a ClassificationDataset.
Args:
root_directory_path (str): The path to the dataset directory.
Returns:
ClassificationDataset: The dataset.
Example:
```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("folder")
>>> cd = sv.ClassificationDataset.from_folder_structure(
... root_directory_path=f"{dataset.location}/train"
... )
```
"""
classes = os.listdir(root_directory_path)
classes = sorted(set(classes))
images = {}
annotations = {}
for class_name in classes:
class_id = classes.index(class_name)
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)
annotations[image_path] = Classifications(
class_id=np.array([class_id]),
)
return cls(
classes=classes,
images=images,
annotations=annotations,
)
+237
View File
@@ -0,0 +1,237 @@
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Tuple
import cv2
import numpy as np
from supervision.dataset.utils import (
approximate_mask_with_polygons,
map_detections_class_id,
)
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask
from supervision.utils.file import read_json_file, save_json_file
def coco_categories_to_classes(coco_categories: List[dict]) -> List[str]:
return [
category["name"]
for category in sorted(coco_categories, key=lambda category: category["id"])
]
def build_coco_class_index_mapping(
coco_categories: List[dict], target_classes: List[str]
) -> Dict[int, int]:
source_class_to_index = {
category["name"]: category["id"] for category in coco_categories
}
return {
source_class_to_index[target_class_name]: target_class_index
for target_class_index, target_class_name in enumerate(target_classes)
}
def classes_to_coco_categories(classes: List[str]) -> List[dict]:
return [
{
"id": class_id,
"name": class_name,
"supercategory": "common-objects",
}
for class_id, class_name in enumerate(classes)
]
def group_coco_annotations_by_image_id(
coco_annotations: List[dict],
) -> Dict[int, List[dict]]:
annotations = {}
for annotation in coco_annotations:
image_id = annotation["image_id"]
if image_id not in annotations:
annotations[image_id] = []
annotations[image_id].append(annotation)
return annotations
def _polygons_to_masks(
polygons: List[np.ndarray], resolution_wh: Tuple[int, int]
) -> np.ndarray:
return np.array(
[
polygon_to_mask(polygon=polygon, resolution_wh=resolution_wh)
for polygon in polygons
],
dtype=bool,
)
def coco_annotations_to_detections(
image_annotations: List[dict], resolution_wh: Tuple[int, int], with_masks: bool
) -> Detections:
if not image_annotations:
return Detections.empty()
class_ids = [
image_annotation["category_id"] for image_annotation in image_annotations
]
xyxy = [image_annotation["bbox"] for image_annotation in image_annotations]
xyxy = np.asarray(xyxy)
xyxy[:, 2:4] += xyxy[:, 0:2]
if with_masks:
polygons = [
np.reshape(
np.asarray(image_annotation["segmentation"], dtype=np.int32), (-1, 2)
)
for image_annotation in image_annotations
]
mask = _polygons_to_masks(polygons=polygons, resolution_wh=resolution_wh)
return Detections(
class_id=np.asarray(class_ids, dtype=int), xyxy=xyxy, mask=mask
)
return Detections(xyxy=xyxy, class_id=np.asarray(class_ids, dtype=int))
def detections_to_coco_annotations(
detections: Detections,
image_id: int,
annotation_id: int,
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> Tuple[List[Dict], int]:
coco_annotations = []
for xyxy, mask, _, class_id, _ in detections:
box_width, box_height = xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]
polygon = []
if mask is not None:
polygon = list(
approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)[0].flatten()
)
coco_annotation = {
"id": annotation_id,
"image_id": image_id,
"category_id": int(class_id),
"bbox": [xyxy[0], xyxy[1], box_width, box_height],
"area": box_width * box_height,
"segmentation": [polygon] if polygon else [],
"iscrowd": 0,
}
coco_annotations.append(coco_annotation)
annotation_id += 1
return coco_annotations, annotation_id
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]]:
coco_data = read_json_file(file_path=annotations_path)
classes = coco_categories_to_classes(coco_categories=coco_data["categories"])
class_index_mapping = build_coco_class_index_mapping(
coco_categories=coco_data["categories"], target_classes=classes
)
coco_images = coco_data["images"]
coco_annotations_groups = group_coco_annotations_by_image_id(
coco_annotations=coco_data["annotations"]
)
images = {}
annotations = {}
for coco_image in coco_images:
image_name, image_width, image_height = (
coco_image["file_name"],
coco_image["width"],
coco_image["height"],
)
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),
with_masks=force_masks,
)
annotation = map_detections_class_id(
source_to_target_mapping=class_index_mapping,
detections=annotation,
)
images[image_path] = image
annotations[image_path] = annotation
return classes, images, annotations
def save_coco_annotations(
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,
"url": "https://creativecommons.org/licenses/by/4.0/",
"name": "CC BY 4.0",
}
]
coco_annotations = []
coco_images = []
coco_categories = classes_to_coco_categories(classes=classes)
image_id, annotation_id = 1, 1
for image_path, image in images.items():
image_height, image_width, _ = image.shape
image_name = f"{Path(image_path).stem}{Path(image_path).suffix}"
coco_image = {
"id": image_id,
"license": 1,
"file_name": image_name,
"height": image_height,
"width": image_width,
"date_captured": datetime.now().strftime("%m/%d/%Y,%H:%M:%S"),
}
coco_images.append(coco_image)
detections = annotations[image_path]
coco_annotation, annotation_id = detections_to_coco_annotations(
detections=detections,
image_id=image_id,
annotation_id=annotation_id,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)
coco_annotations.extend(coco_annotation)
image_id += 1
annotation_dict = {
"info": info,
"licenses": licenses,
"categories": coco_categories,
"images": coco_images,
"annotations": coco_annotations,
}
save_json_file(annotation_dict, file_path=annotation_path)
+284
View File
@@ -0,0 +1,284 @@
import os
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from xml.dom.minidom import parseString
from xml.etree.ElementTree import Element, SubElement, parse, tostring
import cv2
import numpy as np
from supervision.dataset.utils import approximate_mask_with_polygons
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy
from supervision.utils.file import list_files_with_extensions
def object_to_pascal_voc(
xyxy: np.ndarray, name: str, polygon: Optional[np.ndarray] = None
) -> Element:
root = Element("object")
object_name = SubElement(root, "name")
object_name.text = name
# https://github.com/roboflow/supervision/issues/144
xyxy += 1
bndbox = SubElement(root, "bndbox")
xmin = SubElement(bndbox, "xmin")
xmin.text = str(int(xyxy[0]))
ymin = SubElement(bndbox, "ymin")
ymin.text = str(int(xyxy[1]))
xmax = SubElement(bndbox, "xmax")
xmax.text = str(int(xyxy[2]))
ymax = SubElement(bndbox, "ymax")
ymax.text = str(int(xyxy[3]))
if polygon is not None:
# https://github.com/roboflow/supervision/issues/144
polygon += 1
object_polygon = SubElement(root, "polygon")
for index, point in enumerate(polygon, start=1):
x_coordinate, y_coordinate = point
x = SubElement(object_polygon, f"x{index}")
x.text = str(x_coordinate)
y = SubElement(object_polygon, f"y{index}")
y.text = str(y_coordinate)
return root
def detections_to_pascal_voc(
detections: Detections,
classes: List[str],
filename: str,
image_shape: Tuple[int, int, int],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> str:
"""
Converts Detections object to Pascal VOC XML format.
Args:
detections (Detections): A Detections object containing bounding boxes,
class ids, and other relevant information.
classes (List[str]): A list of class names corresponding to the
class ids in the Detections object.
filename (str): The name of the image file associated with the detections.
image_shape (Tuple[int, int, int]): The shape of the image
file associated with the detections.
min_image_area_percentage (float): Minimum detection area
relative to area of image associated with it.
max_image_area_percentage (float): Maximum detection area
relative to area of image associated with it.
approximation_percentage (float): The percentage of
polygon points to be removed from the input polygon, in the range [0, 1).
Returns:
str: An XML string in Pascal VOC format representing the detections.
"""
height, width, depth = image_shape
# Create root element
annotation = Element("annotation")
# Add folder element
folder = SubElement(annotation, "folder")
folder.text = "VOC"
# Add filename element
file_name = SubElement(annotation, "filename")
file_name.text = filename
# Add source element
source = SubElement(annotation, "source")
database = SubElement(source, "database")
database.text = "roboflow.ai"
# Add size element
size = SubElement(annotation, "size")
w = SubElement(size, "width")
w.text = str(width)
h = SubElement(size, "height")
h.text = str(height)
d = SubElement(size, "depth")
d.text = str(depth)
# Add segmented element
segmented = SubElement(annotation, "segmented")
segmented.text = "0"
# Add object elements
for xyxy, mask, _, class_id, _ in detections:
name = classes[class_id]
if mask is not None:
polygons = approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)
for polygon in polygons:
xyxy = polygon_to_xyxy(polygon=polygon)
next_object = object_to_pascal_voc(
xyxy=xyxy, name=name, polygon=polygon
)
annotation.append(next_object)
else:
next_object = object_to_pascal_voc(xyxy=xyxy, name=name)
annotation.append(next_object)
# Generate XML string
xml_string = parseString(tostring(annotation)).toprettyxml(indent=" ")
return xml_string
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]]:
"""
Loads PASCAL VOC XML annotations and returns the image name,
a Detections instance, and a list of class names.
Args:
images_directory_path (str): The path to the directory containing the images.
annotations_directory_path (str): The path to the directory containing the
PASCAL VOC annotation files.
force_masks (bool, optional): If True, forces masks to be loaded for all
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.
"""
image_paths = list_files_with_extensions(
directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
)
classes = []
images = {}
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")
if not os.path.exists(annotation_path):
images[image_path] = image
annotations[image_path] = Detections.empty()
continue
tree = parse(annotation_path)
root = tree.getroot()
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
def detections_from_xml_obj(
root: Element, classes: List[str], resolution_wh, force_masks: bool = False
) -> Tuple[Detections, List[str]]:
"""
Converts an XML object in Pascal VOC format to a Detections object.
Expected XML format:
<annotation>
...
<object>
<name>dog</name>
<bndbox>
<xmin>48</xmin>
<ymin>240</ymin>
<xmax>195</xmax>
<ymax>371</ymax>
</bndbox>
<polygon>
<x1>48</x1>
<y1>240</y1>
<x2>195</x2>
<y2>240</y2>
<x3>195</x3>
<y3>371</y3>
<x4>48</x4>
<y4>371</y4>
</polygon>
</object>
</annotation>
Returns:
Tuple[Detections, List[str]]: A tuple containing a Detections object and an
updated list of class names, extended with the class names
from the XML object.
"""
xyxy = []
class_names = []
masks = []
with_masks = False
extended_classes = classes[:]
for obj in root.findall("object"):
class_name = obj.find("name").text
class_names.append(class_name)
bbox = obj.find("bndbox")
x1 = int(bbox.find("xmin").text)
y1 = int(bbox.find("ymin").text)
x2 = int(bbox.find("xmax").text)
y2 = int(bbox.find("ymax").text)
xyxy.append([x1, y1, x2, y2])
with_masks = obj.find("polygon") is not None
with_masks = force_masks if force_masks else with_masks
for polygon in obj.findall("polygon"):
polygon = parse_polygon_points(polygon)
# https://github.com/roboflow/supervision/issues/144
polygon -= 1
mask_from_polygon = polygon_to_mask(
polygon=polygon,
resolution_wh=resolution_wh,
)
masks.append(mask_from_polygon)
xyxy = np.array(xyxy) if len(xyxy) > 0 else np.empty((0, 4))
# https://github.com/roboflow/supervision/issues/144
xyxy -= 1
for k in set(class_names):
if k not in extended_classes:
extended_classes.append(k)
class_id = np.array(
[extended_classes.index(class_name) for class_name in class_names]
)
if with_masks:
annotation = Detections(
xyxy=xyxy, mask=np.array(masks).astype(bool), class_id=class_id
)
else:
annotation = Detections(xyxy=xyxy, class_id=class_id)
return annotation, extended_classes
def parse_polygon_points(polygon: Element) -> np.ndarray:
coordinates = [int(coord.text) for coord in polygon.findall(".//*")]
return np.array(
[(coordinates[i], coordinates[i + 1]) for i in range(0, len(coordinates), 2)]
)
+251
View File
@@ -0,0 +1,251 @@
import os
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import cv2
import numpy as np
from supervision.dataset.utils import approximate_mask_with_polygons
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy
from supervision.utils.file import (
list_files_with_extensions,
read_txt_file,
read_yaml_file,
save_text_file,
save_yaml_file,
)
def _parse_box(values: List[str]) -> np.ndarray:
x_center, y_center, width, height = values
return np.array(
[
float(x_center) - float(width) / 2,
float(y_center) - float(height) / 2,
float(x_center) + float(width) / 2,
float(y_center) + float(height) / 2,
],
dtype=np.float32,
)
def _box_to_polygon(box: np.ndarray) -> np.ndarray:
return np.array(
[[box[0], box[1]], [box[2], box[1]], [box[2], box[3]], [box[0], box[3]]]
)
def _parse_polygon(values: List[str]) -> np.ndarray:
return np.array(values, dtype=np.float32).reshape(-1, 2)
def _polygons_to_masks(
polygons: List[np.ndarray], resolution_wh: Tuple[int, int]
) -> np.ndarray:
return np.array(
[
polygon_to_mask(polygon=polygon, resolution_wh=resolution_wh)
for polygon in polygons
],
dtype=bool,
)
def _with_mask(lines: List[str]) -> bool:
return any([len(line.split()) > 5 for line in lines])
def _extract_class_names(file_path: str) -> List[str]:
data = read_yaml_file(file_path=file_path)
names = data["names"]
if isinstance(names, dict):
names = [names[key] for key in sorted(names.keys())]
return names
def _image_name_to_annotation_name(image_name: str) -> str:
base_name, _ = os.path.splitext(image_name)
return base_name + ".txt"
def yolo_annotations_to_detections(
lines: List[str], resolution_wh: Tuple[int, int], with_masks: bool
) -> Detections:
if len(lines) == 0:
return Detections.empty()
class_id, relative_xyxy, relative_polygon = [], [], []
w, h = resolution_wh
for line in lines:
values = line.split()
class_id.append(int(values[0]))
if len(values) == 5:
box = _parse_box(values=values[1:])
relative_xyxy.append(box)
if with_masks:
relative_polygon.append(_box_to_polygon(box=box))
elif len(values) > 5:
polygon = _parse_polygon(values=values[1:])
relative_xyxy.append(polygon_to_xyxy(polygon=polygon))
if with_masks:
relative_polygon.append(polygon)
class_id = np.array(class_id, dtype=int)
relative_xyxy = np.array(relative_xyxy, dtype=np.float32)
xyxy = relative_xyxy * np.array([w, h, w, h], dtype=np.float32)
if not with_masks:
return Detections(class_id=class_id, xyxy=xyxy)
polygons = [
(polygon * np.array(resolution_wh)).astype(int) for polygon in relative_polygon
]
mask = _polygons_to_masks(polygons=polygons, resolution_wh=resolution_wh)
return Detections(class_id=class_id, xyxy=xyxy, mask=mask)
def load_yolo_annotations(
images_directory_path: str,
annotations_directory_path: str,
data_yaml_path: str,
force_masks: bool = False,
) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]:
"""
Loads YOLO annotations and returns class names, images,
and their corresponding detections.
Args:
images_directory_path (str): The path to the directory containing the images.
annotations_directory_path (str): The path to the directory
containing the YOLO annotation files.
data_yaml_path (str): The path to the data
YAML file containing class information.
force_masks (bool, optional): If True, forces masks to be loaded
for all 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.
"""
image_paths = 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
lines = read_txt_file(str(annotation_path))
h, w, _ = image.shape
resolution_wh = (w, h)
with_masks = _with_mask(lines=lines)
with_masks = force_masks if force_masks else with_masks
annotation = yolo_annotations_to_detections(
lines=lines, resolution_wh=resolution_wh, with_masks=with_masks
)
images[image_path] = image
annotations[image_path] = annotation
return classes, images, annotations
def object_to_yolo(
xyxy: np.ndarray,
class_id: int,
image_shape: Tuple[int, int, int],
polygon: Optional[np.ndarray] = None,
) -> str:
h, w, _ = image_shape
if polygon is None:
xyxy_relative = xyxy / np.array([w, h, w, h], dtype=np.float32)
x_min, y_min, x_max, y_max = xyxy_relative
x_center = (x_min + x_max) / 2
y_center = (y_min + y_max) / 2
width = x_max - x_min
height = y_max - y_min
return f"{int(class_id)} {x_center:.5f} {y_center:.5f} {width:.5f} {height:.5f}"
else:
polygon_relative = polygon / np.array([w, h], dtype=np.float32)
polygon_relative = polygon_relative.reshape(-1)
polygon_parsed = " ".join([f"{value:.5f}" for value in polygon_relative])
return f"{int(class_id)} {polygon_parsed}"
def detections_to_yolo_annotations(
detections: Detections,
image_shape: Tuple[int, int, int],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> List[str]:
annotation = []
for xyxy, mask, _, class_id, _ in detections:
if mask is not None:
polygons = approximate_mask_with_polygons(
mask=mask,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)
for polygon in polygons:
xyxy = polygon_to_xyxy(polygon=polygon)
next_object = object_to_yolo(
xyxy=xyxy,
class_id=class_id,
image_shape=image_shape,
polygon=polygon,
)
annotation.append(next_object)
else:
next_object = object_to_yolo(
xyxy=xyxy, class_id=class_id, image_shape=image_shape
)
annotation.append(next_object)
return annotation
def save_yolo_annotations(
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]
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,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
)
save_text_file(lines=lines, file_path=yolo_annotations_path)
def save_data_yaml(data_yaml_path: str, classes: List[str]) -> None:
data = {"nc": len(classes), "names": classes}
Path(data_yaml_path).parent.mkdir(parents=True, exist_ok=True)
save_yaml_file(data=data, file_path=data_yaml_path)
+131
View File
@@ -0,0 +1,131 @@
import copy
import os
import random
from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypeVar
import cv2
import numpy as np
from supervision.detection.core import Detections
from supervision.detection.utils import (
approximate_polygon,
filter_polygons_by_area,
mask_to_polygons,
)
T = TypeVar("T")
def approximate_mask_with_polygons(
mask: np.ndarray,
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
) -> List[np.ndarray]:
height, width = mask.shape
image_area = height * width
minimum_detection_area = min_image_area_percentage * image_area
maximum_detection_area = max_image_area_percentage * image_area
polygons = mask_to_polygons(mask=mask)
if len(polygons) == 1:
polygons = filter_polygons_by_area(
polygons=polygons, min_area=None, max_area=maximum_detection_area
)
else:
polygons = filter_polygons_by_area(
polygons=polygons,
min_area=minimum_detection_area,
max_area=maximum_detection_area,
)
return [
approximate_polygon(polygon=polygon, percentage=approximation_percentage)
for polygon in polygons
]
def merge_class_lists(class_lists: List[List[str]]) -> List[str]:
unique_classes = set()
for class_list in class_lists:
for class_name in class_list:
unique_classes.add(class_name.lower())
return sorted(list(unique_classes))
def build_class_index_mapping(
source_classes: List[str], target_classes: List[str]
) -> Dict[int, int]:
index_mapping = {}
for i, class_name in enumerate(source_classes):
if class_name not in target_classes:
raise ValueError(
f"Class {class_name} not found in target classes. "
"source_classes must be a subset of target_classes."
)
corresponding_index = target_classes.index(class_name)
index_mapping[i] = corresponding_index
return index_mapping
def map_detections_class_id(
source_to_target_mapping: Dict[int, int], detections: Detections
) -> Detections:
if detections.class_id is None:
raise ValueError("Detections must have class_id attribute.")
if set(np.unique(detections.class_id)) - set(source_to_target_mapping.keys()):
raise ValueError(
"Detections class_id must be a subset of source_to_target_mapping keys."
)
detections_copy = copy.deepcopy(detections)
if len(detections) > 0:
detections_copy.class_id = np.vectorize(source_to_target_mapping.get)(
detections_copy.class_id
)
return detections_copy
def save_dataset_images(
images_directory_path: str, images: Dict[str, np.ndarray]
) -> 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)
def train_test_split(
data: List[T],
train_ratio: float = 0.8,
random_state: Optional[int] = None,
shuffle: bool = True,
) -> Tuple[List[T], List[T]]:
"""
Splits the data into two parts using the provided train_ratio.
Args:
data (List[T]): The data to split.
train_ratio (float): The ratio of the training set to the entire dataset.
random_state (Optional[int]): The seed for the random number generator.
shuffle (bool): Whether to shuffle the data before splitting.
Returns:
Tuple[List[T], List[T]]: The split data.
"""
if random_state is not None:
random.seed(random_state)
if shuffle:
random.shuffle(data)
split_index = int(len(data) * train_ratio)
return data[:split_index], data[split_index:]
+34 -62
View File
@@ -12,12 +12,15 @@ 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
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
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
"""
@@ -47,13 +50,37 @@ class BoxAnnotator:
"""
Draws bounding boxes on the frame using the detections provided.
Parameters:
Args:
scene (np.ndarray): The image on which the bounding boxes will be drawn
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 is provided, the confidence score of the detection will be replaced with the label.
skip_label (bool): Is set to True, skips bounding box label annotation.
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:
np.ndarray: The image with the bounding boxes drawn on it
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)):
@@ -117,58 +144,3 @@ class BoxAnnotator:
lineType=cv2.LINE_AA,
)
return scene
class MaskAnnotator:
"""
A class for overlaying masks on an image using detections provided.
Attributes:
color (Union[Color, ColorPalette]): The color to fill the mask, can be a single color or a color palette
"""
def __init__(
self,
color: Union[Color, ColorPalette] = ColorPalette.default(),
):
self.color: Union[Color, ColorPalette] = color
def annotate(
self, scene: np.ndarray, detections: Detections, opacity: float = 0.5
) -> np.ndarray:
"""
Overlays the masks on the given image based on the provided detections, with a specified opacity.
Parameters:
scene (np.ndarray): The image on which the masks will be overlaid
detections (Detections): The detections for which the masks will be overlaid
opacity (float): The opacity of the masks, between 0 and 1, default is 0.5
Returns:
np.ndarray: The image with the masks overlaid
"""
for i in range(len(detections.xyxy)):
if detections.mask is None:
continue
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
)
mask = detections.mask[i]
colored_mask = np.zeros_like(scene, dtype=np.uint8)
colored_mask[:] = color.as_bgr()
scene = np.where(
np.expand_dims(mask, axis=-1),
np.uint8(opacity * colored_mask + (1 - opacity) * scene),
scene,
)
return scene
+469 -91
View File
@@ -1,11 +1,16 @@
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import astuple, dataclass
from typing import Any, Iterator, List, Optional, Tuple, Union
import numpy as np
from supervision.detection.utils import non_max_suppression, xywh_to_xyxy
from supervision.detection.utils import (
extract_ultralytics_masks,
non_max_suppression,
process_roboflow_result,
xywh_to_xyxy,
)
from supervision.geometry.core import Position
@@ -20,7 +25,14 @@ def _validate_mask(mask: Any, n: int) -> None:
isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n
)
if not is_valid:
raise ValueError("mask must be 3d np.ndarray with (n, W, H) shape")
raise ValueError("mask must be 3d np.ndarray with (n, H, W) shape")
def validate_inference_callback(callback) -> None:
tmp_img = np.zeros((256, 256, 3), dtype=np.uint8)
res = callback(tmp_img)
if not isinstance(res, Detections):
raise ValueError("Callback function must return sv.Detection type")
def _validate_class_id(class_id: Any, n: int) -> None:
@@ -51,19 +63,23 @@ def _validate_tracker_id(tracker_id: Any, n: int) -> None:
class Detections:
"""
Data class containing information about the detections in a video frame.
Attributes:
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes coordinates in format `[x1, y1, x2, y2]`
mask: (Optional[np.ndarray]): An array of shape `(n, W, H)` containing the segmentation masks.
class_id (Optional[np.ndarray]): An array of shape `(n,)` containing the class ids of the detections.
confidence (Optional[np.ndarray]): An array of shape `(n,)` containing the confidence scores of the detections.
tracker_id (Optional[np.ndarray]): An array of shape `(n,)` containing the tracker ids of the detections.
xyxy (np.ndarray): An array of shape `(n, 4)` containing
the bounding boxes coordinates in format `[x1, y1, x2, y2]`
mask: (Optional[np.ndarray]): An array of shape
`(n, H, W)` containing the segmentation masks.
confidence (Optional[np.ndarray]): An array of shape
`(n,)` containing the confidence scores of the detections.
class_id (Optional[np.ndarray]): An array of shape
`(n,)` containing the class ids of the detections.
tracker_id (Optional[np.ndarray]): An array of shape
`(n,)` containing the tracker ids of the detections.
"""
xyxy: np.ndarray
mask: np.Optional[np.ndarray] = None
class_id: Optional[np.ndarray] = None
mask: Optional[np.ndarray] = None
confidence: Optional[np.ndarray] = None
class_id: Optional[np.ndarray] = None
tracker_id: Optional[np.ndarray] = None
def __post_init__(self):
@@ -92,7 +108,8 @@ class Detections:
]
]:
"""
Iterates over the Detections object and yield a tuple of `(xyxy, mask, confidence, class_id, tracker_id)` for each detection.
Iterates over the Detections object and yield a tuple of
`(xyxy, mask, confidence, class_id, tracker_id)` for each detection.
"""
for i in range(len(self.xyxy)):
yield (
@@ -137,25 +154,30 @@ class Detections:
@classmethod
def from_yolov5(cls, yolov5_results) -> Detections:
"""
Creates a Detections instance from a YOLOv5 output Detections
Creates a Detections instance from a
[YOLOv5](https://github.com/ultralytics/yolov5) inference result.
Args:
yolov5_results (yolov5.models.common.Detections): The output Detections instance from YOLOv5
yolov5_results (yolov5.models.common.Detections):
The output Detections instance from YOLOv5
Returns:
Detections: A new Detections object.
Example:
```python
>>> import cv2
>>> import torch
>>> from supervision import Detections
>>> import supervision as sv
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> model = torch.hub.load('ultralytics/yolov5', 'yolov5s')
>>> results = model(IMAGE)
>>> detections = Detections.from_yolov5(results)
>>> result = model(image)
>>> detections = sv.Detections.from_yolov5(result)
```
"""
yolov5_detections_predictions = yolov5_results.pred[0].cpu().cpu().numpy()
return cls(
xyxy=yolov5_detections_predictions[:, :4],
confidence=yolov5_detections_predictions[:, 4],
@@ -163,40 +185,165 @@ class Detections:
)
@classmethod
def from_yolov8(cls, yolov8_results) -> Detections:
def from_ultralytics(cls, ultralytics_results) -> Detections:
"""
Creates a Detections instance from a YOLOv8 output Results
Creates a Detections instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
Args:
yolov8_results (ultralytics.yolo.engine.results.Results): The output Results instance from YOLOv8
ultralytics_results (ultralytics.yolo.engine.results.Results):
The output Results instance from YOLOv8
Returns:
Detections: A new Detections object.
Example:
```python
>>> from ultralytics import YOLO
>>> from supervision import Detections
>>> import cv2
>>> from ultralytics import YOLO, FastSAM, SAM, RTDETR
>>> import supervision as sv
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> model = YOLO('yolov8s.pt')
>>> yolov8_results = model(IMAGE)[0]
>>> detections = Detections.from_yolov8(yolov8_results)
>>> model = SAM('sam_b.pt')
>>> model = SAM('mobile_sam.pt')
>>> model = FastSAM('FastSAM-s.pt')
>>> model = RTDETR('rtdetr-l.pt')
>>> # model inferences
>>> result = model(image)[0]
>>> # if tracker is enabled
>>> result = model.track(image)[0]
>>> detections = sv.Detections.from_ultralytics(result)
```
"""
return cls(
xyxy=yolov8_results.boxes.xyxy.cpu().numpy(),
confidence=yolov8_results.boxes.conf.cpu().numpy(),
class_id=yolov8_results.boxes.cls.cpu().numpy().astype(int),
xyxy=ultralytics_results.boxes.xyxy.cpu().numpy(),
confidence=ultralytics_results.boxes.conf.cpu().numpy(),
class_id=ultralytics_results.boxes.cls.cpu().numpy().astype(int),
mask=extract_ultralytics_masks(ultralytics_results),
tracker_id=ultralytics_results.boxes.id.int().cpu().numpy()
if ultralytics_results.boxes.id is not None
else None,
)
@classmethod
def from_yolo_nas(cls, yolo_nas_results) -> Detections:
"""
Creates a Detections instance from a
[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md)
inference result.
Args:
yolo_nas_results (ImageDetectionPrediction):
The output Results instance from YOLO-NAS
ImageDetectionPrediction is coming from
'super_gradients.training.models.prediction_results'
Returns:
Detections: A new Detections object.
Example:
```python
>>> import cv2
>>> from super_gradients.training import models
>>> import supervision as sv
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> model = models.get('yolo_nas_l', pretrained_weights="coco")
>>> result = list(model.predict(image, conf=0.35))[0]
>>> detections = sv.Detections.from_yolo_nas(result)
```
"""
if np.asarray(yolo_nas_results.prediction.bboxes_xyxy).shape[0] == 0:
return cls.empty()
return cls(
xyxy=yolo_nas_results.prediction.bboxes_xyxy,
confidence=yolo_nas_results.prediction.confidence,
class_id=yolo_nas_results.prediction.labels.astype(int),
)
@classmethod
def from_deepsparse(cls, deepsparse_results) -> Detections:
"""
Creates a Detections instance from a
[DeepSparse](https://github.com/neuralmagic/deepsparse)
inference result.
Args:
deepsparse_results (deepsparse.yolo.schemas.YOLOOutput):
The output Results instance from DeepSparse.
Returns:
Detections: A new Detections object.
Example:
```python
>>> from deepsparse import Pipeline
>>> import supervision as sv
>>> yolo_pipeline = Pipeline.create(
... task="yolo",
... model_path = "zoo:cv/detection/yolov5-l/pytorch/" \
... "ultralytics/coco/pruned80_quant-none"
>>> pipeline_outputs = yolo_pipeline(SOURCE_IMAGE_PATH,
... iou_thres=0.6, conf_thres=0.001)
>>> detections = sv.Detections.from_deepsparse(result)
```
"""
if np.asarray(deepsparse_results.boxes[0]).shape[0] == 0:
return cls.empty()
return cls(
xyxy=np.array(deepsparse_results.boxes[0]),
confidence=np.array(deepsparse_results.scores[0]),
class_id=np.array(deepsparse_results.labels[0]).astype(float).astype(int),
)
@classmethod
def from_mmdetection(cls, mmdet_results) -> Detections:
"""
Creates a Detections instance from
a [mmdetection](https://github.com/open-mmlab/mmdetection) inference result.
Also supported for [mmyolo](https://github.com/open-mmlab/mmyolo)
Args:
mmdet_results (mmdet.structures.DetDataSample):
The output Results instance from MMDetection.
Returns:
Detections: A new Detections object.
Example:
```python
>>> import cv2
>>> import supervision as sv
>>> from mmdet.apis import DetInferencer
>>> inferencer = DetInferencer(model_name, checkpoint, device)
>>> mmdet_result = inferencer(SOURCE_IMAGE_PATH, out_dir='./output',
... return_datasample=True)["predictions"][0]
>>> detections = sv.Detections.from_mmdet(mmdet_result)
```
"""
return cls(
xyxy=mmdet_results.pred_instances.bboxes.cpu().numpy(),
confidence=mmdet_results.pred_instances.scores.cpu().numpy(),
class_id=mmdet_results.pred_instances.labels.cpu().numpy().astype(int),
)
@classmethod
def from_transformers(cls, transformers_results: dict) -> Detections:
"""
Creates a Detections instance from Object Detection Transformer output Results
Creates a Detections instance from object detection
[transformer](https://github.com/huggingface/transformers) inference result.
Returns:
Detections: A new Detections object.
"""
return cls(
xyxy=transformers_results["boxes"].cpu().numpy(),
confidence=transformers_results["scores"].cpu().numpy(),
@@ -205,6 +352,35 @@ class Detections:
@classmethod
def from_detectron2(cls, detectron2_results) -> Detections:
"""
Create a Detections 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:
(Detections): A Detections object containing the bounding boxes,
class IDs, and confidences of the predictions.
Example:
```python
>>> import cv2
>>> from detectron2.engine import DefaultPredictor
>>> from detectron2.config import get_cfg
>>> import supervision as sv
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> cfg = get_cfg()
>>> cfg.merge_from_file("path/to/config.yaml")
>>> cfg.MODEL.WEIGHTS = "path/to/model_weights.pth"
>>> predictor = DefaultPredictor(cfg)
>>> result = predictor(image)
>>> detections = sv.Detections.from_detectron2(result)
```
"""
return cls(
xyxy=detectron2_results["instances"].pred_boxes.tensor.cpu().numpy(),
confidence=detectron2_results["instances"].scores.cpu().numpy(),
@@ -215,34 +391,62 @@ class Detections:
)
@classmethod
def from_roboflow(cls, roboflow_result: dict, class_list: List[str]) -> Detections:
xyxy = []
confidence = []
class_id = []
def from_roboflow(cls, roboflow_result: dict) -> Detections:
"""
Create a Detections object from the [Roboflow](https://roboflow.com/)
API inference result.
for prediction in roboflow_result["predictions"]:
x = prediction["x"]
y = prediction["y"]
width = prediction["width"]
height = prediction["height"]
x_min = x - width / 2
y_min = y - height / 2
x_max = x_min + width
y_max = y_min + height
xyxy.append([x_min, y_min, x_max, y_max])
class_id.append(class_list.index(prediction["class"]))
confidence.append(prediction["confidence"])
Args:
roboflow_result (dict): The result from the
Roboflow API containing predictions.
return Detections(
xyxy=np.array(xyxy),
confidence=np.array(confidence),
class_id=np.array(class_id).astype(int),
Returns:
(Detections): A Detections object containing the bounding boxes, class IDs,
and confidences of the predictions.
Example:
```python
>>> import supervision as sv
>>> roboflow_result = {
... "predictions": [
... {
... "x": 0.5,
... "y": 0.5,
... "width": 0.2,
... "height": 0.3,
... "class_id": 0,
... "class": "person",
... "confidence": 0.9
... },
... # ... more predictions ...
... ]
... }
>>> detections = sv.Detections.from_roboflow(roboflow_result)
```
"""
xyxy, confidence, class_id, masks, trackers = process_roboflow_result(
roboflow_result=roboflow_result
)
if np.asarray(xyxy).shape[0] == 0:
return cls.empty()
return cls(
xyxy=xyxy,
confidence=confidence,
class_id=class_id,
mask=masks,
tracker_id=trackers,
)
@classmethod
def from_sam(cls, sam_result: List[dict]) -> Detections:
"""
Creates a Detections instance from Segment Anything Model (SAM) by Meta AI.
Creates a Detections instance from
[Segment Anything Model](https://github.com/facebookresearch/segment-anything)
inference result.
Args:
sam_result (List[dict]): The output Results instance from SAM
@@ -252,15 +456,20 @@ class Detections:
Example:
```python
>>> from segment_anything import sam_model_registry, SamAutomaticMaskGenerator
>>> import supervision as sv
>>> from segment_anything import (
... sam_model_registry,
... SamAutomaticMaskGenerator
... )
>>> sam = sam_model_registry[MODEL_TYPE](checkpoint=CHECKPOINT_PATH).to(device=DEVICE)
>>> sam_model_reg = sam_model_registry[MODEL_TYPE]
>>> sam = sam_model_reg(checkpoint=CHECKPOINT_PATH).to(device=DEVICE)
>>> mask_generator = SamAutomaticMaskGenerator(sam)
>>> sam_result = mask_generator.generate(IMAGE)
>>> detections = sv.Detections.from_sam(sam_result=sam_result)
```
"""
sorted_generated_masks = sorted(
sam_result, key=lambda x: x["area"], reverse=True
)
@@ -268,36 +477,148 @@ class Detections:
xywh = np.array([mask["bbox"] for mask in sorted_generated_masks])
mask = np.array([mask["segmentation"] for mask in sorted_generated_masks])
return Detections(xyxy=xywh_to_xyxy(boxes_xywh=xywh), mask=mask)
if np.asarray(xywh).shape[0] == 0:
return cls.empty()
xyxy = xywh_to_xyxy(boxes_xywh=xywh)
return cls(xyxy=xyxy, mask=mask)
@classmethod
def from_coco_annotations(cls, coco_annotation: dict) -> Detections:
xyxy, class_id = [], []
def from_paddledet(cls, paddledet_result) -> Detections:
"""
Creates a Detections instance from
[PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection)
inference result.
for annotation in coco_annotation:
x_min, y_min, width, height = annotation["bbox"]
xyxy.append([x_min, y_min, x_min + width, y_min + height])
class_id.append(annotation["category_id"])
Args:
paddledet_result (List[dict]): The output Results instance from PaddleDet
return cls(xyxy=np.array(xyxy), class_id=np.array(class_id))
Returns:
Detections: A new Detections object.
Example:
```python
>>> import supervision as sv
>>> import paddle
>>> from ppdet.engine import Trainer
>>> from ppdet.core.workspace import load_config
>>> weights = (...)
>>> config = (...)
>>> cfg = load_config(config)
>>> trainer = Trainer(cfg, mode='test')
>>> trainer.load_weights(weights)
>>> paddledet_result = trainer.predict([images])[0]
>>> detections = sv.Detections.from_paddledet(paddledet_result)
```
"""
if np.asarray(paddledet_result["bbox"][:, 2:6]).shape[0] == 0:
return cls.empty()
return cls(
xyxy=paddledet_result["bbox"][:, 2:6],
confidence=paddledet_result["bbox"][:, 1],
class_id=paddledet_result["bbox"][:, 0].astype(int),
)
@classmethod
def empty(cls) -> Detections:
"""
Create an empty Detections object with no bounding boxes,
confidences, or class IDs.
Returns:
(Detections): An empty Detections object.
Example:
```python
>>> from supervision import Detections
>>> empty_detections = Detections.empty()
```
"""
return cls(
xyxy=np.empty((0, 4), dtype=np.float32),
confidence=np.array([], dtype=np.float32),
class_id=np.array([], dtype=int),
)
def get_anchor_coordinates(self, anchor: Position) -> np.ndarray:
@classmethod
def merge(cls, detections_list: List[Detections]) -> Detections:
"""
Returns the bounding box coordinates for a specific anchor.
Merge a list of Detections objects into a single Detections object.
This method takes a list of Detections objects and combines their
respective fields (`xyxy`, `mask`, `confidence`, `class_id`, and `tracker_id`)
into a single Detections object. If all elements in a field are not
`None`, the corresponding field will be stacked.
Otherwise, the field will be set to `None`.
Args:
anchor (Position): Position of bounding box anchor for which to return the coordinates.
detections_list (List[Detections]): A list of Detections objects to merge.
Returns:
np.ndarray: An array of shape `(n, 2)` containing the bounding box anchor coordinates in format `[x, y]`.
(Detections): A single Detections object containing
the merged data from the input list.
Example:
```python
>>> from supervision import Detections
>>> detections_1 = Detections(...)
>>> detections_2 = Detections(...)
>>> merged_detections = Detections.merge([detections_1, detections_2])
```
"""
if len(detections_list) == 0:
return Detections.empty()
detections_tuples_list = [astuple(detection) for detection in detections_list]
xyxy, mask, confidence, class_id, tracker_id = [
list(field) for field in zip(*detections_tuples_list)
]
def __all_not_none(item_list: List[Any]):
return all(x is not None for x in item_list)
xyxy = np.vstack(xyxy)
mask = np.vstack(mask) if __all_not_none(mask) else None
confidence = np.hstack(confidence) if __all_not_none(confidence) else None
class_id = np.hstack(class_id) if __all_not_none(class_id) else None
tracker_id = np.hstack(tracker_id) if __all_not_none(tracker_id) else None
return cls(
xyxy=xyxy,
mask=mask,
confidence=confidence,
class_id=class_id,
tracker_id=tracker_id,
)
def get_anchor_coordinates(self, anchor: Position) -> np.ndarray:
"""
Calculates and returns the coordinates of a specific anchor point
within the bounding boxes defined by the `xyxy` attribute. The anchor
point can be any of the predefined positions in the `Position` enum,
such as `CENTER`, `CENTER_LEFT`, `BOTTOM_RIGHT`, etc.
Args:
anchor (Position): An enum specifying the position of the anchor point
within the bounding box. Supported positions are defined in the
`Position` enum.
Returns:
np.ndarray: An array of shape `(n, 2)`, where `n` is the number of bounding
boxes. Each row contains the `[x, y]` coordinates of the specified
anchor point for the corresponding bounding box.
Raises:
ValueError: If the provided `anchor` is not supported.
"""
if anchor == Position.CENTER:
return np.array(
@@ -306,40 +627,90 @@ class Detections:
(self.xyxy[:, 1] + self.xyxy[:, 3]) / 2,
]
).transpose()
elif anchor == Position.CENTER_LEFT:
return np.array(
[
self.xyxy[:, 0],
(self.xyxy[:, 1] + self.xyxy[:, 3]) / 2,
]
).transpose()
elif anchor == Position.CENTER_RIGHT:
return np.array(
[
self.xyxy[:, 2],
(self.xyxy[:, 1] + self.xyxy[:, 3]) / 2,
]
).transpose()
elif anchor == Position.BOTTOM_CENTER:
return np.array(
[(self.xyxy[:, 0] + self.xyxy[:, 2]) / 2, self.xyxy[:, 3]]
).transpose()
elif anchor == Position.BOTTOM_LEFT:
return np.array([self.xyxy[:, 0], self.xyxy[:, 3]]).transpose()
elif anchor == Position.BOTTOM_RIGHT:
return np.array([self.xyxy[:, 2], self.xyxy[:, 3]]).transpose()
elif anchor == Position.TOP_CENTER:
return np.array(
[(self.xyxy[:, 0] + self.xyxy[:, 2]) / 2, self.xyxy[:, 1]]
).transpose()
elif anchor == Position.TOP_LEFT:
return np.array([self.xyxy[:, 0], self.xyxy[:, 1]]).transpose()
elif anchor == Position.TOP_RIGHT:
return np.array([self.xyxy[:, 2], self.xyxy[:, 1]]).transpose()
raise ValueError(f"{anchor} is not supported.")
def __getitem__(self, index: np.ndarray) -> Detections:
if isinstance(index, np.ndarray) and (
index.dtype == bool or index.dtype == int
):
return Detections(
xyxy=self.xyxy[index],
mask=self.mask[index] if self.mask is not None else None,
confidence=self.confidence[index]
if self.confidence is not None
else None,
class_id=self.class_id[index] if self.class_id is not None else None,
tracker_id=self.tracker_id[index]
if self.tracker_id is not None
else None,
)
raise TypeError(
f"Detections.__getitem__ not supported for index of type {type(index)}."
def __getitem__(
self, index: Union[int, slice, List[int], np.ndarray]
) -> Detections:
"""
Get a subset of the Detections object.
Args:
index (Union[int, slice, List[int], np.ndarray]):
The index or indices of the subset of the Detections
Returns:
(Detections): A subset of the Detections object.
Example:
```python
>>> import supervision as sv
>>> detections = sv.Detections(...)
>>> first_detection = detections[0]
>>> first_10_detections = detections[0:10]
>>> some_detections = detections[[0, 2, 4]]
>>> class_0_detections = detections[detections.class_id == 0]
>>> high_confidence_detections = detections[detections.confidence > 0.5]
```
"""
if isinstance(index, int):
index = [index]
return Detections(
xyxy=self.xyxy[index],
mask=self.mask[index] if self.mask is not None else None,
confidence=self.confidence[index] if self.confidence is not None else None,
class_id=self.class_id[index] if self.class_id is not None else None,
tracker_id=self.tracker_id[index] if self.tracker_id is not None else None,
)
@property
def area(self) -> np.ndarray:
"""
Calculate the area of each detection in the set of object detections. If masks field is defined property
returns are of each mask. If only box is given property return area of each box.
Calculate the area of each detection in the set of object detections.
If masks field is defined property returns are of each mask.
If only box is given property return area of each box.
Returns:
np.ndarray: An array of floats containing the area of each detection in the format of `(area_1, area_2, ..., area_n)`, where n is the number of detections.
np.ndarray: An array of floats containing the area of each detection
in the format of `(area_1, area_2, ..., area_n)`,
where n is the number of detections.
"""
if self.mask is not None:
return np.array([np.sum(mask) for mask in self.mask])
@@ -352,7 +723,9 @@ class Detections:
Calculate the area of each bounding box in the set of object detections.
Returns:
np.ndarray: An array of floats containing the area of each bounding box in the format of `(area_1, area_2, ..., area_n)`, where n is the number of detections.
np.ndarray: An array of floats containing the area of each bounding
box in the format of `(area_1, area_2, ..., area_n)`,
where n is the number of detections.
"""
return (self.xyxy[:, 3] - self.xyxy[:, 1]) * (self.xyxy[:, 2] - self.xyxy[:, 0])
@@ -363,21 +736,26 @@ class Detections:
Perform non-maximum suppression on the current set of object detections.
Args:
threshold (float, optional): The intersection-over-union threshold to use for non-maximum suppression. Defaults to 0.5.
class_agnostic (bool, optional): Whether to perform class-agnostic non-maximum suppression. If True, the class_id of each detection will be ignored. Defaults to False.
threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression. Defaults to 0.5.
class_agnostic (bool, optional): Whether to perform class-agnostic
non-maximum suppression. If True, the class_id of each detection
will be ignored. Defaults to False.
Returns:
Detections: A new Detections object containing the subset of detections after non-maximum suppression.
Detections: A new Detections object containing the subset of detections
after non-maximum suppression.
Raises:
AssertionError: If `confidence` is None and class_agnostic is False. If `class_id` is None and class_agnostic is False.
AssertionError: If `confidence` is None and class_agnostic is False.
If `class_id` is None and class_agnostic is False.
"""
if len(self) == 0:
return self
assert (
self.confidence is not None
), f"Detections confidence must be given for NMS to be executed."
), "Detections confidence must be given for NMS to be executed."
if class_agnostic:
predictions = np.hstack((self.xyxy, self.confidence.reshape(-1, 1)))
@@ -387,8 +765,8 @@ class Detections:
return self[indices]
assert self.class_id is not None, (
f"Detections class_id must be given for NMS to be executed. If you intended to perform class agnostic "
f"NMS set class_agnostic=True."
"Detections class_id must be given for NMS to be executed. If you intended"
" to perform class agnostic NMS set class_agnostic=True."
)
predictions = np.hstack(
+52 -19
View File
@@ -1,4 +1,4 @@
from typing import Dict
from typing import Dict, Optional, Tuple
import cv2
import numpy as np
@@ -10,37 +10,54 @@ from supervision.geometry.core import Point, Rect, Vector
class LineZone:
"""
Count the number of objects that cross a line.
This class is responsible for counting the number of objects that cross a
predefined line.
!!! warning
LineZone utilizes the `tracker_id`. Read
[here](https://supervision.roboflow.com/trackers/) to learn how to plug
tracking into your inference pipeline.
Attributes:
in_count (int): The number of objects that have crossed the line from outside
to inside.
out_count (int): The number of objects that have crossed the line from inside
to outside.
"""
def __init__(self, start: Point, end: Point):
"""
Initialize a LineCounter object.
Attributes:
Args:
start (Point): The starting point of the line.
end (Point): The ending point of the line.
"""
self.vector = Vector(start=start, end=end)
self.tracker_state: Dict[str, bool] = {}
self.in_count: int = 0
self.out_count: int = 0
def trigger(self, detections: Detections):
def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]:
"""
Update the in_count and out_count for the detections that cross the line.
Update the `in_count` and `out_count` based on the objects that cross the line.
Attributes:
detections (Detections): The detections for which to update the counts.
Args:
detections (Detections): A list of detections for which to update the
counts.
Returns:
A tuple of two boolean NumPy arrays. The first array indicates which
detections have crossed the line from outside to inside. The second
array indicates which detections have crossed the line from inside to
outside.
"""
for xyxy, _, confidence, class_id, tracker_id in detections:
# handle detections with no tracker_id
crossed_in = np.full(len(detections), False)
crossed_out = np.full(len(detections), False)
for i, (xyxy, _, confidence, class_id, tracker_id) in enumerate(detections):
if tracker_id is None:
continue
# we check if all four anchors of bbox are on the same side of vector
x1, y1, x2, y2 = xyxy
anchors = [
Point(x=x1, y=y1),
@@ -50,25 +67,27 @@ class LineZone:
]
triggers = [self.vector.is_in(point=anchor) for anchor in anchors]
# detection is partially in and partially out
if len(set(triggers)) == 2:
continue
tracker_state = triggers[0]
# handle new detection
if tracker_id not in self.tracker_state:
self.tracker_state[tracker_id] = tracker_state
continue
# handle detection on the same side of the line
if self.tracker_state.get(tracker_id) == tracker_state:
continue
self.tracker_state[tracker_id] = tracker_state
if tracker_state:
self.in_count += 1
crossed_in[i] = True
else:
self.out_count += 1
crossed_out[i] = True
return crossed_in, crossed_out
class LineZoneAnnotator:
@@ -81,6 +100,8 @@ class LineZoneAnnotator:
text_scale: float = 0.5,
text_offset: float = 1.5,
text_padding: int = 10,
custom_in_text: Optional[str] = None,
custom_out_text: Optional[str] = None,
):
"""
Initialize the LineCounterAnnotator object with default values.
@@ -102,6 +123,8 @@ class LineZoneAnnotator:
self.text_scale: float = text_scale
self.text_offset: float = text_offset
self.text_padding: int = text_padding
self.custom_in_text: str = custom_in_text
self.custom_out_text: str = custom_out_text
def annotate(self, frame: np.ndarray, line_counter: LineZone) -> np.ndarray:
"""
@@ -109,7 +132,8 @@ class LineZoneAnnotator:
Attributes:
frame (np.ndarray): The image on which the line will be drawn.
line_counter (LineCounter): The line counter that will be used to draw the line.
line_counter (LineCounter): The line counter
that will be used to draw the line.
Returns:
np.ndarray: The image with the line drawn on it.
@@ -141,8 +165,16 @@ class LineZoneAnnotator:
lineType=cv2.LINE_AA,
)
in_text = f"in: {line_counter.in_count}"
out_text = f"out: {line_counter.out_count}"
in_text = (
f"{self.custom_in_text}: {line_counter.in_count}"
if self.custom_in_text is not None
else f"in: {line_counter.in_count}"
)
out_text = (
f"{self.custom_out_text}: {line_counter.out_count}"
if self.custom_out_text is not None
else f"out: {line_counter.out_count}"
)
(in_text_width, in_text_height), _ = cv2.getTextSize(
in_text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
@@ -219,3 +251,4 @@ class LineZoneAnnotator:
self.text_thickness,
cv2.LINE_AA,
)
return frame
@@ -0,0 +1,181 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Callable, Optional, Tuple
import numpy as np
from supervision.detection.core import Detections, validate_inference_callback
from supervision.detection.utils import move_boxes
from supervision.utils.image import crop_image
def move_detections(detections: Detections, offset: np.array) -> Detections:
"""
Args:
detections (sv.Detections): Detections object to be moved.
offset (np.array): An array of shape `(2,)` containing offset values in format
is `[dx, dy]`.
Returns:
(sv.Detections) repositioned Detections object.
"""
detections.xyxy = move_boxes(xyxy=detections.xyxy, offset=offset)
return detections
class InferenceSlicer:
"""
InferenceSlicer performs slicing-based inference for small target detection. This
method, often referred to as
[Slicing Adaptive Inference (SAHI)](https://ieeexplore.ieee.org/document/9897990),
involves dividing a larger image into smaller slices, performing inference on each
slice, and then merging the detections.
Args:
slice_wh (Tuple[int, int]): Dimensions of each slice in the format
`(width, height)`.
overlap_ratio_wh (Tuple[float, float]): Overlap ratio between consecutive
slices in the format `(width_ratio, height_ratio)`.
iou_threshold (Optional[float]): Intersection over Union (IoU) threshold
used for non-max suppression.
callback (Callable): A function that performs inference on a given image
slice and returns detections.
thread_workers (int): Number of threads for parallel execution.
Note:
The class ensures that slices do not exceed the boundaries of the original
image. As a result, the final slices in the row and column dimensions might be
smaller than the specified slice dimensions if the image's width or height is
not a multiple of the slice's width or height minus the overlap.
"""
def __init__(
self,
callback: Callable[[np.ndarray], Detections],
slice_wh: Tuple[int, int] = (320, 320),
overlap_ratio_wh: Tuple[float, float] = (0.2, 0.2),
iou_threshold: Optional[float] = 0.5,
thread_workers: int = 1,
):
self.slice_wh = slice_wh
self.overlap_ratio_wh = overlap_ratio_wh
self.iou_threshold = iou_threshold
self.callback = callback
self.thread_workers = thread_workers
validate_inference_callback(callback=callback)
def __call__(self, image: np.ndarray) -> Detections:
"""
Performs slicing-based inference on the provided image using the specified
callback.
Args:
image (np.ndarray): The input image on which inference needs to be
performed. The image should be in the format
`(height, width, channels)`.
Returns:
Detections: A collection of detections for the entire image after merging
results from all slices and applying NMS.
Example:
```python
>>> import cv2
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> image = cv2.imread(SOURCE_IMAGE_PATH)
>>> model = YOLO(...)
>>> def callback(image_slice: np.ndarray) -> sv.Detections:
... result = model(image_slice)[0]
... return sv.Detections.from_ultralytics(result)
>>> slicer = sv.InferenceSlicer(callback = callback)
>>> detections = slicer(image)
```
"""
detections_list = []
resolution_wh = (image.shape[1], image.shape[0])
offsets = self._generate_offset(
resolution_wh=resolution_wh,
slice_wh=self.slice_wh,
overlap_ratio_wh=self.overlap_ratio_wh,
)
with ThreadPoolExecutor(max_workers=self.thread_workers) as executor:
futures = [
executor.submit(self._run_callback, image, offset) for offset in offsets
]
for future in as_completed(futures):
detections_list.append(future.result())
return Detections.merge(detections_list=detections_list).with_nms(
threshold=self.iou_threshold
)
def _run_callback(self, image, offset) -> Detections:
"""
Run the provided callback on a slice of an image.
Args:
image (np.ndarray): The input image on which inference needs to run
offset (np.ndarray): An array of shape `(4,)` containing coordinates
for the slice.
Returns:
Detections: A collection of detections for the slice.
"""
image_slice = crop_image(image=image, xyxy=offset)
detections = self.callback(image_slice)
detections = move_detections(detections=detections, offset=offset[:2])
return detections
@staticmethod
def _generate_offset(
resolution_wh: Tuple[int, int],
slice_wh: Tuple[int, int],
overlap_ratio_wh: Tuple[float, float],
) -> np.ndarray:
"""
Generate offset coordinates for slicing an image based on the given resolution,
slice dimensions, and overlap ratios.
Args:
resolution_wh (Tuple[int, int]): A tuple representing the width and height
of the image to be sliced.
slice_wh (Tuple[int, int]): A tuple representing the desired width and
height of each slice.
overlap_ratio_wh (Tuple[float, float]): A tuple representing the desired
overlap ratio for width and height between consecutive slices. Each
value should be in the range [0, 1), where 0 means no overlap and a
value close to 1 means high overlap.
Returns:
np.ndarray: An array of shape `(n, 4)` containing coordinates for each
slice in the format `[xmin, ymin, xmax, ymax]`.
Note:
The function ensures that slices do not exceed the boundaries of the
original image. As a result, the final slices in the row and column
dimensions might be smaller than the specified slice dimensions if the
image's width or height is not a multiple of the slice's width or
height minus the overlap.
"""
slice_width, slice_height = slice_wh
image_width, image_height = resolution_wh
overlap_ratio_width, overlap_ratio_height = overlap_ratio_wh
width_stride = slice_width - int(overlap_ratio_width * slice_width)
height_stride = slice_height - int(overlap_ratio_height * slice_height)
ws = np.arange(0, image_width, width_stride)
hs = np.arange(0, image_height, height_stride)
xmin, ymin = np.meshgrid(ws, hs)
xmax = np.clip(xmin + slice_width, 0, image_width)
ymax = np.clip(ymin + slice_height, 0, image_height)
offsets = np.stack([xmin, ymin, xmax, ymax], axis=-1).reshape(-1, 4)
return offsets
+18 -11
View File
@@ -5,7 +5,7 @@ import cv2
import numpy as np
from supervision import Detections
from supervision.detection.utils import clip_boxes, generate_2d_mask
from supervision.detection.utils import clip_boxes, polygon_to_mask
from supervision.draw.color import Color
from supervision.draw.utils import draw_polygon, draw_text
from supervision.geometry.core import Position
@@ -17,9 +17,11 @@ class PolygonZone:
A class for defining a polygon-shaped zone within a frame for detecting objects.
Attributes:
polygon (np.ndarray): A numpy array defining the polygon vertices
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_position (Position): The position within the bounding
box that triggers the zone (default: 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
"""
@@ -30,13 +32,13 @@ class PolygonZone:
frame_resolution_wh: Tuple[int, int],
triggering_position: Position = Position.BOTTOM_CENTER,
):
self.polygon = polygon
self.polygon = polygon.astype(int)
self.frame_resolution_wh = frame_resolution_wh
self.triggering_position = triggering_position
self.current_count = 0
width, height = frame_resolution_wh
self.mask = generate_2d_mask(
self.mask = polygon_to_mask(
polygon=polygon, resolution_wh=(width + 1, height + 1)
)
@@ -45,10 +47,12 @@ class PolygonZone:
Determines if the detections are within the polygon zone.
Parameters:
detections (Detections): The detections to be checked against the polygon zone
detections (Detections): The detections
to be checked against the polygon zone
Returns:
np.ndarray: A boolean numpy array indicating if each detection is within the polygon zone
np.ndarray: A boolean numpy array indicating
if each detection is within the polygon zone
"""
clipped_xyxy = clip_boxes(
@@ -59,13 +63,14 @@ class PolygonZone:
clipped_detections.get_anchor_coordinates(anchor=self.triggering_position)
).astype(int)
is_in_zone = self.mask[clipped_anchors[:, 1], clipped_anchors[:, 0]]
self.current_count = np.sum(is_in_zone)
self.current_count = int(np.sum(is_in_zone))
return is_in_zone.astype(bool)
class PolygonZoneAnnotator:
"""
A class for annotating a polygon-shaped zone within a frame with a count of detected objects.
A class for annotating a polygon-shaped zone within a
frame with a count of detected objects.
Attributes:
zone (PolygonZone): The polygon zone to be annotated
@@ -75,7 +80,8 @@ class PolygonZoneAnnotator:
text_scale (float): The scale of the text on the polygon, default is 0.5
text_thickness (int): The thickness of the text on the polygon, default is 1
text_padding (int): The padding around the text on the polygon, default is 10
font (int): The font type for the text on the polygon, default is cv2.FONT_HERSHEY_SIMPLEX
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
"""
@@ -105,7 +111,8 @@ class PolygonZoneAnnotator:
Parameters:
scene (np.ndarray): The image on which the polygon zone will be annotated
label (Optional[str]): An optional label for the count of detected objects within the polygon zone (default: None)
label (Optional[str]): An optional label for the count of detected objects
within the polygon zone (default: None)
Returns:
np.ndarray: The image with the polygon zone and count of detected objects
+273 -24
View File
@@ -1,36 +1,46 @@
from typing import Tuple
from typing import List, Optional, Tuple
import cv2
import numpy as np
MIN_POLYGON_POINT_COUNT = 3
def generate_2d_mask(polygon: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
"""Generate a 2D mask from a polygon.
def polygon_to_mask(polygon: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray:
"""Generate a mask from a polygon.
Args:
polygon (np.ndarray): The polygon for which the mask should be generated, given as a list of vertices.
polygon (np.ndarray): The polygon for which the mask should be generated,
given as a list of vertices.
resolution_wh (Tuple[int, int]): The width and height of the desired resolution.
Returns:
np.ndarray: The generated 2D mask, where the polygon is marked with `1`'s and the rest is filled with `0`'s.
np.ndarray: The generated 2D mask, where the polygon is marked with
`1`'s and the rest is filled with `0`'s.
"""
width, height = resolution_wh
mask = np.zeros((height, width), dtype=np.uint8)
mask = np.zeros((height, width))
cv2.fillPoly(mask, [polygon], color=1)
return mask
def box_iou_batch(boxes_true: np.ndarray, boxes_detection: np.ndarray) -> np.ndarray:
"""
Compute Intersection over Union (IoU) of two sets of bounding boxes - `boxes_true` and `boxes_detection`. Both sets
of boxes are expected to be in `(x_min, y_min, x_max, y_max)` format.
Compute Intersection over Union (IoU) of two sets of bounding boxes -
`boxes_true` and `boxes_detection`. Both sets
of boxes are expected to be in `(x_min, y_min, x_max, y_max)` format.
Args:
boxes_true (np.ndarray): 2D `np.ndarray` representing ground-truth boxes. `shape = (N, 4)` where `N` is number of true objects.
boxes_detection (np.ndarray): 2D `np.ndarray` representing detection boxes. `shape = (M, 4)` where `M` is number of detected objects.
boxes_true (np.ndarray): 2D `np.ndarray` representing ground-truth boxes.
`shape = (N, 4)` where `N` is number of true objects.
boxes_detection (np.ndarray): 2D `np.ndarray` representing detection boxes.
`shape = (M, 4)` where `M` is number of detected objects.
Returns:
np.ndarray: Pairwise IoU of boxes from `boxes_true` and `boxes_detection`. `shape = (N, M)` where `N` is number of true objects and `M` is number of detected objects.
np.ndarray: Pairwise IoU of boxes from `boxes_true` and `boxes_detection`.
`shape = (N, M)` where `N` is number of true objects and
`M` is number of detected objects.
"""
def box_area(box):
@@ -53,17 +63,22 @@ def non_max_suppression(
Perform Non-Maximum Suppression (NMS) on object detection predictions.
Args:
predictions (np.ndarray): An array of object detection predictions in the format of `(x_min, y_min, x_max, y_max, score)` or `(x_min, y_min, x_max, y_max, score, class)`.
iou_threshold (float, optional): The intersection-over-union threshold to use for non-maximum suppression.
predictions (np.ndarray): An array of object detection predictions in
the format of `(x_min, y_min, x_max, y_max, score)`
or `(x_min, y_min, x_max, y_max, score, class)`.
iou_threshold (float, optional): The intersection-over-union threshold
to use for non-maximum suppression.
Returns:
np.ndarray: A boolean array indicating which predictions to keep after non-maximum suppression.
np.ndarray: A boolean array indicating which predictions to keep after n
on-maximum suppression.
Raises:
AssertionError: If `iou_threshold` is not within the closed range from `0` to `1`.
AssertionError: If `iou_threshold` is not within the
closed range from `0` to `1`.
"""
assert 0 <= iou_threshold <= 1, (
f"Value of `iou_threshold` must be in the closed range from 0 to 1, "
"Value of `iou_threshold` must be in the closed range from 0 to 1, "
f"{iou_threshold} given."
)
rows, columns = predictions.shape
@@ -87,7 +102,8 @@ def non_max_suppression(
if not keep[index]:
continue
# drop detections with iou > iou_threshold and same category as current detections
# drop detections with iou > iou_threshold and
# same category as current detections
condition = (iou > iou_threshold) & (categories == category)
keep = keep & ~condition
@@ -101,14 +117,16 @@ def clip_boxes(
Clips bounding boxes coordinates to fit within the frame resolution.
Args:
boxes_xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each row corresponds to a bounding box in
boxes_xyxy (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)`.
frame_resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)` representing the resolution of the
frame.
frame_resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
representing the resolution of the frame.
Returns:
np.ndarray: A numpy array of shape `(N, 4)` where each row corresponds to a bounding box with coordinates
clipped to fit within the frame resolution.
np.ndarray: A numpy array of shape `(N, 4)` where each row
corresponds to a bounding box with coordinates clipped to fit
within the frame resolution.
"""
result = np.copy(boxes_xyxy)
width, height = frame_resolution_wh
@@ -129,10 +147,12 @@ def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
Converts a 3D `np.array` of 2D bool masks into a 2D `np.array` of bounding boxes.
Parameters:
masks (np.ndarray): A 3D `np.array` of shape `(N, W, H)` containing 2D bool masks
masks (np.ndarray): A 3D `np.array` of shape `(N, W, H)`
containing 2D bool masks
Returns:
np.ndarray: A 2D `np.array` of shape `(N, 4)` containing the bounding boxes `(x_min, y_min, x_max, y_max)` for each mask
np.ndarray: A 2D `np.array` of shape `(N, 4)` containing the bounding boxes
`(x_min, y_min, x_max, y_max)` for each mask
"""
n = masks.shape[0]
bboxes = np.zeros((n, 4), dtype=int)
@@ -146,3 +166,232 @@ def mask_to_xyxy(masks: np.ndarray) -> np.ndarray:
bboxes[i, :] = [x_min, y_min, x_max, y_max]
return bboxes
def mask_to_polygons(mask: np.ndarray) -> List[np.ndarray]:
"""
Converts a binary mask to a list of polygons.
Parameters:
mask (np.ndarray): A binary mask represented as a 2D NumPy array of
shape `(H, W)`, where H and W are the height and width of
the mask, respectively.
Returns:
List[np.ndarray]: A list of polygons, where each polygon is represented by a
NumPy array of shape `(N, 2)`, containing the `x`, `y` coordinates
of the points. Polygons with fewer points than `MIN_POLYGON_POINT_COUNT = 3`
are excluded from the output.
"""
contours, _ = cv2.findContours(
mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE
)
return [
np.squeeze(contour, axis=1)
for contour in contours
if contour.shape[0] >= MIN_POLYGON_POINT_COUNT
]
def filter_polygons_by_area(
polygons: List[np.ndarray],
min_area: Optional[float] = None,
max_area: Optional[float] = None,
) -> List[np.ndarray]:
"""
Filters a list of polygons based on their area.
Parameters:
polygons (List[np.ndarray]): A list of polygons, where each polygon is
represented by a NumPy array of shape `(N, 2)`,
containing the `x`, `y` coordinates of the points.
min_area (Optional[float]): The minimum area threshold.
Only polygons with an area greater than or equal to this value
will be included in the output. If set to None,
no minimum area constraint will be applied.
max_area (Optional[float]): The maximum area threshold.
Only polygons with an area less than or equal to this value
will be included in the output. If set to None,
no maximum area constraint will be applied.
Returns:
List[np.ndarray]: A new list of polygons containing only those with
areas within the specified thresholds.
"""
if min_area is None and max_area is None:
return polygons
ares = [cv2.contourArea(polygon) for polygon in polygons]
return [
polygon
for polygon, area in zip(polygons, ares)
if (min_area is None or area >= min_area)
and (max_area is None or area <= max_area)
]
def polygon_to_xyxy(polygon: np.ndarray) -> np.ndarray:
"""
Converts a polygon represented by a NumPy array into a bounding box.
Parameters:
polygon (np.ndarray): A polygon represented by a NumPy array of shape `(N, 2)`,
containing the `x`, `y` coordinates of the points.
Returns:
np.ndarray: A 1D NumPy array containing the bounding box
`(x_min, y_min, x_max, y_max)` of the input polygon.
"""
x_min, y_min = np.min(polygon, axis=0)
x_max, y_max = np.max(polygon, axis=0)
return np.array([x_min, y_min, x_max, y_max])
def approximate_polygon(
polygon: np.ndarray, percentage: float, epsilon_step: float = 0.05
) -> np.ndarray:
"""
Approximates a given polygon by reducing a certain percentage of points.
This function uses the Ramer-Douglas-Peucker algorithm to simplify the input
polygon by reducing the number of points
while preserving the general shape.
Parameters:
polygon (np.ndarray): A 2D NumPy array of shape `(N, 2)` containing
the `x`, `y` coordinates of the input polygon's points.
percentage (float): The percentage of points to be removed from the
input polygon, in the range `[0, 1)`.
epsilon_step (float): Approximation accuracy step.
Epsilon is the maximum distance between the original curve
and its approximation.
Returns:
np.ndarray: A new 2D NumPy array of shape `(M, 2)`,
where `M <= N * (1 - percentage)`, containing
the `x`, `y` coordinates of the
approximated polygon's points.
"""
if percentage < 0 or percentage >= 1:
raise ValueError("Percentage must be in the range [0, 1).")
target_points = max(int(len(polygon) * (1 - percentage)), 3)
if len(polygon) <= target_points:
return polygon
epsilon = 0
approximated_points = polygon
while True:
epsilon += epsilon_step
new_approximated_points = cv2.approxPolyDP(polygon, epsilon, closed=True)
if len(new_approximated_points) > target_points:
approximated_points = new_approximated_points
else:
break
return np.squeeze(approximated_points, axis=1)
def extract_ultralytics_masks(yolov8_results) -> Optional[np.ndarray]:
if not yolov8_results.masks:
return None
orig_shape = yolov8_results.orig_shape
inference_shape = tuple(yolov8_results.masks.data.shape[1:])
gain = 0
pad = (0, 0)
if inference_shape != orig_shape:
gain = min(
inference_shape[0] / orig_shape[0],
inference_shape[1] / orig_shape[1],
)
pad = (
(inference_shape[1] - orig_shape[1] * gain) / 2,
(inference_shape[0] - orig_shape[0] * gain) / 2,
)
top, left = int(pad[1]), int(pad[0])
bottom, right = int(inference_shape[0] - pad[1]), int(inference_shape[1] - pad[0])
mask_maps = []
masks = yolov8_results.masks.data.cpu().numpy()
for i in range(masks.shape[0]):
mask = masks[i]
mask = mask[top:bottom, left:right]
if mask.shape != orig_shape:
mask = cv2.resize(mask, (orig_shape[1], orig_shape[0]))
mask_maps.append(mask)
return np.asarray(mask_maps, dtype=bool)
def process_roboflow_result(
roboflow_result: dict,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], np.ndarray]:
if not roboflow_result["predictions"]:
return np.empty((0, 4)), np.empty(0), np.empty(0), None, None
xyxy = []
confidence = []
class_id = []
masks = []
tracker_ids = []
image_width = int(roboflow_result["image"]["width"])
image_height = int(roboflow_result["image"]["height"])
for prediction in roboflow_result["predictions"]:
x = prediction["x"]
y = prediction["y"]
width = prediction["width"]
height = prediction["height"]
x_min = x - width / 2
y_min = y - height / 2
x_max = x_min + width
y_max = y_min + height
if "points" not in prediction:
xyxy.append([x_min, y_min, x_max, y_max])
class_id.append(prediction["class_id"])
confidence.append(prediction["confidence"])
if "tracker_id" in prediction:
tracker_ids.append(prediction["tracker_id"])
elif len(prediction["points"]) >= 3:
polygon = np.array(
[[point["x"], point["y"]] for point in prediction["points"]], dtype=int
)
mask = polygon_to_mask(polygon, resolution_wh=(image_width, image_height))
xyxy.append([x_min, y_min, x_max, y_max])
class_id.append(prediction["class_id"])
confidence.append(prediction["confidence"])
masks.append(mask)
if "tracker_id" in prediction:
tracker_ids.append(prediction["tracker_id"])
xyxy = np.array(xyxy) if len(xyxy) > 0 else np.empty((0, 4))
confidence = np.array(confidence) if len(confidence) > 0 else np.empty(0)
class_id = np.array(class_id).astype(int) if len(class_id) > 0 else np.empty(0)
masks = np.array(masks, dtype=bool) if len(masks) > 0 else None
tracker_id = np.array(tracker_ids).astype(int) if len(tracker_ids) > 0 else None
return xyxy, confidence, class_id, masks, tracker_id
def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray:
"""
Args:
xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes
coordinates in format `[x1, y1, x2, y2]`
offset (np.array): An array of shape `(2,)` containing offset values in format
is `[dx, dy]`.
Returns:
(np.ndarray) repositioned bounding boxes
"""
return xyxy + np.hstack([offset, offset])
+72 -19
View File
@@ -4,6 +4,7 @@ from dataclasses import dataclass
from typing import List, Tuple
DEFAULT_COLOR_PALETTE = [
"#a351fb",
"#e6194b",
"#3cb44b",
"#ffe119",
@@ -33,20 +34,35 @@ def _validate_color_hex(color_hex: str):
@dataclass
class Color:
"""
Represents a color in RGB format.
Attributes:
r (int): Red channel.
g (int): Green channel.
b (int): Blue channel.
"""
r: int
g: int
b: int
@classmethod
def from_hex(cls, color_hex: str):
def from_hex(cls, color_hex: str) -> Color:
"""
Creates a Color instance from a color hex string
Create a Color instance from a hex string.
:param color_hex: str : The color hex string in the format of "fff", "ffffff", "#fff", or "#ffffff"
:return: Color : A Color instance representing the color
Args:
color_hex (str): Hex string of the color.
Returns:
Color: Instance representing the color.
Example:
color = Color.from_hex('#ff00ff')
```
>>> Color.from_hex('#ff00ff')
Color(r=255, g=0, b=255)
```
"""
_validate_color_hex(color_hex)
color_hex = color_hex.lstrip("#")
@@ -57,17 +73,31 @@ class Color:
def as_rgb(self) -> Tuple[int, int, int]:
"""
Returns the color as a tuple of integers in the RGB format
Returns the color as an RGB tuple.
:return: Tuple[int, int, int] : The color in the RGB format
Returns:
Tuple[int, int, int]: RGB tuple.
Example:
```
>>> color.as_rgb()
(255, 0, 255)
```
"""
return self.r, self.g, self.b
def as_bgr(self) -> Tuple[int, int, int]:
"""
Returns the color as a tuple of integers in the BGR format
Returns the color as a BGR tuple.
:return: Tuple[int, int, int] : The color in the BGR format
Returns:
Tuple[int, int, int]: BGR tuple.
Example:
```
>>> color.as_bgr()
(255, 0, 255)
```
"""
return self.b, self.g, self.r
@@ -98,32 +128,55 @@ class ColorPalette:
@classmethod
def default(cls) -> ColorPalette:
"""
Returns a default color palette.
Returns:
ColorPalette: A ColorPalette instance with default colors.
Example:
```
>>> ColorPalette.default()
ColorPalette(colors=[Color(r=255, g=0, b=0), Color(r=0, g=255, b=0), ...])
```
"""
return ColorPalette.from_hex(color_hex_list=DEFAULT_COLOR_PALETTE)
@classmethod
def from_hex(cls, color_hex_list: List[str]):
def from_hex(cls, color_hex_list: List[str]) -> ColorPalette:
"""
Creates a ColorPalette instance from a list of color hex strings
Create a ColorPalette instance from a list of hex strings.
:param color_hex_list: List[str] : A list of color hex strings in the format of "fff", "ffffff", "#fff", or "#ffffff"
:return: ColorPalette : A ColorPalette instance representing the color palette
Args:
color_hex_list (List[str]): List of color hex strings.
Returns:
ColorPalette: A ColorPalette instance.
Example:
color_palette = ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff'])
```
>>> ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff'])
ColorPalette(colors=[Color(r=255, g=0, b=0), Color(r=0, g=255, b=0), ...])
```
"""
colors = [Color.from_hex(color_hex) for color_hex in color_hex_list]
return cls(colors)
def by_idx(self, idx: int) -> Color:
"""
Returns the color at a given index in the color palette.
Return the color at a given index in the palette.
:param idx: int : The index of the color in the color palette
:return: Color : The color at the given index
Args:
idx (int): Index of the color in the palette.
Returns:
Color: Color at the given index.
Example:
color_palette = ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff'])
color = color_palette.by_idx(1)
```
>>> color_palette.by_idx(1)
Color(r=0, g=255, b=0)
```
"""
if idx < 0:
raise ValueError("idx argument should not be negative")
+74 -7
View File
@@ -1,4 +1,5 @@
from typing import Optional
import os
from typing import Optional, Union
import cv2
import numpy as np
@@ -115,15 +116,19 @@ def draw_text(
Draw text with background on a scene.
Parameters:
scene (np.ndarray): A 2-dimensional numpy ndarray representing an image or scene.
scene (np.ndarray): A 2-dimensional numpy ndarray representing an image or scene
text (str): The text to be drawn.
text_anchor (Point): The anchor point for the text, represented as a Point object with x and y attributes.
text_anchor (Point): The anchor point for the text, represented as a
Point object with x and y attributes.
text_color (Color, optional): The color of the text. Defaults to black.
text_scale (float, optional): The scale of the text. Defaults to 0.5.
text_thickness (int, optional): The thickness of the text. Defaults to 1.
text_padding (int, optional): The amount of padding to add around the text when drawing a rectangle in the background. Defaults to 10.
text_font (int, optional): The font to use for the text. Defaults to cv2.FONT_HERSHEY_SIMPLEX.
background_color (Color, optional): The color of the background rectangle, if one is to be drawn. Defaults to None.
text_padding (int, optional): The amount of padding to add around the text
when drawing a rectangle in the background. Defaults to 10.
text_font (int, optional): The font to use for the text.
Defaults to cv2.FONT_HERSHEY_SIMPLEX.
background_color (Color, optional): The color of the background rectangle,
if one is to be drawn. Defaults to None.
Returns:
np.ndarray: The input scene with the text drawn on it.
@@ -132,7 +137,7 @@ def draw_text(
```python
>>> scene = np.zeros((100, 100, 3), dtype=np.uint8)
>>> text_anchor = Point(x=50, y=50)
>>> scene = draw_text(scene=scene, text="Hello, world!", text_anchor=text_anchor)
>>> scene = draw_text(scene=scene, text="Hello, world!",text_anchor=text_anchor)
```
"""
text_width, text_height = cv2.getTextSize(
@@ -164,3 +169,65 @@ def draw_text(
lineType=cv2.LINE_AA,
)
return scene
def draw_image(
scene: np.ndarray, image: Union[str, np.ndarray], opacity: float, rect: Rect
) -> np.ndarray:
"""
Draws an image onto a given scene with specified opacity and dimensions.
Args:
scene (np.ndarray): Background image where the new image will be drawn.
image (Union[str, np.ndarray]): Image to draw.
opacity (float): Opacity of the image to be drawn.
rect (Rect): Rectangle specifying where to draw the image.
Returns:
np.ndarray: The updated scene.
Raises:
FileNotFoundError: If the image path does not exist.
ValueError: For invalid opacity or rectangle dimensions.
"""
# Validate and load image
if isinstance(image, str):
if not os.path.exists(image):
raise FileNotFoundError(f"Image path ('{image}') does not exist.")
image = cv2.imread(image, cv2.IMREAD_UNCHANGED)
# Validate opacity
if not 0.0 <= opacity <= 1.0:
raise ValueError("Opacity must be between 0.0 and 1.0.")
# Validate rectangle dimensions
if (
rect.x < 0
or rect.y < 0
or rect.x + rect.width > scene.shape[1]
or rect.y + rect.height > scene.shape[0]
):
raise ValueError("Invalid rectangle dimensions.")
# Resize and isolate alpha channel
image = cv2.resize(image, (rect.width, rect.height))
alpha_channel = (
image[:, :, 3]
if image.shape[2] == 4
else np.ones((rect.height, rect.width), dtype=image.dtype) * 255
)
alpha_scaled = cv2.convertScaleAbs(alpha_channel * opacity)
# Perform blending
scene_roi = scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width]
alpha_float = alpha_scaled.astype(np.float32) / 255.0
blended_roi = cv2.convertScaleAbs(
(1 - alpha_float[..., np.newaxis]) * scene_roi
+ alpha_float[..., np.newaxis] * image[:, :, :3]
)
# Update the scene
scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width] = blended_roi
return scene
+11
View File
@@ -6,8 +6,19 @@ from typing import Tuple
class Position(Enum):
"""
Enum representing the position of an anchor point.
"""
CENTER = "CENTER"
CENTER_LEFT = "CENTER_LEFT"
CENTER_RIGHT = "CENTER_RIGHT"
TOP_CENTER = "TOP_CENTER"
TOP_LEFT = "TOP_LEFT"
TOP_RIGHT = "TOP_RIGHT"
BOTTOM_LEFT = "BOTTOM_LEFT"
BOTTOM_CENTER = "BOTTOM_CENTER"
BOTTOM_RIGHT = "BOTTOM_RIGHT"
@classmethod
def list(cls):
+8 -3
View File
@@ -7,13 +7,18 @@ def get_polygon_center(polygon: np.ndarray) -> Point:
"""
Calculate the center of a polygon.
This function takes in a polygon as a 2-dimensional numpy ndarray and returns the center of the polygon as a Point object. The center is calculated as the mean of the polygon's vertices along each axis, and is rounded down to the nearest integer.
This function takes in a polygon as a 2-dimensional numpy ndarray and
returns the center of the polygon as a Point object.
The center is calculated as the mean of the polygon's vertices along each axis,
and is rounded down to the nearest integer.
Parameters:
polygon (np.ndarray): A 2-dimensional numpy ndarray representing the vertices of the polygon.
polygon (np.ndarray): A 2-dimensional numpy ndarray representing the
vertices of the polygon.
Returns:
Point: The center of the polygon, represented as a Point object with x and y attributes.
Point: The center of the polygon, represented as a
Point object with x and y attributes.
Examples:
```python
View File
+843
View File
@@ -0,0 +1,843 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable, List, Optional, Tuple
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from supervision.dataset.core import DetectionDataset
from supervision.detection.core import Detections
from supervision.detection.utils import box_iou_batch
def detections_to_tensor(
detections: Detections, with_confidence: bool = False
) -> np.ndarray:
"""
Convert Supervision Detections to numpy tensors for further computation
Args:
detections (sv.Detections): Detections/Targets in the format of sv.Detections
with_confidence (bool): Whether to include confidence in the tensor
Returns:
(np.ndarray): Detections as numpy tensors as in (xyxy, class_id,
confidence) order
"""
if detections.class_id is None:
raise ValueError(
"ConfusionMatrix can only be calculated for Detections with class_id"
)
arrays_to_concat = [detections.xyxy, np.expand_dims(detections.class_id, 1)]
if with_confidence:
if detections.confidence is None:
raise ValueError(
"ConfusionMatrix can only be calculated for Detections with confidence"
)
arrays_to_concat.append(np.expand_dims(detections.confidence, 1))
return np.concatenate(arrays_to_concat, axis=1)
def validate_input_tensors(predictions: List[np.ndarray], targets: List[np.ndarray]):
"""
Checks for shape consistency of input tensors.
"""
if len(predictions) != len(targets):
raise ValueError(
f"Number of predictions ({len(predictions)}) and"
f"targets ({len(targets)}) must be equal."
)
if len(predictions) > 0:
if not isinstance(predictions[0], np.ndarray) or not isinstance(
targets[0], np.ndarray
):
raise ValueError(
f"Predictions and targets must be lists of numpy arrays."
f"Got {type(predictions[0])} and {type(targets[0])} instead."
)
if predictions[0].shape[1] != 6:
raise ValueError(
f"Predictions must have shape (N, 6)."
f"Got {predictions[0].shape} instead."
)
if targets[0].shape[1] != 5:
raise ValueError(
f"Targets must have shape (N, 5). Got {targets[0].shape} instead."
)
@dataclass
class ConfusionMatrix:
"""
Confusion matrix for object detection tasks.
Attributes:
matrix (np.ndarray): An 2D `np.ndarray` of shape
`(len(classes) + 1, len(classes) + 1)`
containing the number of `TP`, `FP`, `FN` and `TN` for each class.
classes (List[str]): Model class names.
conf_threshold (float): Detection confidence threshold between `0` and `1`.
Detections with lower confidence will be excluded from the matrix.
iou_threshold (float): Detection IoU threshold between `0` and `1`.
Detections with lower IoU will be classified as `FP`.
"""
matrix: np.ndarray
classes: List[str]
conf_threshold: float
iou_threshold: float
@classmethod
def from_detections(
cls,
predictions: List[Detections],
targets: List[Detections],
classes: List[str],
conf_threshold: float = 0.3,
iou_threshold: float = 0.5,
) -> ConfusionMatrix:
"""
Calculate confusion matrix based on predicted and ground-truth detections.
Args:
targets (List[Detections]): Detections objects from ground-truth.
predictions (List[Detections]): Detections objects predicted by the model.
classes (List[str]): Model class names.
conf_threshold (float): Detection confidence threshold between `0` and `1`.
Detections with lower confidence will be excluded.
iou_threshold (float): Detection IoU threshold between `0` and `1`.
Detections with lower IoU will be classified as `FP`.
Returns:
ConfusionMatrix: New instance of ConfusionMatrix.
Example:
```python
>>> import supervision as sv
>>> targets = [
... sv.Detections(...),
... sv.Detections(...)
... ]
>>> predictions = [
... sv.Detections(...),
... sv.Detections(...)
... ]
>>> confusion_matrix = sv.ConfusionMatrix.from_detections(
... predictions=predictions,
... targets=target,
... classes=['person', ...]
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
"""
prediction_tensors = []
target_tensors = []
for prediction, target in zip(predictions, targets):
prediction_tensors.append(
detections_to_tensor(prediction, with_confidence=True)
)
target_tensors.append(detections_to_tensor(target, with_confidence=False))
return cls.from_tensors(
predictions=prediction_tensors,
targets=target_tensors,
classes=classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
@classmethod
def from_tensors(
cls,
predictions: List[np.ndarray],
targets: List[np.ndarray],
classes: List[str],
conf_threshold: float = 0.3,
iou_threshold: float = 0.5,
) -> ConfusionMatrix:
"""
Calculate confusion matrix based on predicted and ground-truth detections.
Args:
predictions (List[np.ndarray]): Each element of the list describes a single
image and has `shape = (M, 6)` where `M` is the number of detected
objects. Each row is expected to be in
`(x_min, y_min, x_max, y_max, class, conf)` format.
targets (List[np.ndarray]): Each element of the list describes a single
image and has `shape = (N, 5)` where `N` is the number of
ground-truth objects. Each row is expected to be in
`(x_min, y_min, x_max, y_max, class)` format.
classes (List[str]): Model class names.
conf_threshold (float): Detection confidence threshold between `0` and `1`.
Detections with lower confidence will be excluded.
iou_threshold (float): Detection iou threshold between `0` and `1`.
Detections with lower iou will be classified as `FP`.
Returns:
ConfusionMatrix: New instance of ConfusionMatrix.
Example:
```python
>>> import supervision as sv
>>> targets = (
... [
... array(
... [
... [0.0, 0.0, 3.0, 3.0, 1],
... [2.0, 2.0, 5.0, 5.0, 1],
... [6.0, 1.0, 8.0, 3.0, 2],
... ]
... ),
... array([1.0, 1.0, 2.0, 2.0, 2]),
... ]
... )
>>> predictions = [
... array(
... [
... [0.0, 0.0, 3.0, 3.0, 1, 0.9],
... [0.1, 0.1, 3.0, 3.0, 0, 0.9],
... [6.0, 1.0, 8.0, 3.0, 1, 0.8],
... [1.0, 6.0, 2.0, 7.0, 1, 0.8],
... ]
... ),
... array([[1.0, 1.0, 2.0, 2.0, 2, 0.8]])
... ]
>>> confusion_matrix = sv.ConfusionMatrix.from_tensors(
... predictions=predictions,
... targets=targets,
... classes=['person', ...]
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
"""
validate_input_tensors(predictions, targets)
num_classes = len(classes)
matrix = np.zeros((num_classes + 1, num_classes + 1))
for true_batch, detection_batch in zip(targets, predictions):
matrix += cls.evaluate_detection_batch(
predictions=detection_batch,
targets=true_batch,
num_classes=num_classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
return cls(
matrix=matrix,
classes=classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
@staticmethod
def evaluate_detection_batch(
predictions: np.ndarray,
targets: np.ndarray,
num_classes: int,
conf_threshold: float,
iou_threshold: float,
) -> np.ndarray:
"""
Calculate confusion matrix for a batch of detections for a single image.
Args:
predictions (np.ndarray): Batch prediction. Describes a single image and
has `shape = (M, 6)` where `M` is the number of detected objects.
Each row is expected to be in
`(x_min, y_min, x_max, y_max, class, conf)` format.
targets (np.ndarray): Batch target labels. Describes a single image and
has `shape = (N, 5)` where `N` is the number of ground-truth objects.
Each row is expected to be in
`(x_min, y_min, x_max, y_max, class)` format.
num_classes (int): Number of classes.
conf_threshold (float): Detection confidence threshold between `0` and `1`.
Detections with lower confidence will be excluded.
iou_threshold (float): Detection iou threshold between `0` and `1`.
Detections with lower iou will be classified as `FP`.
Returns:
np.ndarray: Confusion matrix based on a single image.
"""
result_matrix = np.zeros((num_classes + 1, num_classes + 1))
conf_idx = 5
confidence = predictions[:, conf_idx]
detection_batch_filtered = predictions[confidence > conf_threshold]
class_id_idx = 4
true_classes = np.array(targets[:, class_id_idx], dtype=np.int16)
detection_classes = np.array(
detection_batch_filtered[:, class_id_idx], dtype=np.int16
)
true_boxes = targets[:, :class_id_idx]
detection_boxes = detection_batch_filtered[:, :class_id_idx]
iou_batch = box_iou_batch(
boxes_true=true_boxes, boxes_detection=detection_boxes
)
matched_idx = np.asarray(iou_batch > iou_threshold).nonzero()
if matched_idx[0].shape[0]:
matches = np.stack(
(matched_idx[0], matched_idx[1], iou_batch[matched_idx]), axis=1
)
matches = ConfusionMatrix._drop_extra_matches(matches=matches)
else:
matches = np.zeros((0, 3))
matched_true_idx, matched_detection_idx, _ = matches.transpose().astype(
np.int16
)
for i, true_class_value in enumerate(true_classes):
j = matched_true_idx == i
if matches.shape[0] > 0 and sum(j) == 1:
result_matrix[
true_class_value, detection_classes[matched_detection_idx[j]]
] += 1 # TP
else:
result_matrix[true_class_value, num_classes] += 1 # FN
for i, detection_class_value in enumerate(detection_classes):
if not any(matched_detection_idx == i):
result_matrix[num_classes, detection_class_value] += 1 # FP
return result_matrix
@staticmethod
def _drop_extra_matches(matches: np.ndarray) -> np.ndarray:
"""
Deduplicate matches. If there are multiple matches for the same true or
predicted box, only the one with the highest IoU is kept.
"""
if matches.shape[0] > 0:
matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
return matches
@classmethod
def benchmark(
cls,
dataset: DetectionDataset,
callback: Callable[[np.ndarray], Detections],
conf_threshold: float = 0.3,
iou_threshold: float = 0.5,
) -> ConfusionMatrix:
"""
Calculate confusion matrix from dataset and callback function.
Args:
dataset (DetectionDataset): Object detection dataset used for evaluation.
callback (Callable[[np.ndarray], Detections]): Function that takes an image
as input and returns Detections object.
conf_threshold (float): Detection confidence threshold between `0` and `1`.
Detections with lower confidence will be excluded.
iou_threshold (float): Detection IoU threshold between `0` and `1`.
Detections with lower IoU will be classified as `FP`.
Returns:
ConfusionMatrix: New instance of ConfusionMatrix.
Example:
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... result = model(image)[0]
... return sv.Detections.from_ultralytics(result)
>>> confusion_matrix = sv.ConfusionMatrix.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> confusion_matrix.matrix
array([
[0., 0., 0., 0.],
[0., 1., 0., 1.],
[0., 1., 1., 0.],
[1., 1., 0., 0.]
])
```
"""
predictions, targets = [], []
for img_name, img in dataset.images.items():
predictions_batch = callback(img)
predictions.append(predictions_batch)
targets_batch = dataset.annotations[img_name]
targets.append(targets_batch)
return cls.from_detections(
predictions=predictions,
targets=targets,
classes=dataset.classes,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
)
def plot(
self,
save_path: Optional[str] = None,
title: Optional[str] = None,
classes: Optional[List[str]] = None,
normalize: bool = False,
fig_size: Tuple[int, int] = (12, 10),
) -> matplotlib.figure.Figure:
"""
Create confusion matrix plot and save it at selected location.
Args:
save_path (Optional[str]): Path to save the plot. If not provided,
plot will be displayed.
title (Optional[str]): Title of the plot.
classes (Optional[List[str]]): List of classes to be displayed on the plot.
If not provided, all classes will be displayed.
normalize (bool): If True, normalize the confusion matrix.
fig_size (Tuple[int, int]): Size of the plot.
Returns:
matplotlib.figure.Figure: Confusion matrix plot.
"""
array = self.matrix.copy()
if normalize:
eps = 1e-8
array = array / (array.sum(0).reshape(1, -1) + eps)
array[array < 0.005] = np.nan
fig, ax = plt.subplots(figsize=fig_size, tight_layout=True, facecolor="white")
class_names = classes if classes is not None else self.classes
use_labels_for_ticks = class_names is not None and (0 < len(class_names) < 99)
if use_labels_for_ticks:
x_tick_labels = class_names + ["FN"]
y_tick_labels = class_names + ["FP"]
num_ticks = len(x_tick_labels)
else:
x_tick_labels = None
y_tick_labels = None
num_ticks = len(array)
im = ax.imshow(array, cmap="Blues")
cbar = ax.figure.colorbar(im, ax=ax)
cbar.mappable.set_clim(vmin=0, vmax=np.nanmax(array))
if x_tick_labels is None:
tick_interval = 2
else:
tick_interval = 1
ax.set_xticks(np.arange(0, num_ticks, tick_interval), labels=x_tick_labels)
ax.set_yticks(np.arange(0, num_ticks, tick_interval), labels=y_tick_labels)
plt.setp(ax.get_xticklabels(), rotation=90, ha="right", rotation_mode="default")
labelsize = 10 if num_ticks < 50 else 8
ax.tick_params(axis="both", which="both", labelsize=labelsize)
if num_ticks < 30:
for i in range(array.shape[0]):
for j in range(array.shape[1]):
n_preds = array[i, j]
if not np.isnan(n_preds):
ax.text(
j,
i,
f"{n_preds:.2f}" if normalize else f"{n_preds:.0f}",
ha="center",
va="center",
color="black"
if n_preds < 0.5 * np.nanmax(array)
else "white",
)
if title:
ax.set_title(title, fontsize=20)
ax.set_xlabel("Predicted")
ax.set_ylabel("True")
ax.set_facecolor("white")
if save_path:
fig.savefig(
save_path, dpi=250, facecolor=fig.get_facecolor(), transparent=True
)
return fig
@dataclass(frozen=True)
class MeanAveragePrecision:
"""
Mean Average Precision for object detection tasks.
Attributes:
map50_95 (float): Mean Average Precision (mAP) calculated over IoU thresholds
ranging from `0.50` to `0.95` with a step size of `0.05`.
map50 (float): Mean Average Precision (mAP) calculated specifically at
an IoU threshold of `0.50`.
map75 (float): Mean Average Precision (mAP) calculated specifically at
an IoU threshold of `0.75`.
per_class_ap50_95 (np.ndarray): Average Precision (AP) values calculated over
IoU thresholds ranging from `0.50` to `0.95` with a step size of `0.05`,
provided for each individual class.
"""
map50_95: float
map50: float
map75: float
per_class_ap50_95: np.ndarray
@classmethod
def from_detections(
cls,
predictions: List[Detections],
targets: List[Detections],
) -> MeanAveragePrecision:
"""
Calculate mean average precision based on predicted and ground-truth detections.
Args:
targets (List[Detections]): Detections objects from ground-truth.
predictions (List[Detections]): Detections objects predicted by the model.
Returns:
MeanAveragePrecision: New instance of ConfusionMatrix.
Example:
```python
>>> import supervision as sv
>>> targets = [
... sv.Detections(...),
... sv.Detections(...)
... ]
>>> predictions = [
... sv.Detections(...),
... sv.Detections(...)
... ]
>>> mean_average_precision = sv.MeanAveragePrecision.from_detections(
... predictions=predictions,
... targets=target,
... )
>>> mean_average_precison.map50_95
0.2899
```
"""
prediction_tensors = []
target_tensors = []
for prediction, target in zip(predictions, targets):
prediction_tensors.append(
detections_to_tensor(prediction, with_confidence=True)
)
target_tensors.append(detections_to_tensor(target, with_confidence=False))
return cls.from_tensors(
predictions=prediction_tensors,
targets=target_tensors,
)
@classmethod
def benchmark(
cls,
dataset: DetectionDataset,
callback: Callable[[np.ndarray], Detections],
) -> MeanAveragePrecision:
"""
Calculate mean average precision from dataset and callback function.
Args:
dataset (DetectionDataset): Object detection dataset used for evaluation.
callback (Callable[[np.ndarray], Detections]): Function that takes
an image as input and returns Detections object.
Returns:
MeanAveragePrecision: New instance of MeanAveragePrecision.
Example:
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> dataset = sv.DetectionDataset.from_yolo(...)
>>> model = YOLO(...)
>>> def callback(image: np.ndarray) -> sv.Detections:
... result = model(image)[0]
... return sv.Detections.from_ultralytics(result)
>>> mean_average_precision = sv.MeanAveragePrecision.benchmark(
... dataset = dataset,
... callback = callback
... )
>>> mean_average_precision.map50_95
0.433
```
"""
predictions, targets = [], []
for img_name, img in dataset.images.items():
predictions_batch = callback(img)
predictions.append(predictions_batch)
targets_batch = dataset.annotations[img_name]
targets.append(targets_batch)
return cls.from_detections(
predictions=predictions,
targets=targets,
)
@classmethod
def from_tensors(
cls,
predictions: List[np.ndarray],
targets: List[np.ndarray],
) -> MeanAveragePrecision:
"""
Calculate Mean Average Precision based on predicted and ground-truth
detections at different threshold.
Args:
predictions (List[np.ndarray]): Each element of the list describes
a single image and has `shape = (M, 6)` where `M` is
the number of detected objects. Each row is expected to be
in `(x_min, y_min, x_max, y_max, class, conf)` format.
targets (List[np.ndarray]): Each element of the list describes a single
image and has `shape = (N, 5)` where `N` is the
number of ground-truth objects. Each row is expected to be in
`(x_min, y_min, x_max, y_max, class)` format.
Returns:
MeanAveragePrecision: New instance of MeanAveragePrecision.
Example:
```python
>>> import supervision as sv
>>> targets = (
... [
... array(
... [
... [0.0, 0.0, 3.0, 3.0, 1],
... [2.0, 2.0, 5.0, 5.0, 1],
... [6.0, 1.0, 8.0, 3.0, 2],
... ]
... ),
... array([1.0, 1.0, 2.0, 2.0, 2]),
... ]
... )
>>> predictions = [
... array(
... [
... [0.0, 0.0, 3.0, 3.0, 1, 0.9],
... [0.1, 0.1, 3.0, 3.0, 0, 0.9],
... [6.0, 1.0, 8.0, 3.0, 1, 0.8],
... [1.0, 6.0, 2.0, 7.0, 1, 0.8],
... ]
... ),
... array([[1.0, 1.0, 2.0, 2.0, 2, 0.8]])
... ]
>>> mean_average_precison = sv.MeanAveragePrecision.from_tensors(
... predictions=predictions,
... targets=targets,
... )
>>> mean_average_precison.map50_95
0.2899
```
"""
validate_input_tensors(predictions, targets)
iou_thresholds = np.linspace(0.5, 0.95, 10)
stats = []
# Gather matching stats for predictions and targets
for true_objs, predicted_objs in zip(targets, predictions):
if predicted_objs.shape[0] == 0:
if true_objs.shape[0]:
stats.append(
(
np.zeros((0, iou_thresholds.size), dtype=bool),
*np.zeros((2, 0)),
true_objs[:, 4],
)
)
continue
if true_objs.shape[0]:
matches = cls._match_detection_batch(
predicted_objs, true_objs, iou_thresholds
)
stats.append(
(
matches,
predicted_objs[:, 5],
predicted_objs[:, 4],
true_objs[:, 4],
)
)
# Compute average precisions if any matches exist
if stats:
concatenated_stats = [np.concatenate(items, 0) for items in zip(*stats)]
average_precisions = cls._average_precisions_per_class(*concatenated_stats)
map50 = average_precisions[:, 0].mean()
map75 = average_precisions[:, 5].mean()
map50_95 = average_precisions.mean()
else:
map50, map75, map50_95 = 0, 0, 0
average_precisions = []
return cls(
map50_95=map50_95,
map50=map50,
map75=map75,
per_class_ap50_95=average_precisions,
)
@staticmethod
def compute_average_precision(recall: np.ndarray, precision: np.ndarray) -> float:
"""
Compute the average precision using 101-point interpolation (COCO), given
the recall and precision curves.
Args:
recall (np.ndarray): The recall curve.
precision (np.ndarray): The precision curve.
Returns:
float: Average precision.
"""
extended_recall = np.concatenate(([0.0], recall, [1.0]))
extended_precision = np.concatenate(([1.0], precision, [0.0]))
max_accumulated_precision = np.flip(
np.maximum.accumulate(np.flip(extended_precision))
)
interpolated_recall_levels = np.linspace(0, 1, 101)
interpolated_precision = np.interp(
interpolated_recall_levels, extended_recall, max_accumulated_precision
)
average_precision = np.trapz(interpolated_precision, interpolated_recall_levels)
return average_precision
@staticmethod
def _match_detection_batch(
predictions: np.ndarray, targets: np.ndarray, iou_thresholds: np.ndarray
) -> np.ndarray:
"""
Match predictions with target labels based on IoU levels.
Args:
predictions (np.ndarray): Batch prediction. Describes a single image and
has `shape = (M, 6)` where `M` is the number of detected objects.
Each row is expected to be in
`(x_min, y_min, x_max, y_max, class, conf)` format.
targets (np.ndarray): Batch target labels. Describes a single image and
has `shape = (N, 5)` where `N` is the number of ground-truth objects.
Each row is expected to be in
`(x_min, y_min, x_max, y_max, class)` format.
iou_thresholds (np.ndarray): Array contains different IoU thresholds.
Returns:
np.ndarray: Matched prediction with target labels result.
"""
num_predictions, num_iou_levels = predictions.shape[0], iou_thresholds.shape[0]
correct = np.zeros((num_predictions, num_iou_levels), dtype=bool)
iou = box_iou_batch(targets[:, :4], predictions[:, :4])
correct_class = targets[:, 4:5] == predictions[:, 4]
for i, iou_level in enumerate(iou_thresholds):
matched_indices = np.where((iou >= iou_level) & correct_class)
if matched_indices[0].shape[0]:
combined_indices = np.stack(matched_indices, axis=1)
iou_values = iou[matched_indices][:, None]
matches = np.hstack([combined_indices, iou_values])
if matched_indices[0].shape[0] > 1:
matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
correct[matches[:, 1].astype(int), i] = True
return correct
@staticmethod
def _average_precisions_per_class(
matches: np.ndarray,
prediction_confidence: np.ndarray,
prediction_class_ids: np.ndarray,
true_class_ids: np.ndarray,
eps: float = 1e-16,
) -> np.ndarray:
"""
Compute the average precision, given the recall and precision curves.
Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
Args:
matches (np.ndarray): True positives.
prediction_confidence (np.ndarray): Objectness value from 0-1.
prediction_class_ids (np.ndarray): Predicted object classes.
true_class_ids (np.ndarray): True object classes.
eps (float, optional): Small value to prevent division by zero.
Returns:
np.ndarray: Average precision for different IoU levels.
"""
sorted_indices = np.argsort(-prediction_confidence)
matches = matches[sorted_indices]
prediction_class_ids = prediction_class_ids[sorted_indices]
unique_classes, class_counts = np.unique(true_class_ids, return_counts=True)
num_classes = unique_classes.shape[0]
average_precisions = np.zeros((num_classes, matches.shape[1]))
for class_idx, class_id in enumerate(unique_classes):
is_class = prediction_class_ids == class_id
total_true = class_counts[class_idx]
total_prediction = is_class.sum()
if total_prediction == 0 or total_true == 0:
continue
false_positives = (1 - matches[is_class]).cumsum(0)
true_positives = matches[is_class].cumsum(0)
recall = true_positives / (total_true + eps)
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]
)
return average_precisions
View File
@@ -0,0 +1,55 @@
from collections import OrderedDict
from enum import Enum
import numpy as np
class TrackState(Enum):
New = 0
Tracked = 1
Lost = 2
Removed = 3
class BaseTrack:
_count = 0
def __init__(self):
self.track_id = 0
self.is_activated = False
self.state = TrackState.New
self.history = OrderedDict()
self.features = []
self.curr_feature = None
self.score = 0
self.start_frame = 0
self.frame_id = 0
self.time_since_update = 0
# multi-camera
self.location = (np.inf, np.inf)
@property
def end_frame(self) -> int:
return self.frame_id
@staticmethod
def next_id() -> int:
BaseTrack._count += 1
return BaseTrack._count
def activate(self, *args):
raise NotImplementedError
def predict(self):
raise NotImplementedError
def update(self, *args, **kwargs):
raise NotImplementedError
def mark_lost(self):
self.state = TrackState.Lost
def mark_removed(self):
self.state = TrackState.Removed
+477
View File
@@ -0,0 +1,477 @@
from typing import List, Tuple
import numpy as np
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
class STrack(BaseTrack):
shared_kalman = KalmanFilter()
def __init__(self, tlwh, score, class_ids):
# wait activate
self._tlwh = np.asarray(tlwh, dtype=np.float32)
self.kalman_filter = None
self.mean, self.covariance = None, None
self.is_activated = False
self.score = score
self.class_ids = class_ids
self.tracklet_len = 0
def predict(self):
mean_state = self.mean.copy()
if self.state != TrackState.Tracked:
mean_state[7] = 0
self.mean, self.covariance = self.kalman_filter.predict(
mean_state, self.covariance
)
@staticmethod
def multi_predict(stracks):
if len(stracks) > 0:
multi_mean = []
multi_covariance = []
for i, st in enumerate(stracks):
multi_mean.append(st.mean.copy())
multi_covariance.append(st.covariance)
if st.state != TrackState.Tracked:
multi_mean[i][7] = 0
multi_mean, multi_covariance = STrack.shared_kalman.multi_predict(
np.asarray(multi_mean), np.asarray(multi_covariance)
)
for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
stracks[i].mean = mean
stracks[i].covariance = cov
def activate(self, kalman_filter, frame_id):
"""Start a new tracklet"""
self.kalman_filter = kalman_filter
self.track_id = self.next_id()
self.mean, self.covariance = self.kalman_filter.initiate(
self.tlwh_to_xyah(self._tlwh)
)
self.tracklet_len = 0
self.state = TrackState.Tracked
if frame_id == 1:
self.is_activated = True
self.frame_id = frame_id
self.start_frame = frame_id
def re_activate(self, new_track, frame_id, new_id=False):
self.mean, self.covariance = self.kalman_filter.update(
self.mean, self.covariance, self.tlwh_to_xyah(new_track.tlwh)
)
self.tracklet_len = 0
self.state = TrackState.Tracked
self.is_activated = True
self.frame_id = frame_id
if new_id:
self.track_id = self.next_id()
self.score = new_track.score
def update(self, new_track, frame_id):
"""
Update a matched track
:type new_track: STrack
:type frame_id: int
:type update_feature: bool
:return:
"""
self.frame_id = frame_id
self.tracklet_len += 1
new_tlwh = new_track.tlwh
self.mean, self.covariance = self.kalman_filter.update(
self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh)
)
self.state = TrackState.Tracked
self.is_activated = True
self.score = new_track.score
@property
def tlwh(self):
"""Get current position in bounding box format `(top left x, top left y,
width, height)`.
"""
if self.mean is None:
return self._tlwh.copy()
ret = self.mean[:4].copy()
ret[2] *= ret[3]
ret[:2] -= ret[2:] / 2
return ret
@property
def tlbr(self):
"""Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
`(top left, bottom right)`.
"""
ret = self.tlwh.copy()
ret[2:] += ret[:2]
return ret
@staticmethod
def tlwh_to_xyah(tlwh):
"""Convert bounding box to format `(center x, center y, aspect ratio,
height)`, where the aspect ratio is `width / height`.
"""
ret = np.asarray(tlwh).copy()
ret[:2] += ret[2:] / 2
ret[2] /= ret[3]
return ret
def to_xyah(self):
return self.tlwh_to_xyah(self.tlwh)
@staticmethod
def tlbr_to_tlwh(tlbr):
ret = np.asarray(tlbr).copy()
ret[2:] -= ret[:2]
return ret
@staticmethod
def tlwh_to_tlbr(tlwh):
ret = np.asarray(tlwh).copy()
ret[2:] += ret[:2]
return ret
def __repr__(self):
return "OT_{}_({}-{})".format(self.track_id, self.start_frame, self.end_frame)
def detections2boxes(detections: Detections) -> np.ndarray:
"""
Convert Supervision Detections to numpy tensors for further computation.
Args:
detections (Detections): Detections/Targets in the format of sv.Detections.
Returns:
(np.ndarray): Detections as numpy tensors as in
`(x_min, y_min, x_max, y_max, confidence, class_id)` order.
"""
return np.hstack(
(
detections.xyxy,
detections.confidence[:, np.newaxis],
detections.class_id[:, np.newaxis],
)
)
class ByteTrack:
"""
Initialize the ByteTrack object.
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.
frame_rate (int, optional): The frame rate of the video.
"""
def __init__(
self,
track_thresh: float = 0.25,
track_buffer: int = 30,
match_thresh: float = 0.8,
frame_rate: int = 30,
):
self.track_thresh = track_thresh
self.match_thresh = match_thresh
self.frame_id = 0
self.det_thresh = self.track_thresh + 0.1
self.max_time_lost = int(frame_rate / 30.0 * track_buffer)
self.kalman_filter = KalmanFilter()
self.tracked_tracks: List[STrack] = []
self.lost_tracks: List[STrack] = []
self.removed_tracks: List[STrack] = []
def update_with_detections(self, detections: Detections) -> Detections:
"""
Updates the tracker with the provided detections and
returns the updated detection results.
Parameters:
detections: The new detections to update with.
Returns:
Detection: The updated detection results that now include tracking IDs.
Example:
```python
>>> import supervision as sv
>>> from ultralytics import YOLO
>>> model = YOLO(...)
>>> byte_tracker = sv.ByteTrack()
>>> annotator = sv.BoxAnnotator()
>>> def callback(frame: np.ndarray, index: int) -> np.ndarray:
... results = model(frame)[0]
... detections = sv.Detections.from_ultralytics(results)
... detections = byte_tracker.update_with_detections(detections)
... labels = [
... f"#{tracker_id} {model.model.names[class_id]} {confidence:0.2f}"
... for _, _, confidence, class_id, tracker_id
... in detections
... ]
... return annotator.annotate(scene=frame.copy(),
... detections=detections, labels=labels)
>>> sv.process_video(
... source_path='...',
... target_path='...',
... callback=callback
... )
```
"""
tracks = self.update_with_tensors(
tensors=detections2boxes(detections=detections)
)
detections = Detections.empty()
if len(tracks) > 0:
detections.xyxy = np.array(
[track.tlbr for track in tracks], dtype=np.float32
)
detections.class_id = np.array(
[int(t.class_ids) for t in tracks], dtype=int
)
detections.tracker_id = np.array(
[int(t.track_id) for t in tracks], dtype=int
)
detections.confidence = np.array(
[t.score for t in tracks], dtype=np.float32
)
else:
detections.tracker_id = np.array([], dtype=int)
return detections
def update_with_tensors(self, tensors: np.ndarray) -> List[STrack]:
"""
Updates the tracker with the provided tensors and returns the updated tracks.
Parameters:
tensors: The new tensors to update with.
Returns:
List[STrack]: Updated tracks.
"""
self.frame_id += 1
activated_starcks = []
refind_stracks = []
lost_stracks = []
removed_stracks = []
class_ids = tensors[:, 5]
scores = tensors[:, 4]
bboxes = tensors[:, :4]
remain_inds = scores > self.track_thresh
inds_low = scores > 0.1
inds_high = scores < self.track_thresh
inds_second = np.logical_and(inds_low, inds_high)
dets_second = bboxes[inds_second]
dets = bboxes[remain_inds]
scores_keep = scores[remain_inds]
scores_second = scores[inds_second]
class_ids_keep = class_ids[remain_inds]
class_ids_second = class_ids[inds_second]
if len(dets) > 0:
"""Detections"""
detections = [
STrack(STrack.tlbr_to_tlwh(tlbr), s, c)
for (tlbr, s, c) in zip(dets, scores_keep, class_ids_keep)
]
else:
detections = []
""" Add newly detected tracklets to tracked_stracks"""
unconfirmed = []
tracked_stracks = [] # type: list[STrack]
for track in self.tracked_tracks:
if not track.is_activated:
unconfirmed.append(track)
else:
tracked_stracks.append(track)
""" Step 2: First association, with high score detection boxes"""
strack_pool = joint_tracks(tracked_stracks, self.lost_tracks)
# Predict the current location with KF
STrack.multi_predict(strack_pool)
dists = matching.iou_distance(strack_pool, detections)
dists = matching.fuse_score(dists, detections)
matches, u_track, u_detection = matching.linear_assignment(
dists, thresh=self.match_thresh
)
for itracked, idet in matches:
track = strack_pool[itracked]
det = detections[idet]
if track.state == TrackState.Tracked:
track.update(detections[idet], self.frame_id)
activated_starcks.append(track)
else:
track.re_activate(det, self.frame_id, new_id=False)
refind_stracks.append(track)
""" Step 3: Second association, with low score detection boxes"""
# association the untrack to the low score detections
if len(dets_second) > 0:
"""Detections"""
detections_second = [
STrack(STrack.tlbr_to_tlwh(tlbr), s, c)
for (tlbr, s, c) in zip(dets_second, scores_second, class_ids_second)
]
else:
detections_second = []
r_tracked_stracks = [
strack_pool[i]
for i in u_track
if strack_pool[i].state == TrackState.Tracked
]
dists = matching.iou_distance(r_tracked_stracks, detections_second)
matches, u_track, u_detection_second = matching.linear_assignment(
dists, thresh=0.5
)
for itracked, idet in matches:
track = r_tracked_stracks[itracked]
det = detections_second[idet]
if track.state == TrackState.Tracked:
track.update(det, self.frame_id)
activated_starcks.append(track)
else:
track.re_activate(det, self.frame_id, new_id=False)
refind_stracks.append(track)
for it in u_track:
track = r_tracked_stracks[it]
if not track.state == TrackState.Lost:
track.mark_lost()
lost_stracks.append(track)
"""Deal with unconfirmed tracks, usually tracks with only one beginning frame"""
detections = [detections[i] for i in u_detection]
dists = matching.iou_distance(unconfirmed, detections)
dists = matching.fuse_score(dists, detections)
matches, u_unconfirmed, u_detection = matching.linear_assignment(
dists, thresh=0.7
)
for itracked, idet in matches:
unconfirmed[itracked].update(detections[idet], self.frame_id)
activated_starcks.append(unconfirmed[itracked])
for it in u_unconfirmed:
track = unconfirmed[it]
track.mark_removed()
removed_stracks.append(track)
""" Step 4: Init new stracks"""
for inew in u_detection:
track = detections[inew]
if track.score < self.det_thresh:
continue
track.activate(self.kalman_filter, self.frame_id)
activated_starcks.append(track)
""" Step 5: Update state"""
for track in self.lost_tracks:
if self.frame_id - track.end_frame > self.max_time_lost:
track.mark_removed()
removed_stracks.append(track)
self.tracked_tracks = [
t for t in self.tracked_tracks if t.state == TrackState.Tracked
]
self.tracked_tracks = joint_tracks(self.tracked_tracks, activated_starcks)
self.tracked_tracks = joint_tracks(self.tracked_tracks, refind_stracks)
self.lost_tracks = sub_tracks(self.lost_tracks, self.tracked_tracks)
self.lost_tracks.extend(lost_stracks)
self.lost_tracks = sub_tracks(self.lost_tracks, self.removed_tracks)
self.removed_tracks.extend(removed_stracks)
self.tracked_tracks, self.lost_tracks = remove_duplicate_tracks(
self.tracked_tracks, self.lost_tracks
)
output_stracks = [track for track in self.tracked_tracks if track.is_activated]
return output_stracks
def joint_tracks(
track_list_a: List[STrack], track_list_b: List[STrack]
) -> List[STrack]:
"""
Joins two lists of tracks, ensuring that the resulting list does not
contain tracks with duplicate track_id values.
Parameters:
track_list_a: First list of tracks (with track_id attribute).
track_list_b: Second list of tracks (with track_id attribute).
Returns:
Combined list of tracks from track_list_a and track_list_b
without duplicate track_id values.
"""
seen_track_ids = set()
result = []
for track in track_list_a + track_list_b:
if track.track_id not in seen_track_ids:
seen_track_ids.add(track.track_id)
result.append(track)
return result
def sub_tracks(track_list_a: List, track_list_b: List) -> List[int]:
"""
Returns a list of tracks from track_list_a after removing any tracks
that share the same track_id with tracks in track_list_b.
Parameters:
track_list_a: List of tracks (with track_id attribute).
track_list_b: List of tracks (with track_id attribute) to
be subtracted from track_list_a.
Returns:
List of remaining tracks from track_list_a after subtraction.
"""
tracks = {track.track_id: track for track in track_list_a}
track_ids_b = {track.track_id for track in track_list_b}
for track_id in track_ids_b:
tracks.pop(track_id, None)
return list(tracks.values())
def remove_duplicate_tracks(tracks_a: List, tracks_b: List) -> Tuple[List, List]:
pairwise_distance = matching.iou_distance(tracks_a, tracks_b)
matching_pairs = np.where(pairwise_distance < 0.15)
duplicates_a, duplicates_b = set(), set()
for track_index_a, track_index_b in zip(*matching_pairs):
time_a = tracks_a[track_index_a].frame_id - tracks_a[track_index_a].start_frame
time_b = tracks_b[track_index_b].frame_id - tracks_b[track_index_b].start_frame
if time_a > time_b:
duplicates_b.add(track_index_b)
else:
duplicates_a.add(track_index_a)
result_a = [
track for index, track in enumerate(tracks_a) if index not in duplicates_a
]
result_b = [
track for index, track in enumerate(tracks_b) if index not in duplicates_b
]
return result_a, result_b
@@ -0,0 +1,205 @@
from typing import Tuple
import numpy as np
import scipy.linalg
class KalmanFilter:
"""
A simple Kalman filter for tracking bounding boxes in image space.
The 8-dimensional state space
x, y, a, h, vx, vy, va, vh
contains the bounding box center position (x, y), aspect ratio a, height h,
and their respective velocities.
Object motion follows a constant velocity model. The bounding box location
(x, y, a, h) is taken as direct observation of the state space (linear
observation model).
"""
def __init__(self):
ndim, dt = 4, 1.0
self._motion_mat = np.eye(2 * ndim, 2 * ndim)
for i in range(ndim):
self._motion_mat[i, ndim + i] = dt
self._update_mat = np.eye(ndim, 2 * ndim)
self._std_weight_position = 1.0 / 20
self._std_weight_velocity = 1.0 / 160
def initiate(self, measurement: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
"""
Create track from an unassociated measurement.
Args:
measurement (ndarray): Bounding box coordinates (x, y, a, h) with
center position (x, y), aspect ratio a, and height h.
Returns:
Tuple[ndarray, ndarray]: Returns the mean vector (8 dimensional) and
covariance matrix (8x8 dimensional) of the new track.
Unobserved velocities are initialized to 0 mean.
"""
mean_pos = measurement
mean_vel = np.zeros_like(mean_pos)
mean = np.r_[mean_pos, mean_vel]
std = [
2 * self._std_weight_position * measurement[3],
2 * self._std_weight_position * measurement[3],
1e-2,
2 * self._std_weight_position * measurement[3],
10 * self._std_weight_velocity * measurement[3],
10 * self._std_weight_velocity * measurement[3],
1e-5,
10 * self._std_weight_velocity * measurement[3],
]
covariance = np.diag(np.square(std))
return mean, covariance
def predict(
self, mean: np.ndarray, covariance: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""
Run Kalman filter prediction step.
Args:
mean (ndarray): The 8 dimensional mean vector of the object
state at the previous time step.
covariance (ndarray): The 8x8 dimensional covariance matrix of
the object state at the previous time step.
Returns:
Tuple[ndarray, ndarray]: Returns the mean vector and
covariance matrix of the predicted state.
Unobserved velocities are initialized to 0 mean.
"""
std_pos = [
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-2,
self._std_weight_position * mean[3],
]
std_vel = [
self._std_weight_velocity * mean[3],
self._std_weight_velocity * mean[3],
1e-5,
self._std_weight_velocity * mean[3],
]
motion_cov = np.diag(np.square(np.r_[std_pos, std_vel]))
mean = np.dot(mean, self._motion_mat.T)
covariance = (
np.linalg.multi_dot((self._motion_mat, covariance, self._motion_mat.T))
+ motion_cov
)
return mean, covariance
def project(
self, mean: np.ndarray, covariance: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""
Project state distribution to measurement space.
Args:
mean (ndarray): The state's mean vector (8 dimensional array).
covariance (ndarray): The state's covariance matrix (8x8 dimensional).
Returns:
Tuple[ndarray, ndarray]: Returns the projected mean and
covariance matrix of the given state estimate.
"""
std = [
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-1,
self._std_weight_position * mean[3],
]
innovation_cov = np.diag(np.square(std))
mean = np.dot(self._update_mat, mean)
covariance = np.linalg.multi_dot(
(self._update_mat, covariance, self._update_mat.T)
)
return mean, covariance + innovation_cov
def multi_predict(
self, mean: np.ndarray, covariance: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""
Run Kalman filter prediction step (Vectorized version).
Args:
mean (ndarray): The Nx8 dimensional mean matrix
of the object states at the previous time step.
covariance (ndarray): The Nx8x8 dimensional covariance matrices
of the object states at the previous time step.
Returns:
Tuple[ndarray, ndarray]: Returns the mean vector and
covariance matrix of the predicted state.
Unobserved velocities are initialized to 0 mean.
"""
std_pos = [
self._std_weight_position * mean[:, 3],
self._std_weight_position * mean[:, 3],
1e-2 * np.ones_like(mean[:, 3]),
self._std_weight_position * mean[:, 3],
]
std_vel = [
self._std_weight_velocity * mean[:, 3],
self._std_weight_velocity * mean[:, 3],
1e-5 * np.ones_like(mean[:, 3]),
self._std_weight_velocity * mean[:, 3],
]
sqr = np.square(np.r_[std_pos, std_vel]).T
motion_cov = []
for i in range(len(mean)):
motion_cov.append(np.diag(sqr[i]))
motion_cov = np.asarray(motion_cov)
mean = np.dot(mean, self._motion_mat.T)
left = np.dot(self._motion_mat, covariance).transpose((1, 0, 2))
covariance = np.dot(left, self._motion_mat.T) + motion_cov
return mean, covariance
def update(
self, mean: np.ndarray, covariance: np.ndarray, measurement: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""
Run Kalman filter correction step.
Args:
mean (ndarray): The predicted state's mean vector (8 dimensional).
covariance (ndarray): The state's covariance matrix (8x8 dimensional).
measurement (ndarray): The 4-dimensional measurement vector (x, y, a, h),
where (x, y) is the center position, a the aspect ratio,
and h the height of the bounding box.
Returns:
Tuple[ndarray, ndarray]: Returns the measurement-corrected
state distribution.
"""
projected_mean, projected_cov = self.project(mean, covariance)
chol_factor, lower = scipy.linalg.cho_factor(
projected_cov, lower=True, check_finite=False
)
kalman_gain = scipy.linalg.cho_solve(
(chol_factor, lower),
np.dot(covariance, self._update_mat.T).T,
check_finite=False,
).T
innovation = measurement - projected_mean
new_mean = mean + np.dot(innovation, kalman_gain.T)
new_covariance = covariance - np.linalg.multi_dot(
(kalman_gain, projected_cov, kalman_gain.T)
)
return new_mean, new_covariance
@@ -0,0 +1,64 @@
from typing import List, Tuple
import numpy as np
from scipy.optimize import linear_sum_assignment
from supervision.detection.utils import box_iou_batch
def indices_to_matches(
cost_matrix: np.ndarray, indices: np.ndarray, thresh: float
) -> Tuple[np.ndarray, tuple, tuple]:
matched_cost = cost_matrix[tuple(zip(*indices))]
matched_mask = matched_cost <= thresh
matches = indices[matched_mask]
unmatched_a = tuple(set(range(cost_matrix.shape[0])) - set(matches[:, 0]))
unmatched_b = tuple(set(range(cost_matrix.shape[1])) - set(matches[:, 1]))
return matches, unmatched_a, unmatched_b
def linear_assignment(
cost_matrix: np.ndarray, thresh: float
) -> [np.ndarray, Tuple[int], Tuple[int, int]]:
if cost_matrix.size == 0:
return (
np.empty((0, 2), dtype=int),
tuple(range(cost_matrix.shape[0])),
tuple(range(cost_matrix.shape[1])),
)
cost_matrix[cost_matrix > thresh] = thresh + 1e-4
row_ind, col_ind = linear_sum_assignment(cost_matrix)
indices = np.column_stack((row_ind, col_ind))
return indices_to_matches(cost_matrix, indices, thresh)
def iou_distance(atracks: List, btracks: List) -> np.ndarray:
if (len(atracks) > 0 and isinstance(atracks[0], np.ndarray)) or (
len(btracks) > 0 and isinstance(btracks[0], np.ndarray)
):
atlbrs = atracks
btlbrs = btracks
else:
atlbrs = [track.tlbr for track in atracks]
btlbrs = [track.tlbr for track in btracks]
_ious = np.zeros((len(atlbrs), len(btlbrs)), dtype=np.float32)
if _ious.size != 0:
_ious = box_iou_batch(np.asarray(atlbrs), np.asarray(btlbrs))
cost_matrix = 1 - _ious
return cost_matrix
def fuse_score(cost_matrix: np.ndarray, detections: List) -> np.ndarray:
if cost_matrix.size == 0:
return cost_matrix
iou_sim = 1 - cost_matrix
det_scores = np.array([det.score for det in detections])
det_scores = np.expand_dims(det_scores, axis=0).repeat(cost_matrix.shape[0], axis=0)
fuse_sim = iou_sim * det_scores
fuse_cost = 1 - fuse_sim
return fuse_cost
View File
+144
View File
@@ -0,0 +1,144 @@
import json
from pathlib import Path
from typing import List, Optional, Union
import numpy as np
import yaml
class NumpyJsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
if isinstance(obj, np.floating):
return float(obj)
if isinstance(obj, np.ndarray):
return obj.tolist()
return super(NumpyJsonEncoder, self).default(obj)
def list_files_with_extensions(
directory: Union[str, Path], extensions: Optional[List[str]] = None
) -> List[Path]:
"""
List files in a directory with specified extensions or
all files if no extensions are provided.
Args:
directory (Union[str, Path]): The directory path as a string or Path object.
extensions (Optional[List[str]]): A list of file extensions to filter.
Default is None, which lists all files.
Returns:
(List[Path]): A list of Path objects for the matching files.
Examples:
```python
>>> import supervision as sv
>>> # List all files in the directory
>>> files = sv.list_files_with_extensions(directory='my_directory')
>>> # List only files with '.txt' and '.md' extensions
>>> files = sv.list_files_with_extensions(
... directory='my_directory', extensions=['txt', 'md'])
```
"""
directory = Path(directory)
files_with_extensions = []
if extensions is not None:
for ext in extensions:
files_with_extensions.extend(directory.glob(f"*.{ext}"))
else:
files_with_extensions.extend(directory.glob("*"))
return files_with_extensions
def read_txt_file(file_path: str) -> List[str]:
"""
Read a text file and return a list of strings without newline characters.
Args:
file_path (str): The path to the text file.
Returns:
List[str]: A list of strings representing the lines in the text file.
"""
with open(file_path, "r") as file:
lines = file.readlines()
lines = [line.rstrip("\n") for line in lines]
return lines
def save_text_file(lines: List[str], file_path: str):
"""
Write a list of strings to a text file, each string on a new line.
Args:
lines (List[str]): The list of strings to be written to the file.
file_path (str): The path to the text file.
"""
with open(file_path, "w") as file:
for line in lines:
file.write(line + "\n")
def read_json_file(file_path: str) -> dict:
"""
Read a json file and return a dict.
Args:
file_path (str): The path to the json file.
Returns:
dict: A dict of annotations information
"""
with open(file_path, "r") as file:
data = json.load(file)
return data
def save_json_file(data: dict, file_path: str, indent: int = 3) -> None:
"""
Write a dict to a json file.
Args:
indent:
data (dict): dict with unique keys and value as pair.
file_path (str): The path to the json file.
"""
with open(file_path, "w") as fp:
json.dump(data, fp, cls=NumpyJsonEncoder, indent=indent)
def read_yaml_file(file_path: str) -> dict:
"""
Read a yaml file and return a dict.
Args:
file_path (str): The path to the yaml file.
Returns:
dict: A dict of content information
"""
with open(file_path, "r") as file:
data = yaml.safe_load(file)
return data
def save_yaml_file(data: dict, file_path: str) -> None:
"""
Save a dict to a json file.
Args:
indent:
data (dict): dict with unique keys and value as pair.
file_path (str): The path to the json file.
"""
with open(file_path, "w") as outfile:
yaml.dump(data, outfile, sort_keys=False, default_flow_style=None)
+101
View File
@@ -0,0 +1,101 @@
import os
import shutil
from typing import Optional
import cv2
import numpy as np
def crop_image(image: np.ndarray, xyxy: np.ndarray) -> np.ndarray:
"""
Crops the given image based on the given bounding box.
Args:
image (np.ndarray): The image to be cropped, represented as a numpy array.
xyxy (np.ndarray): A numpy array containing the bounding box coordinates
in the format (x1, y1, x2, y2).
Returns:
(np.ndarray): The cropped image as a numpy array.
Examples:
```python
>>> import supervision as sv
>>> detection = sv.Detections(...)
>>> with sv.ImageSink(target_dir_path='target/directory/path') as sink:
... for xyxy in detection.xyxy:
... cropped_image = sv.crop_image(image=image, xyxy=xyxy)
... sink.save_image(image=image)
```
"""
xyxy = np.round(xyxy).astype(int)
x1, y1, x2, y2 = xyxy
cropped_img = image[y1:y2, x1:x2]
return cropped_img
class ImageSink:
def __init__(
self,
target_dir_path: str,
overwrite: bool = False,
image_name_pattern: str = "image_{:05d}.png",
):
"""
Initialize a context manager for saving images.
Args:
target_dir_path (str): The target directory where images will be saved.
overwrite (bool, optional): Whether to overwrite the existing directory.
Defaults to False.
image_name_pattern (str, optional): The image file name pattern.
Defaults to "image_{:05d}.png".
Examples:
```python
>>> import supervision as sv
>>> with sv.ImageSink(target_dir_path='target/directory/path',
... overwrite=True) as sink:
... for image in sv.get_video_frames_generator(
... source_path='source_video.mp4', stride=2):
... sink.save_image(image=image)
```
"""
self.target_dir_path = target_dir_path
self.overwrite = overwrite
self.image_name_pattern = image_name_pattern
self.image_count = 0
def __enter__(self):
if os.path.exists(self.target_dir_path):
if self.overwrite:
shutil.rmtree(self.target_dir_path)
os.makedirs(self.target_dir_path)
else:
os.makedirs(self.target_dir_path)
return self
def save_image(self, image: np.ndarray, image_name: Optional[str] = None):
"""
Save a given image in the target directory.
Args:
image (np.ndarray): The image to be saved.
image_name (str, optional): The name to use for the saved image.
If not provided, a name will be
generated using the `image_name_pattern`.
"""
if image_name is None:
image_name = self.image_name_pattern.format(self.image_count)
image_path = os.path.join(self.target_dir_path, image_name)
cv2.imwrite(image_path, image)
self.image_count += 1
def __exit__(self, exc_type, exc_value, exc_traceback):
pass
+18
View File
@@ -0,0 +1,18 @@
import functools
import warnings
def deprecated(reason: str):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated: {reason}",
category=DeprecationWarning,
stacklevel=2,
)
return func(*args, **kwargs)
return wrapper
return decorator
@@ -24,7 +24,7 @@ def plot_image(
>>> image = cv2.imread("path/to/image.jpg")
%matplotlib inline
>>> sv.plot_image(image, (16, 16))
>>> sv.plot_image(image=image, size=(16, 16))
```
"""
plt.figure(figsize=size)
@@ -50,9 +50,12 @@ def plot_images_grid(
Args:
images (List[np.ndarray]): A list of images as numpy arrays.
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. Defaults to None.
size (Tuple[int, int]): A tuple specifying the width and height of the entire plot in inches.
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.
Defaults to None.
size (Tuple[int, int]): A tuple specifying the width and
height of the entire plot in inches.
cmap (str): the colormap to use for single channel images.
Raises:
@@ -71,14 +74,15 @@ def plot_images_grid(
>>> titles = ["Image 1", "Image 2", "Image 3"]
%matplotlib inline
>>> plot_images_grid(images, grid_size=(2, 2), titles=titles, figsize=(16, 16))
>>> plot_images_grid(images, grid_size=(2, 2), titles=titles, size=(16, 16))
```
"""
nrows, ncols = grid_size
if len(images) > nrows * ncols:
raise ValueError(
"The number of images exceeds the grid size. Please increase the grid size or reduce the number of images."
"The number of images exceeds the grid size. Please increase the grid size"
" or reduce the number of images."
)
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=size)
+256
View File
@@ -0,0 +1,256 @@
from __future__ import annotations
import time
from collections import deque
from dataclasses import dataclass
from typing import Callable, Generator, Optional, Tuple
import cv2
import numpy as np
@dataclass
class VideoInfo:
"""
A class to store video information, including width, height, fps and
total number of frames.
Attributes:
width (int): width of the video in pixels
height (int): height of the video in pixels
fps (int): frames per second of the video
total_frames (int, optional): total number of frames in the video,
default is None
Examples:
```python
>>> import supervision as sv
>>> video_info = sv.VideoInfo.from_video_path(video_path='video.mp4')
>>> video_info
VideoInfo(width=3840, height=2160, fps=25, total_frames=538)
>>> video_info.resolution_wh
(3840, 2160)
```
"""
width: int
height: int
fps: int
total_frames: Optional[int] = None
@classmethod
def from_video_path(cls, video_path: str) -> VideoInfo:
video = cv2.VideoCapture(video_path)
if not video.isOpened():
raise Exception(f"Could not open video at {video_path}")
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(video.get(cv2.CAP_PROP_FPS))
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
video.release()
return VideoInfo(width, height, fps, total_frames)
@property
def resolution_wh(self) -> Tuple[int, int]:
return self.width, self.height
class VideoSink:
"""
Context manager that saves video frames to a file using OpenCV.
Attributes:
target_path (str): The path to the output file where the video will be saved.
video_info (VideoInfo): Information about the video resolution, fps,
and total frame count.
codec (str): FOURCC code for video format
Example:
```python
>>> import supervision as sv
>>> video_info = sv.VideoInfo.from_video_path('source.mp4')
>>> frames_generator = get_video_frames_generator('source.mp4')
>>> with sv.VideoSink(target_path='target.mp4', video_info=video_info) as sink:
... for frame in frames_generator:
... sink.write_frame(frame=frame)
```
"""
def __init__(self, target_path: str, video_info: VideoInfo, codec: str = "mp4v"):
self.target_path = target_path
self.video_info = video_info
self.__codec = codec
self.__writer = None
def __enter__(self):
try:
self.__fourcc = cv2.VideoWriter_fourcc(*self.__codec)
except TypeError as e:
print(str(e) + ". Defaulting to mp4v...")
self.__fourcc = cv2.VideoWriter_fourcc(*"mp4v")
self.__writer = cv2.VideoWriter(
self.target_path,
self.__fourcc,
self.video_info.fps,
self.video_info.resolution_wh,
)
return self
def write_frame(self, frame: np.ndarray):
self.__writer.write(frame)
def __exit__(self, exc_type, exc_value, exc_traceback):
self.__writer.release()
def _validate_and_setup_video(source_path: str, start: int, end: Optional[int]):
video = cv2.VideoCapture(source_path)
if not video.isOpened():
raise Exception(f"Could not open video at {source_path}")
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
if end is not None and end > total_frames:
raise Exception("Requested frames are outbound")
start = max(start, 0)
end = min(end, total_frames) if end is not None else total_frames
video.set(cv2.CAP_PROP_POS_FRAMES, start)
return video, start, end
def get_video_frames_generator(
source_path: str, stride: int = 1, start: int = 0, end: Optional[int] = None
) -> Generator[np.ndarray, None, None]:
"""
Get a generator that yields the frames of the video.
Args:
source_path (str): The path of the video file.
stride (int): Indicates the interval at which frames are returned,
skipping stride - 1 frames between each.
start (int): Indicates the starting position from which
video should generate frames
end (Optional[int]): Indicates the ending position at which video
should stop generating frames. If None, video will be read to the end.
Returns:
(Generator[np.ndarray, None, None]): A generator that yields the
frames of the video.
Examples:
```python
>>> import supervision as sv
>>> for frame in sv.get_video_frames_generator(source_path='source_video.mp4'):
... ...
```
"""
video, start, end = _validate_and_setup_video(source_path, start, end)
frame_position = start
while True:
success, frame = video.read()
if not success or frame_position >= end:
break
yield frame
for _ in range(stride - 1):
success = video.grab()
if not success:
break
frame_position += stride
video.release()
def process_video(
source_path: str,
target_path: str,
callback: Callable[[np.ndarray, int], np.ndarray],
) -> None:
"""
Process a video file by applying a callback function on each frame
and saving the result to a target video file.
Args:
source_path (str): The path to the source video file.
target_path (str): The path to the target video file.
callback (Callable[[np.ndarray, int], np.ndarray]): A function that takes in
a numpy ndarray representation of a video frame and an
int index of the frame and returns a processed numpy ndarray
representation of the frame.
Examples:
```python
>>> import supervision as sv
>>> def callback(scene: np.ndarray, index: int) -> np.ndarray:
... ...
>>> process_video(
... source_path='...',
... target_path='...',
... callback=callback
... )
```
"""
source_video_info = VideoInfo.from_video_path(video_path=source_path)
with VideoSink(target_path=target_path, video_info=source_video_info) as sink:
for index, frame in enumerate(
get_video_frames_generator(source_path=source_path)
):
result_frame = callback(frame, index)
sink.write_frame(frame=result_frame)
class FPSMonitor:
"""
A class for monitoring frames per second (FPS) to benchmark latency.
"""
def __init__(self, sample_size: int = 30):
"""
Args:
sample_size (int): The maximum number of observations for latency
benchmarking.
Examples:
```python
>>> import supervision as sv
>>> frames_generator = sv.get_video_frames_generator('source.mp4')
>>> fps_monitor = sv.FPSMonitor()
>>> for frame in frames_generator:
... # your processing code here
... fps_monitor.tick()
... fps = fps_monitor()
```
"""
self.all_timestamps = deque(maxlen=sample_size)
def __call__(self) -> float:
"""
Computes and returns the average FPS based on the stored time stamps.
Returns:
float: The average FPS. Returns 0.0 if no time stamps are stored.
"""
if not self.all_timestamps:
return 0.0
taken_time = self.all_timestamps[-1] - self.all_timestamps[0]
return (len(self.all_timestamps)) / taken_time if taken_time != 0 else 0.0
def tick(self) -> None:
"""
Adds a new time stamp to the deque for FPS calculation.
"""
self.all_timestamps.append(time.monotonic())
def reset(self) -> None:
"""
Clears all the time stamps from the deque.
"""
self.all_timestamps.clear()

Some files were not shown because too many files have changed in this diff Show More