Compare commits

...
35 Commits
Author SHA1 Message Date
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
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
Hardik Dava b474a47065 Adding yolov8 masks to detections 2023-05-17 18:38:40 +02:00
24 changed files with 664 additions and 163 deletions
+207 -73
View File
@@ -10,6 +10,213 @@
<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-ai/notebooks/blob/main/notebooks/how-to-detect-and-count-objects-in-polygon-zone.ipynb)
</div>
## 👋 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.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>
## 🔥 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_yolov8(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 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='...'
... )
```
- 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)
```
- Save object detection / instance segmentation datasets in one of 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='...'
... )
```
- Convert labels between suppoted formats
```python
>>> sv.DetectionDataset.from_yolo(
... images_directory_path='...',
... annotations_directory_path='...',
... data_yaml_path='...'
... ).as_pascal_voc(
... images_directory_path='...',
... annotations_directory_path='...'
... )
```
</details>
## 🎬 tutorials
<p align="left">
<a href="https://youtu.be/oEQYStnF2l8" title="Accelerate Image Annotation with SAM and Grounding DINO"><img src="https://github.com/SkalskiP/SkalskiP/assets/26109316/ae1ca38e-40b7-4b35-8582-e8ea5de3806e" alt="Accelerate Image Annotation with SAM and Grounding DINO" width="300px" align="left" /></a>
<a href="https://youtu.be/oEQYStnF2l8" title="Accelerate Image Annotation with SAM and Grounding DINO"><strong>Accelerate Image Annotation with SAM and Grounding DINO</strong></a>
<div><strong>Created: 20 Apr 2023</strong> | <strong>Updated: 20 Apr 2023</strong></div>
<br/> Discover how to speed up your image annotation process using Grounding DINO and Segment Anything Model (SAM). Learn how to convert object detection datasets into instance segmentation datasets, and see the potential of using these models to automatically annotate your datasets for real-time detectors like YOLOv8... </p>
<br/>
<p align="left">
<a href="https://youtu.be/oEQYStnF2l8" 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/oEQYStnF2l8" 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>
## 📚 documentation
Curious how Supervision can help you solve problems on your project? Visit our [documentation](https://roboflow.github.io/supervision) page!
## 🏆 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!
<br>
<div align="center">
<div align="center">
<a href="https://youtube.com/roboflow">
<img
@@ -54,77 +261,4 @@
</a>
</div>
<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-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!
+102 -3
View File
@@ -1,8 +1,107 @@
### 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.
```python
>>> import cv2
>>> from ultralytics import YOLO
>>> import supervision as sv
>>> image = cv2.imread(...)
>>> image.shape
(640, 640, 3)
>>> model = YOLO('yolov8s-seg.pt')
>>> result = model(image)[0]
>>> detections = sv.Detections.from_yolov8(result)
>>> detections.mask.shape
(2, 640, 640)
```
- 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`.
```python
>>> import cv2
>>> import supervision as sv
>>> image = cv2.imread(...)
>>> detections = sv.Detections(...)
>>> len(detections)
2
>>> crops = [
... sv.crop(image=image, xyxy=xyxy)
... for xyxy
... in detections.xyxy
... ]
>>> len(crops)
2
```
- 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. Current `Dataset` got renamed to `DetectionDataset` and make it inherit from `BaseDataset`.
- Added [[#100](https://github.com/roboflow/supervision/pull/100)]: ability to save datasets in YOLO format using `DetectionDataset.as_yolo`.
- Added [[#102](https://github.com/roboflow/supervision/pull/103)]: support for splitting `DetectionDataset`.
- 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>
+1 -1
View File
@@ -19,7 +19,7 @@ pre-release stage 🚧 Keep your eyes open for potential bugs and be aware that
## 💻 How to Install
You can install `supervision` with pip in a
[**3.10>=Python>=3.7**](https://www.python.org/) environment.
[**3.11>=Python>=3.7**](https://www.python.org/) environment.
!!! example "Pip install method (recommended)"
-7
View File
@@ -1,7 +0,0 @@
## plot_image
:::supervision.notebook.utils.plot_image
## plot_images_grid
:::supervision.notebook.utils.plot_images_grid
+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
+7
View File
@@ -0,0 +1,7 @@
## plot_image
:::supervision.utils.notebook.plot_image
## plot_images_grid
:::supervision.utils.notebook.plot_images_grid
+15
View File
@@ -0,0 +1,15 @@
## VideoInfo
:::supervision.utils.video.VideoInfo
## VideoSink
:::supervision.utils.video.VideoSink
## 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
+5 -4
View File
@@ -39,10 +39,11 @@ nav:
- Core: dataset/core.md
- Draw:
- Utils: draw/utils.md
- Notebook:
- Utils: notebook/utils.md
- Video:
- Utils: video.md
- Utils:
- Video: utils/video.md
- Image: utils/image.md
- Notebook: utils/notebook.md
- File: utils/file.md
- Changelog: changelog.md
theme:
+1
View File
@@ -52,6 +52,7 @@ setuptools.setup(
'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',
+5 -4
View File
@@ -1,4 +1,4 @@
__version__ = "0.8.0"
__version__ = "0.9.0"
from supervision.dataset.core import BaseDataset, DetectionDataset
from supervision.detection.annotate import BoxAnnotator, MaskAnnotator
@@ -16,11 +16,12 @@ from supervision.detection.utils import (
)
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import draw_filled_rectangle, draw_polygon, draw_text
from supervision.file import list_files_with_extensions
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.utils.file import list_files_with_extensions
from supervision.utils.image import ImageSink, crop
from supervision.utils.notebook import plot_image, plot_images_grid
from supervision.utils.video import (
VideoInfo,
VideoSink,
get_video_frames_generator,
+9 -9
View File
@@ -19,7 +19,7 @@ from supervision.dataset.formats.yolo import (
)
from supervision.dataset.ultils import save_dataset_images, train_test_split
from supervision.detection.core import Detections
from supervision.file import list_files_with_extensions
from supervision.utils.file import list_files_with_extensions
@dataclass
@@ -88,9 +88,9 @@ class DetectionDataset(BaseDataset):
```python
>>> import supervision as sv
>>> dataset = sv.DetectionDataset(...)
>>> train_dataset, test_dataset = dataset.split(split_ratio=0.7, random_state=42, shuffle=True)
>>> len(train_dataset), len(test_dataset)
>>> 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)
```
"""
@@ -195,12 +195,12 @@ class DetectionDataset(BaseDataset):
>>> project = rf.workspace(WORKSPACE_ID).project(PROJECT_ID)
>>> dataset = project.version(PROJECT_VERSION).download("voc")
>>> train_dataset = sv.Dataset.from_yolo(
>>> ds = sv.DetectionDataset.from_yolo(
... images_directory_path=f"{dataset.location}/train/images",
... annotations_directory_path=f"{dataset.location}/train/labels"
... )
>>> dataset.classes
>>> ds.classes
['dog', 'person']
```
"""
@@ -267,13 +267,13 @@ class DetectionDataset(BaseDataset):
>>> project = rf.workspace(WORKSPACE_ID).project(PROJECT_ID)
>>> dataset = project.version(PROJECT_VERSION).download("yolov5")
>>> train_dataset = sv.Dataset.from_yolo(
>>> 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"
... )
>>> dataset.classes
>>> ds.classes
['dog', 'person']
```
"""
@@ -295,7 +295,7 @@ class DetectionDataset(BaseDataset):
approximation_percentage: float = 0.0,
) -> None:
"""
Exports the dataset to YOLO (You Only Look Once) format. This method saves the images and their corresponding
Exports the dataset to YOLO format. This method saves the images and their corresponding
annotations in YOLO format, which is a simple text file that describes an object in the image. It also allows
for the optional saving of a data.yaml file, used in YOLOv5, that contains metadata about the dataset.
+5 -1
View File
@@ -9,7 +9,11 @@ import yaml
from supervision.dataset.ultils import approximate_mask_with_polygons
from supervision.detection.core import Detections
from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy
from supervision.file import list_files_with_extensions, read_txt_file, save_text_file
from supervision.utils.file import (
list_files_with_extensions,
read_txt_file,
save_text_file,
)
def _parse_box(values: List[str]) -> np.ndarray:
+46 -20
View File
@@ -1,13 +1,18 @@
from __future__ import annotations
from dataclasses import astuple, dataclass
from typing import Any, Iterator, List, Optional, Tuple
from typing import Any, Iterator, List, Optional, Tuple, Union
import cv2
import numpy as np
from supervision.detection.utils import non_max_suppression, xywh_to_xyxy
from supervision.detection.utils import (
extract_yolov8_masks,
non_max_suppression,
xywh_to_xyxy,
)
from supervision.geometry.core import Position
from supervision.internal import deprecated
from supervision.utils.internal import deprecated
def _validate_xyxy(xyxy: Any, n: int) -> None:
@@ -191,6 +196,7 @@ class Detections:
xyxy=yolov8_results.boxes.xyxy.cpu().numpy(),
confidence=yolov8_results.boxes.conf.cpu().numpy(),
class_id=yolov8_results.boxes.cls.cpu().numpy().astype(int),
mask=extract_yolov8_masks(yolov8_results),
)
@classmethod
@@ -469,23 +475,43 @@ class Detections:
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
+2 -2
View File
@@ -17,7 +17,7 @@ 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)
current_count (int): The current count of detected objects within the zone
@@ -30,7 +30,7 @@ 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
+34
View File
@@ -258,3 +258,37 @@ def approximate_polygon(
break
return np.squeeze(approximated_points, axis=1)
def extract_yolov8_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]
mask = cv2.resize(mask, (orig_shape[1], orig_shape[0]))
mask_maps.append(mask)
return np.asarray(mask_maps, dtype=bool)
@@ -17,13 +17,13 @@ def list_files_with_extensions(
Examples:
```python
>>> from supervision import list_files_with_extensions
>>> import supervision as sv
>>> # List all files in the directory
>>> files = list_files_with_extensions(directory='my_directory')
>>> files = sv.list_files_with_extensions(directory='my_directory')
>>> # List only files with '.txt' and '.md' extensions
>>> files = list_files_with_extensions(directory='my_directory', extensions=['txt', 'md'])
>>> files = sv.list_files_with_extensions(directory='my_directory', extensions=['txt', 'md'])
```
"""
directory = Path(directory)
+93
View File
@@ -0,0 +1,93 @@
import os
import shutil
from typing import Optional
import cv2
import numpy as np
def crop(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, 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
@@ -20,9 +20,9 @@ class VideoInfo:
Examples:
```python
>>> from supervision import VideoInfo
>>> import supervision as sv
>>> video_info = VideoInfo.from_video_path(video_path='video.mp4')
>>> video_info = sv.VideoInfo.from_video_path(video_path='video.mp4')
>>> video_info
VideoInfo(width=3840, height=2160, fps=25, total_frames=538)
@@ -63,15 +63,15 @@ class VideoSink:
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.
Examples:
Example:
```python
>>> from supervision import VideoInfo, VideoSink
>>> import supervision as sv
>>> video_info = VideoInfo.from_video_path(video_path='source_video.mp4')
>>> video_info = sv.VideoInfo.from_video_path(video_path='source_video.mp4')
>>> with VideoSink(target_path='target_video.mp4', video_info=video_info) as s:
... frame = ...
... s.write_frame(frame=frame)
>>> with sv.VideoSink(target_path='target_video.mp4', video_info=video_info) as sink:
... for frame in get_video_frames_generator(source_path='source_video.mp4', stride=2):
... sink.write_frame(frame=frame)
```
"""
@@ -93,35 +93,43 @@ class VideoSink:
def write_frame(self, frame: np.ndarray):
self.__writer.write(frame)
def __exit__(self, exc_type, exc_val, exc_tb):
def __exit__(self, exc_type, exc_value, exc_traceback):
self.__writer.release()
def get_video_frames_generator(source_path: str) -> Generator[np.ndarray, None, None]:
def get_video_frames_generator(
source_path: str, stride: int = 1
) -> 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): The number of frames to skip before returning the next one.
Returns:
(Generator[np.ndarray, None, None]): A generator that yields the frames of the video.
Examples:
```python
>>> from supervision import get_video_frames_generator
>>> import supervision as sv
>>> for frame in get_video_frames_generator(source_path='source_video.mp4'):
>>> for frame in sv.get_video_frames_generator(source_path='source_video.mp4', stride=2):
... ...
```
"""
video = cv2.VideoCapture(source_path)
if not video.isOpened():
raise Exception(f"Could not open video at {source_path}")
frame_count = 0
success, frame = video.read()
while success:
yield frame
if frame_count % stride == 0:
yield frame
success, frame = video.read()
frame_count += 1
video.release()
+98 -8
View File
@@ -28,13 +28,6 @@ DETECTIONS = Detections(
)
PREDICTIONS = np.array([
[ 2254, 906, 2447, 1353, 0.90538, 0],
[ 2049, 1133, 2226, 1371, 0.59002, 56],
[ 727, 1224, 838, 1601, 0.51119, 39],
], dtype=np.float32)
def mock_detections(xyxy, confidence = None, class_id = None, tracker_id = None) -> Detections:
return Detections(
xyxy = np.array(xyxy, dtype=np.float32),
@@ -101,11 +94,108 @@ def mock_detections(xyxy, confidence = None, class_id = None, tracker_id = None)
),
DoesNotRaise()
), # take no detections
(
DETECTIONS,
[0, 2],
Detections(
xyxy=np.array([
[ 2254, 906, 2447, 1353],
[ 727, 1224, 838, 1601]
], dtype=np.float32),
confidence=np.array([
0.90538,
0.51119
], dtype=np.float32),
class_id=np.array([
0,
39
], dtype=int)
),
DoesNotRaise()
), # take only first and third detection using List[int] index
(
DETECTIONS,
np.array([0, 2]),
Detections(
xyxy=np.array([
[ 2254, 906, 2447, 1353],
[ 727, 1224, 838, 1601]
], dtype=np.float32),
confidence=np.array([
0.90538,
0.51119
], dtype=np.float32),
class_id=np.array([
0,
39
], dtype=int)
),
DoesNotRaise()
), # take only first and third detection using np.ndarray index
(
DETECTIONS,
0,
Detections(
xyxy=np.array([
[ 2254, 906, 2447, 1353]
], dtype=np.float32),
confidence=np.array([
0.90538
], dtype=np.float32),
class_id=np.array([
0
], dtype=int)
),
DoesNotRaise()
), # take only first detection by index
(
DETECTIONS,
slice(1, 3),
Detections(
xyxy=np.array([
[ 2049, 1133, 2226, 1371],
[ 727, 1224, 838, 1601]
], dtype=np.float32),
confidence=np.array([
0.59002,
0.51119
], dtype=np.float32),
class_id=np.array([
56,
39
], dtype=int)
),
DoesNotRaise()
), # take only first detection by index slice (1, 3)
(
DETECTIONS,
10,
None,
pytest.raises(IndexError)
), # index out of range
(
DETECTIONS,
[0, 2, 10],
None,
pytest.raises(IndexError)
), # index out of range
(
DETECTIONS,
np.array([0, 2, 10]),
None,
pytest.raises(IndexError)
),
(
DETECTIONS,
np.array([True, True, True, True, True, True, True, True, True, True, True]),
None,
pytest.raises(IndexError)
)
]
)
def test_getitem(
detections: Detections,
index: Union[int, slice, np.ndarray],
index: Union[int, slice, List[int], np.ndarray],
expected_result: Optional[Detections],
exception: Exception
) -> None: