From ad0e6c3b51498beb25046b74704c86ac87128503 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Fri, 29 Mar 2024 16:59:29 -0400 Subject: [PATCH 001/136] Add minimum consecutive frames to ByteTrack --- supervision/tracker/byte_tracker/core.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index c77878cf..6475bf50 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -13,7 +13,7 @@ from supervision.utils.internal import deprecated_parameter class STrack(BaseTrack): shared_kalman = KalmanFilter() - def __init__(self, tlwh, score, class_ids): + def __init__(self, tlwh, score, class_ids, minimum_consecutive_frames): # wait activate self._tlwh = np.asarray(tlwh, dtype=np.float32) self.kalman_filter = None @@ -24,6 +24,8 @@ class STrack(BaseTrack): self.class_ids = class_ids self.tracklet_len = 0 + self.minimum_consecutive_frames = minimum_consecutive_frames + def predict(self): mean_state = self.mean.copy() if self.state != TrackState.Tracked: @@ -60,7 +62,7 @@ class STrack(BaseTrack): self.tracklet_len = 0 self.state = TrackState.Tracked - if frame_id == 1: + if frame_id == 1 and self.minimum_consecutive_frames == 1: self.is_activated = True self.frame_id = frame_id self.start_frame = frame_id @@ -71,7 +73,7 @@ class STrack(BaseTrack): ) 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() @@ -93,7 +95,8 @@ class STrack(BaseTrack): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - self.is_activated = True + if self.tracklet_len >= self.minimum_consecutive_frames: + self.is_activated = True self.score = new_track.score @@ -186,6 +189,10 @@ class ByteTrack: Increasing minimum_matching_threshold improves accuracy but risks fragmentation. Decreasing it improves completeness but risks false positives and drift. frame_rate (int, optional): The frame rate of the video. + minimum_consecutive_frames (int, optional): Number of consecutive frames that an object must + be tracked before it is considered a 'valid' track. + Increasing minimum_consecutive_frames prevents the creation of accidental tracks from + false detection or double detection, but risks missing shorter tracks. """ # noqa: E501 // docs @deprecated_parameter( @@ -218,6 +225,7 @@ class ByteTrack: lost_track_buffer: int = 30, minimum_matching_threshold: float = 0.8, frame_rate: int = 30, + minimum_consecutive_frames: int = 1, ): self.track_activation_threshold = track_activation_threshold self.minimum_matching_threshold = minimum_matching_threshold @@ -225,6 +233,7 @@ class ByteTrack: self.frame_id = 0 self.det_thresh = self.track_activation_threshold + 0.1 self.max_time_lost = int(frame_rate / 30.0 * lost_track_buffer) + self.minimum_consecutive_frames = minimum_consecutive_frames self.kalman_filter = KalmanFilter() self.tracked_tracks: List[STrack] = [] @@ -290,6 +299,7 @@ class ByteTrack: return detections[detections.tracker_id != -1] else: + detections = Detections.empty() detections.tracker_id = np.array([], dtype=int) return detections @@ -345,7 +355,7 @@ class ByteTrack: if len(dets) > 0: """Detections""" detections = [ - STrack(STrack.tlbr_to_tlwh(tlbr), s, c) + STrack(STrack.tlbr_to_tlwh(tlbr), s, c, self.minimum_consecutive_frames) for (tlbr, s, c) in zip(dets, scores_keep, class_ids_keep) ] else: @@ -387,7 +397,7 @@ class ByteTrack: if len(dets_second) > 0: """Detections""" detections_second = [ - STrack(STrack.tlbr_to_tlwh(tlbr), s, c) + STrack(STrack.tlbr_to_tlwh(tlbr), s, c, self.minimum_consecutive_frames) for (tlbr, s, c) in zip(dets_second, scores_second, class_ids_second) ] else: From 8fc541339440be84875a5cd24bfd19536cf3520e Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 1 Apr 2024 00:32:35 +0300 Subject: [PATCH 002/136] =?UTF-8?q?ci:=20=F0=9F=91=B7=20supervision=20docu?= =?UTF-8?q?mentation=20=F0=9F=93=9A=20release=20CI=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- .../{docs.yml => publish-dev-docs.yml} | 0 .github/workflows/publish-release-docs.yml | 55 +++++++++++++++++++ 2 files changed, 55 insertions(+) rename .github/workflows/{docs.yml => publish-dev-docs.yml} (100%) create mode 100644 .github/workflows/publish-release-docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/publish-dev-docs.yml similarity index 100% rename from .github/workflows/docs.yml rename to .github/workflows/publish-dev-docs.yml diff --git a/.github/workflows/publish-release-docs.yml b/.github/workflows/publish-release-docs.yml new file mode 100644 index 00000000..22710033 --- /dev/null +++ b/.github/workflows/publish-release-docs.yml @@ -0,0 +1,55 @@ +name: Supervision Release Documentation Workflow 📚 +on: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref}} + cancel-in-progress: true + +permissions: + contents: write + pages: write + pull-requests: write + + +jobs: + doc-build-deploy: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + steps: + - name: 🛎️ Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.head_ref }} + + - name: 🐍 Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: 📦 Install mkdocs-material + run: pip install "mkdocs-material" + - name: 📦 Install mkdocstrings[python] + run: pip install "mkdocstrings[python]" + - name: 📦 Install mkdocs-material[imaging] + run: pip install "mkdocs-material[imaging]" + - name: 📦 Install mike + run: pip install "mike" + - name: 📦 Install mkdocs-git-revision-date-localized-plugin + run: pip install "mkdocs-git-revision-date-localized-plugin" + - name: 📦 Install JupyterLab + run: pip install jupyterlab + - name: 📦 Install mkdocs-jupyter + run: pip install mkdocs-jupyter + - name: 📦 Install mkdocs-git-committers-plugin-2 + run: pip install mkdocs-git-committers-plugin-2 + - name: ⚙️ Configure git for github-actions 👷 + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + - name: 🚀 Deploy MkDoc-Material 📚 + run: | + latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) + MKDOCS_GIT_COMMITTERS_APIKEY=${{ secrets.GITHUB_TOKEN }} mike deploy --push --update-aliases $latest_tag latest From 574c61b6b00bb80f90b6826b404b843b3c1f6bf4 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 14:28:40 +0200 Subject: [PATCH 003/136] initial commit --- docs/how_to/detect_and_annotate.md | 2 +- docs/how_to/save_detections.md | 124 +++++++++++++++++++++++++++++ mkdocs.yml | 4 +- supervision/draw/color.py | 18 ++++- supervision/utils/image.py | 51 ++++++------ test/utils/test_image.py | 4 +- 6 files changed, 170 insertions(+), 33 deletions(-) create mode 100644 docs/how_to/save_detections.md diff --git a/docs/how_to/detect_and_annotate.md b/docs/how_to/detect_and_annotate.md index 221d9ca1..adea95cf 100644 --- a/docs/how_to/detect_and_annotate.md +++ b/docs/how_to/detect_and_annotate.md @@ -15,7 +15,7 @@ source image. ![basic-annotation](https://media.roboflow.com/supervision_detect_and_annotate_example_1.png) -## Run Inference +## Run Detection First, you'll need to obtain predictions from your object detection or segmentation model. diff --git a/docs/how_to/save_detections.md b/docs/how_to/save_detections.md new file mode 100644 index 00000000..622c504f --- /dev/null +++ b/docs/how_to/save_detections.md @@ -0,0 +1,124 @@ +--- +comments: true +status: new +--- + +# Save Detections + +TODO + +## Run Detection + +=== "Inference" + + ```python + import cv2 + from inference import get_model + + model = get_model(model_id="yolov8n-640") + image = cv2.imread() + results = model.infer(image)[0] + ``` + +=== "Ultralytics" + + ```python + import cv2 + from ultralytics import YOLO + + model = YOLO("yolov8n.pt") + image = cv2.imread() + results = model(image)[0] + ``` + +=== "Transformers" + + ```python + import torch + from PIL import Image + from transformers import DetrImageProcessor, DetrForObjectDetection + + processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") + model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") + + image = Image.open() + inputs = processor(images=image, return_tensors="pt") + + with torch.no_grad(): + outputs = model(**inputs) + + width, height = image.size + target_size = torch.tensor([[height, width]]) + results = processor.post_process_object_detection( + outputs=outputs, target_sizes=target_size)[0] + ``` + +## Save Detections as CSV + +TODO + +=== "Inference" + + ```python + import supervision as sv + from inference import get_model + + model = get_model(model_id="yolov8n-640") + + with sv.CSVSink() as sink: + for frame in sv.get_video_frames_generator(): + + results = model.infer(image)[0] + detections = sv.Detections.from_inference(results) + sink.append(detections, {}) + ``` + +=== "Ultralytics" + + ```python + import supervision as sv + from ultralytics import YOLO + + model = YOLO("yolov8n.pt") + + with sv.CSVSink() as sink: + for frame in sv.get_video_frames_generator(): + + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) + sink.append(detections, {}) + ``` + +=== "Transformers" + + ```python + import torch + from PIL import Image + from transformers import DetrImageProcessor, DetrForObjectDetection + + processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") + model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") + + image = Image.open() + inputs = processor(images=image, return_tensors="pt") + + with torch.no_grad(): + outputs = model(**inputs) + + width, height = image.size + target_size = torch.tensor([[height, width]]) + results = processor.post_process_object_detection( + outputs=outputs, target_sizes=target_size)[0] + ``` + +## Custom Fields + +TODO + +## Save Detections as JSON + +TODO + +## Process Video and Save Detections + +TODO \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 8bae5d76..dd6feb8d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,9 +38,11 @@ nav: - Home: index.md - How to: - Detect and Annotate: how_to/detect_and_annotate.md + - Save Detections: how_to/save_detections.md + - Filter Detections: how_to/filter_detections.md - Detect Small Objects: how_to/detect_small_objects.md - Track Objects: how_to/track_objects.md - - Filter Detections: how_to/filter_detections.md + - API: - Annotators: annotators.md - Classifications: diff --git a/supervision/draw/color.py b/supervision/draw/color.py index debb46f3..b195cffe 100644 --- a/supervision/draw/color.py +++ b/supervision/draw/color.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import List, Tuple +from typing import List, Tuple, Union import matplotlib.pyplot as plt @@ -448,3 +448,19 @@ class ColorPalette: raise ValueError("idx argument should not be negative") idx = idx % len(self.colors) return self.colors[idx] + + +def unify_to_bgr(color: Union[Tuple[int, int, int], Color]) -> Tuple[int, int, int]: + """ + Converts a color input in multiple formats to a standardized BGR format. + + Args: + color (Union[Tuple[int, int, int], Color]): The color input to be converted, + which can be either a tuple of RGB values or an instance of a Color class. + + Returns: + Tuple[int, int, int]: The color in BGR format as a tuple of three integers. + """ + if issubclass(type(color), Color): + return color.as_bgr() + return color diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 4e0ba5d7..64fb6de1 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -9,7 +9,7 @@ import cv2 import numpy as np from supervision.annotators.base import ImageType -from supervision.draw.color import Color +from supervision.draw.color import Color, unify_to_bgr from supervision.draw.utils import calculate_optimal_text_scale, draw_text from supervision.geometry.core import Point from supervision.utils.conversion import ( @@ -25,17 +25,18 @@ MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3 @convert_for_image_processing -def crop_image(image: np.ndarray, xyxy: np.ndarray) -> np.ndarray: +def crop_image(image: ImageType, 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. + image (ImageType): The image to be cropped. `ImageType` is a flexible type, + accepting either `numpy.ndarray` or `PIL.Image.Image`. 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. + (ImageType): The cropped image. Examples: ```python @@ -74,10 +75,10 @@ def resize_image(image: np.ndarray, scale_factor: float) -> np.ndarray: raise ValueError("Scale factor must be positive.") old_width, old_height = image.shape[1], image.shape[0] - nwe_width = int(old_width * scale_factor) + new_width = int(old_width * scale_factor) new_height = int(old_height * scale_factor) - return cv2.resize(image, (nwe_width, new_height), interpolation=cv2.INTER_LINEAR) + return cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_LINEAR) def place_image( @@ -285,14 +286,14 @@ def create_tiles( raise ValueError("Could not create image tiles from empty list of images.") if return_type == "auto": return_type = _negotiate_tiles_format(images=images) - tile_padding_color = _color_to_bgr(color=tile_padding_color) - tile_margin_color = _color_to_bgr(color=tile_margin_color) + tile_padding_color = unify_to_bgr(color=tile_padding_color) + tile_margin_color = unify_to_bgr(color=tile_margin_color) images = images_to_cv2(images=images) if single_tile_size is None: single_tile_size = _aggregate_images_shape(images=images, mode=tile_scaling) resized_images = [ letterbox_image( - image=i, desired_size=single_tile_size, color=tile_padding_color + image=i, target_resolution_wh=single_tile_size, color=tile_padding_color ) for i in images ] @@ -311,8 +312,8 @@ def create_tiles( titles_anchors = fill( sequence=titles_anchors, desired_size=len(images), content=None ) - titles_color = _color_to_bgr(color=titles_color) - titles_background_color = _color_to_bgr(color=titles_background_color) + titles_color = unify_to_bgr(color=titles_color) + titles_background_color = unify_to_bgr(color=titles_background_color) tiles = _generate_tiles( images=resized_images, grid_size=grid_size, @@ -546,8 +547,8 @@ def _generate_color_image( @convert_for_image_processing def letterbox_image( - image: np.ndarray, - desired_size: Tuple[int, int], + image: ImageType, + target_resolution_wh: Tuple[int, int], color: Union[Tuple[int, int, int], Color] = (0, 0, 0), ) -> np.ndarray: """ @@ -555,27 +556,27 @@ def letterbox_image( ratio, adding padding of given color if needed to maintain aspect ratio. Args: - image (np.ndarray): Input image (type will be adjusted by decorator, + image (ImageType): Input image (type will be adjusted by decorator, you can provide PIL.Image) - desired_size (Tuple[int, int]): image size (width, height) representing + target_resolution_wh (Tuple[int, int]): image size (width, height) representing the target dimensions. color (Union[Tuple[int, int, int], Color]): the color to pad with - If tuple provided - should be BGR. Returns: - np.ndarray: letterboxed image (type may be adjusted to PIL.Image by + ImageType: letterboxed image (type may be adjusted to PIL.Image by decorator if function was called with PIL.Image) """ - color = _color_to_bgr(color=color) + color = unify_to_bgr(color=color) resized_img = resize_image_keeping_aspect_ratio( image=image, - desired_size=desired_size, + desired_size=target_resolution_wh, ) new_height, new_width = resized_img.shape[:2] - top_padding = (desired_size[1] - new_height) // 2 - bottom_padding = desired_size[1] - new_height - top_padding - left_padding = (desired_size[0] - new_width) // 2 - right_padding = desired_size[0] - new_width - left_padding + top_padding = (target_resolution_wh[1] - new_height) // 2 + bottom_padding = target_resolution_wh[1] - new_height - top_padding + left_padding = (target_resolution_wh[0] - new_width) // 2 + right_padding = target_resolution_wh[0] - new_width - left_padding return cv2.copyMakeBorder( resized_img, top_padding, @@ -625,9 +626,3 @@ def resize_image_keeping_aspect_ratio( new_height = desired_size[1] new_width = int(desired_size[1] * img_ratio) return cv2.resize(image, (new_width, new_height)) - - -def _color_to_bgr(color: Union[Tuple[int, int, int], Color]) -> Tuple[int, int, int]: - if issubclass(type(color), Color): - return color.as_bgr() - return color diff --git a/test/utils/test_image.py b/test/utils/test_image.py index e50f2e57..50b6b5c1 100644 --- a/test/utils/test_image.py +++ b/test/utils/test_image.py @@ -62,7 +62,7 @@ def test_letterbox_image_for_opencv_image() -> None: # when result = letterbox_image( - image=image, desired_size=(1024, 1024), color=(255, 255, 255) + image=image, target_resolution_wh=(1024, 1024), color=(255, 255, 255) ) # then @@ -88,7 +88,7 @@ def test_letterbox_image_for_pillow_image() -> None: # when result = letterbox_image( - image=image, desired_size=(1024, 1024), color=(255, 255, 255) + image=image, target_resolution_wh=(1024, 1024), color=(255, 255, 255) ) # then From 5b1ee8f870c1c9e091fb84167fbf060ebc9c0cac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:31:30 +0000 Subject: [PATCH 004/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/how_to/save_detections.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/how_to/save_detections.md b/docs/how_to/save_detections.md index 622c504f..81de930e 100644 --- a/docs/how_to/save_detections.md +++ b/docs/how_to/save_detections.md @@ -62,12 +62,12 @@ TODO ```python import supervision as sv from inference import get_model - + model = get_model(model_id="yolov8n-640") - + with sv.CSVSink() as sink: for frame in sv.get_video_frames_generator(): - + results = model.infer(image)[0] detections = sv.Detections.from_inference(results) sink.append(detections, {}) @@ -78,12 +78,12 @@ TODO ```python import supervision as sv from ultralytics import YOLO - + model = YOLO("yolov8n.pt") - + with sv.CSVSink() as sink: for frame in sv.get_video_frames_generator(): - + results = model(frame)[0] detections = sv.Detections.from_ultralytics(results) sink.append(detections, {}) @@ -121,4 +121,4 @@ TODO ## Process Video and Save Detections -TODO \ No newline at end of file +TODO From 2115c9a1526dfc8d2d5de5f74651ac48f3f2c585 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 14:50:32 +0200 Subject: [PATCH 005/136] crop_image improvements --- supervision/utils/image.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 64fb6de1..a6466eaa 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -25,15 +25,19 @@ MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3 @convert_for_image_processing -def crop_image(image: ImageType, xyxy: np.ndarray) -> np.ndarray: +def crop_image( + image: ImageType, + xyxy: Union[np.ndarray, List[int], Tuple[int, int, int, int]] +) -> ImageType: """ Crops the given image based on the given bounding box. Args: image (ImageType): The image to be cropped. `ImageType` is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`. - xyxy (np.ndarray): A numpy array containing the bounding box coordinates - in the format (x1, y1, x2, y2). + xyxy (Union[np.ndarray, List[int], Tuple[int, int, int, int]]): A bounding box + coordinates in the format (x_min, y_min, x_max, y_max), accepted as either + a numpy array, a list, or a tuple. Returns: (ImageType): The cropped image. @@ -50,9 +54,11 @@ def crop_image(image: ImageType, xyxy: np.ndarray) -> np.ndarray: ``` """ + if isinstance(xyxy, (list, tuple)): + xyxy = np.array(xyxy) xyxy = np.round(xyxy).astype(int) - x1, y1, x2, y2 = xyxy - return image[y1:y2, x1:x2] + x_min, y_min, x_max, y_max = xyxy.flatten() + return image[y_min:y_max, x_min:x_max] @convert_for_image_processing From f2e6d882fbb64f326e179cdd24f5837715982ac8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:50:48 +0000 Subject: [PATCH 006/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index a6466eaa..954533a2 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -26,8 +26,7 @@ MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3 @convert_for_image_processing def crop_image( - image: ImageType, - xyxy: Union[np.ndarray, List[int], Tuple[int, int, int, int]] + image: ImageType, xyxy: Union[np.ndarray, List[int], Tuple[int, int, int, int]] ) -> ImageType: """ Crops the given image based on the given bounding box. From 7d0cf8a7233aa81b204e57d72de57b59b3e1f994 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Tue, 9 Apr 2024 15:44:53 +0300 Subject: [PATCH 007/136] =?UTF-8?q?feat:=20=F0=9F=9A=80=20initial=20sv.Key?= =?UTF-8?q?points=20features=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- supervision/__init__.py | 1 + supervision/detection/core.py | 2 +- supervision/detection/utils.py | 98 +----------------- supervision/keypoints/__init__.py | 0 supervision/keypoints/core.py | 160 +++++++++++++++++++++++++++++ supervision/keypoints/utils.py | 0 supervision/validators/__init__.py | 141 +++++++++++++++++++++++++ 7 files changed, 304 insertions(+), 98 deletions(-) create mode 100644 supervision/keypoints/__init__.py create mode 100644 supervision/keypoints/core.py create mode 100644 supervision/keypoints/utils.py create mode 100644 supervision/validators/__init__.py diff --git a/supervision/__init__.py b/supervision/__init__.py index 35f525b0..df04534d 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -69,6 +69,7 @@ from supervision.draw.utils import ( ) from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center +from supervision.keypoints.core import KeyPoints from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision from supervision.tracker.byte_tracker.core import ByteTrack from supervision.utils.file import list_files_with_extensions diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 8c29cfc4..bb8775ad 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -17,11 +17,11 @@ from supervision.detection.utils import ( mask_to_xyxy, merge_data, process_roboflow_result, - validate_detections_fields, xywh_to_xyxy, ) from supervision.geometry.core import Position from supervision.utils.internal import deprecated +from supervision.validators import validate_detections_fields @dataclass diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index b3928fdb..3eeba5b4 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -1,5 +1,5 @@ from itertools import chain -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import cv2 import numpy as np @@ -656,102 +656,6 @@ def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray: return np.column_stack((centroid_x, centroid_y)).astype(int) -def validate_xyxy(xyxy: Any) -> None: - expected_shape = "(_, 4)" - actual_shape = str(getattr(xyxy, "shape", None)) - is_valid = isinstance(xyxy, np.ndarray) and xyxy.ndim == 2 and xyxy.shape[1] == 4 - if not is_valid: - raise ValueError( - f"xyxy must be a 2D np.ndarray with shape {expected_shape}, but got shape " - f"{actual_shape}" - ) - - -def validate_mask(mask: Any, n: int) -> None: - expected_shape = f"({n}, H, W)" - actual_shape = str(getattr(mask, "shape", None)) - is_valid = mask is None or ( - isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n - ) - if not is_valid: - raise ValueError( - f"mask must be a 3D np.ndarray with shape {expected_shape}, but got shape " - f"{actual_shape}" - ) - - -def validate_class_id(class_id: Any, n: int) -> None: - expected_shape = f"({n},)" - actual_shape = str(getattr(class_id, "shape", None)) - is_valid = class_id is None or ( - isinstance(class_id, np.ndarray) and class_id.shape == (n,) - ) - if not is_valid: - raise ValueError( - f"class_id must be a 1D np.ndarray with shape {expected_shape}, but got " - f"shape {actual_shape}" - ) - - -def validate_confidence(confidence: Any, n: int) -> None: - expected_shape = f"({n},)" - actual_shape = str(getattr(confidence, "shape", None)) - is_valid = confidence is None or ( - isinstance(confidence, np.ndarray) and confidence.shape == (n,) - ) - if not is_valid: - raise ValueError( - f"confidence must be a 1D np.ndarray with shape {expected_shape}, but got " - f"shape {actual_shape}" - ) - - -def validate_tracker_id(tracker_id: Any, n: int) -> None: - expected_shape = f"({n},)" - actual_shape = str(getattr(tracker_id, "shape", None)) - is_valid = tracker_id is None or ( - isinstance(tracker_id, np.ndarray) and tracker_id.shape == (n,) - ) - if not is_valid: - raise ValueError( - f"tracker_id must be a 1D np.ndarray with shape {expected_shape}, but got " - f"shape {actual_shape}" - ) - - -def validate_data(data: Dict[str, Any], n: int) -> None: - for key, value in data.items(): - if isinstance(value, list): - if len(value) != n: - raise ValueError(f"Length of list for key '{key}' must be {n}") - elif isinstance(value, np.ndarray): - if value.ndim == 1 and value.shape[0] != n: - raise ValueError(f"Shape of np.ndarray for key '{key}' must be ({n},)") - elif value.ndim > 1 and value.shape[0] != n: - raise ValueError( - f"First dimension of np.ndarray for key '{key}' must have size {n}" - ) - else: - raise ValueError(f"Value for key '{key}' must be a list or np.ndarray") - - -def validate_detections_fields( - xyxy: Any, - mask: Any, - class_id: Any, - confidence: Any, - tracker_id: Any, - data: Dict[str, Any], -) -> None: - validate_xyxy(xyxy) - n = len(xyxy) - validate_mask(mask, n) - validate_class_id(class_id, n) - validate_confidence(confidence, n) - validate_tracker_id(tracker_id, n) - validate_data(data, n) - - def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray]) -> bool: """ Compares the data payloads of two Detections instances. diff --git a/supervision/keypoints/__init__.py b/supervision/keypoints/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/supervision/keypoints/core.py b/supervision/keypoints/core.py new file mode 100644 index 00000000..bf2ab886 --- /dev/null +++ b/supervision/keypoints/core.py @@ -0,0 +1,160 @@ +from dataclasses import dataclass, field +from typing import Any, Dict, Iterator, List, Optional, Tuple, Union + +import numpy as np +import numpy.typing as npt + +from supervision.config import CLASS_NAME_DATA_FIELD +from supervision.detection.utils import get_data_item, is_data_equal +from supervision.validators import validate_keypoints_fields + + +@dataclass +class KeyPoints: + """ + The `sv.KeyPoints` allows you to convert results from a variety of keypoint + detections models into a single, unified format. + + ```python + import cv2 + import supervision as sv + from ultralytics import YOLO + + image = cv2.imread() + model = YOLO('yolov8s-pose.pt') + result = model(image)[0] + keypoints = sv.KeyPoints.from_ultralytics(result) + ``` + + !!! tip + + In `sv.KeyPoints`, detection data is categorized into two main field types: + fixed and custom. The fixed fields include `xy`, `confidence`, + `class_id`. For any additional data requirements, custom + fields come into play, stored in the data field. These custom fields are easily + accessible using the `detections[]` syntax, providing flexibility + for diverse data handling needs. + + Attributes: + xy (np.ndarray): An array of shape `(n, 2)` containing + the bounding boxes coordinates in format `[x1, y1]` + confidence (Optional[np.ndarray]): An array of shape + `(n,)` containing the confidence scores of the keypoint detections. + class_id (Optional[np.ndarray]): An array of shape + `(n,)` containing the class ids of the keypoint detections. + data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional + data where each key is a string representing the data type, and the value + is either a NumPy array or a list of corresponding data. + + !!! warning + + The `data` field in the `sv.KeyPoints` class is currently in an experimental + phase. Please be aware that its API and functionality are subject to change in + future updates as we continue to refine and improve its capabilities. + We encourage users to experiment with this feature and provide feedback, but + also to be prepared for potential modifications in upcoming releases. + """ + + xy: npt.NDArray[np.float32] + class_id: Optional[npt.NDArray[np.int_]] = None + confidence: Optional[npt.NDArray[np.float32]] = None + data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) + + def __post_init__(self): + validate_keypoints_fields( + xy=self.xy, + confidence=self.confidence, + class_id=self.class_id, + data=self.data, + ) + + def __len__(self) -> int: + """ + Returns the number of keypoints in the keypoints object. + """ + return len(self.xy) + + def __iter__( + self, + ) -> Iterator[ + Tuple[ + np.ndarray, + Optional[np.ndarray], + Optional[float], + Optional[int], + Optional[int], + Dict[str, Union[np.ndarray, List]], + ] + ]: + """ + Iterates over the Keypoint object and yield a tuple of + `(xy, confidence, class_id, data)` for each keypoint detection. + """ + for i in range(len(self.xy)): + yield ( + self.xy[i], + self.confidence[i] if self.confidence is not None else None, + self.class_id[i] if self.class_id is not None else None, + get_data_item(self.data, i), + ) + + def __eq__(self, other: "KeyPoints") -> bool: + return all( + [ + np.array_equal(self.xy, other.xy), + np.array_equal(self.class_id, other.class_id), + np.array_equal(self.confidence, other.confidence), + is_data_equal(self.data, other.data), + ] + ) + + @classmethod + def from_ultralytics(cls, ultralytics_results) -> "KeyPoints": + """ + Creates a Keypoints instance from a + [YOLOv8](https://github.com/ultralytics/ultralytics) inference result. + + Args: + ultralytics_results (ultralytics.engine.results.Keypoints): + The output Results instance from YOLOv8 + + Returns: + KeyPoints: A new Keypoints object. + + Example: + ```python + import cv2 + import supervision as sv + from ultralytics import YOLO + + image = cv2.imread() + model = YOLO('yolov8s-pose.pt') + result = model(image)[0] + keypoints = sv.KeyPoints.from_ultralytics(result) + ``` + """ # + + class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) + class_names = np.array([ultralytics_results.names[i] for i in class_id]) + + xy = ultralytics_results.keypoints.xy.cpu().numpy() + confidence = ultralytics_results.keypoints.conf.cpu().numpy() + class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) + data = {CLASS_NAME_DATA_FIELD: class_names} + return cls(xy, class_id, confidence, data) + + @classmethod + def empty(cls) -> "KeyPoints": + """ + Create an empty Keypoints object with no keypoints. + + Returns: + (Keypoints): An empty Keypoints object. + + Example: + ```python + from supervision import Keypoints + empty_keypoints = Keypoints.empty() + ``` + """ + return cls(xy=np.empty((0, 0, 2), dtype=np.float32)) diff --git a/supervision/keypoints/utils.py b/supervision/keypoints/utils.py new file mode 100644 index 00000000..e69de29b diff --git a/supervision/validators/__init__.py b/supervision/validators/__init__.py new file mode 100644 index 00000000..9a9fca8c --- /dev/null +++ b/supervision/validators/__init__.py @@ -0,0 +1,141 @@ +from typing import Any, Dict + +import numpy as np + + +def validate_xyxy(xyxy: Any) -> None: + expected_shape = "(_, 4)" + actual_shape = str(getattr(xyxy, "shape", None)) + is_valid = isinstance(xyxy, np.ndarray) and xyxy.ndim == 2 and xyxy.shape[1] == 4 + if not is_valid: + raise ValueError( + f"xyxy must be a 2D np.ndarray with shape {expected_shape}, but got shape " + f"{actual_shape}" + ) + + +def validate_mask(mask: Any, n: int) -> None: + expected_shape = f"({n}, H, W)" + actual_shape = str(getattr(mask, "shape", None)) + is_valid = mask is None or ( + isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n + ) + if not is_valid: + raise ValueError( + f"mask must be a 3D np.ndarray with shape {expected_shape}, but got shape " + f"{actual_shape}" + ) + + +def validate_class_id(class_id: Any, n: int) -> None: + expected_shape = f"({n},)" + actual_shape = str(getattr(class_id, "shape", None)) + is_valid = class_id is None or ( + isinstance(class_id, np.ndarray) and class_id.shape == (n,) + ) + if not is_valid: + raise ValueError( + f"class_id must be a 1D np.ndarray with shape {expected_shape}, but got " + f"shape {actual_shape}" + ) + + +def validate_confidence(confidence: Any, n: int) -> None: + expected_shape = f"({n},)" + actual_shape = str(getattr(confidence, "shape", None)) + is_valid = confidence is None or ( + isinstance(confidence, np.ndarray) and confidence.shape == (n,) + ) + if not is_valid: + raise ValueError( + f"confidence must be a 1D np.ndarray with shape {expected_shape}, but got " + f"shape {actual_shape}" + ) + + +def validate_keypoint_confidence(confidence: Any, n: int, m: int) -> None: + expected_shape = f"({n,m})" + actual_shape = str(getattr(confidence, "shape", None)) + + if confidence is not None: + is_valid = isinstance(confidence, np.ndarray) and confidence.shape == (n, m) + if not is_valid: + raise ValueError( + f"confidence must be a 1D np.ndarray with shape {expected_shape}, but " + "got" + f"shape {actual_shape}" + ) + + +def validate_tracker_id(tracker_id: Any, n: int) -> None: + expected_shape = f"({n},)" + actual_shape = str(getattr(tracker_id, "shape", None)) + is_valid = tracker_id is None or ( + isinstance(tracker_id, np.ndarray) and tracker_id.shape == (n,) + ) + if not is_valid: + raise ValueError( + f"tracker_id must be a 1D np.ndarray with shape {expected_shape}, but got " + f"shape {actual_shape}" + ) + + +def validate_data(data: Dict[str, Any], n: int) -> None: + for key, value in data.items(): + if isinstance(value, list): + if len(value) != n: + raise ValueError(f"Length of list for key '{key}' must be {n}") + elif isinstance(value, np.ndarray): + if value.ndim == 1 and value.shape[0] != n: + raise ValueError(f"Shape of np.ndarray for key '{key}' must be ({n},)") + elif value.ndim > 1 and value.shape[0] != n: + raise ValueError( + f"First dimension of np.ndarray for key '{key}' must have size {n}" + ) + else: + raise ValueError(f"Value for key '{key}' must be a list or np.ndarray") + + +def validate_xy(xy: Any, n: int, m: int) -> None: + expected_shape = f"({n, m},)" + actual_shape = str(getattr(xy, "shape", None)) + + is_valid = isinstance(xy, np.ndarray) and ( + xy.shape == (n, m, 2) or xy.shape == (n, m, 3) + ) + if not is_valid: + raise ValueError( + f"xy must be a 2D np.ndarray with shape {expected_shape}, but got shape " + f"{actual_shape}" + ) + + +def validate_detections_fields( + xyxy: Any, + mask: Any, + class_id: Any, + confidence: Any, + tracker_id: Any, + data: Dict[str, Any], +) -> None: + validate_xyxy(xyxy) + n = len(xyxy) + validate_mask(mask, n) + validate_class_id(class_id, n) + validate_confidence(confidence, n) + validate_tracker_id(tracker_id, n) + validate_data(data, n) + + +def validate_keypoints_fields( + xy: Any, + class_id: Any, + confidence: Any, + data: Dict[str, Any], +) -> None: + n = len(xy) + m = len(xy[0]) if len(xy) > 0 else 0 + validate_xy(xy, n, m) + validate_class_id(class_id, n) + validate_keypoint_confidence(confidence, n, m) + validate_data(data, n) From 5d43e1380d248d2c8db5a3352feea855199d01f6 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Tue, 9 Apr 2024 17:42:31 +0300 Subject: [PATCH 008/136] =?UTF-8?q?feat(test):=20=F0=9F=9A=80=20mock=20key?= =?UTF-8?q?points=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- test/test_utils.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/test_utils.py b/test/test_utils.py index b8ef9231..d3624845 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -3,6 +3,7 @@ from typing import Any, Dict, List, Optional import numpy as np from supervision.detection.core import Detections +from supervision.keypoints.core import KeyPoints def mock_detections( @@ -30,5 +31,24 @@ def mock_detections( ) +def mock_keypoints( + xy: List[List[float]], + confidence: Optional[List[float]] = None, + class_id: Optional[List[int]] = None, + data: Optional[Dict[str, List[Any]]] = None, +) -> KeyPoints: + def convert_data(data: Dict[str, List[Any]]): + return {k: np.array(v) for k, v in data.items()} + + return KeyPoints( + xy=np.array(xy, dtype=np.float32), + confidence=( + confidence if confidence is None else np.array(confidence, dtype=np.float32) + ), + class_id=(class_id if class_id is None else np.array(class_id, dtype=int)), + data=convert_data(data) if data else {}, + ) + + def assert_almost_equal(actual, expected, tolerance=1e-5): assert abs(actual - expected) < tolerance, f"Expected {expected}, but got {actual}." From b63379749e9862846307fcbea99be3d4b7b3b68b Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Tue, 9 Apr 2024 17:43:19 +0300 Subject: [PATCH 009/136] =?UTF-8?q?fix(keypoint):=20=F0=9F=9A=80=20getitem?= =?UTF-8?q?=20and=20setitem=20funcs=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- supervision/keypoints/core.py | 88 +++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/supervision/keypoints/core.py b/supervision/keypoints/core.py index bf2ab886..646b99db 100644 --- a/supervision/keypoints/core.py +++ b/supervision/keypoints/core.py @@ -13,7 +13,7 @@ from supervision.validators import validate_keypoints_fields class KeyPoints: """ The `sv.KeyPoints` allows you to convert results from a variety of keypoint - detections models into a single, unified format. + keypoints models into a single, unified format. ```python import cv2 @@ -32,16 +32,16 @@ class KeyPoints: fixed and custom. The fixed fields include `xy`, `confidence`, `class_id`. For any additional data requirements, custom fields come into play, stored in the data field. These custom fields are easily - accessible using the `detections[]` syntax, providing flexibility + accessible using the `keypoints[]` syntax, providing flexibility for diverse data handling needs. Attributes: xy (np.ndarray): An array of shape `(n, 2)` containing the bounding boxes coordinates in format `[x1, y1]` confidence (Optional[np.ndarray]): An array of shape - `(n,)` containing the confidence scores of the keypoint detections. + `(n,)` containing the confidence scores of the keypoint keypoints. class_id (Optional[np.ndarray]): An array of shape - `(n,)` containing the class ids of the keypoint detections. + `(n,)` containing the class ids of the keypoint keypoints. data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional data where each key is a string representing the data type, and the value is either a NumPy array or a list of corresponding data. @@ -143,6 +143,86 @@ class KeyPoints: data = {CLASS_NAME_DATA_FIELD: class_names} return cls(xy, class_id, confidence, data) + def __getitem__( + self, index: Union[int, slice, List[int], np.ndarray, str] + ) -> Union["KeyPoints", List, np.ndarray, None]: + """ + Get a subset of the KeyPoints object or access an item from its data field. + + When provided with an integer, slice, list of integers, or a numpy array, this + method returns a new KeyPoints object that represents a subset of the original + keypoints. When provided with a string, it accesses the corresponding item in + the data dictionary. + + Args: + index (Union[int, slice, List[int], np.ndarray, str]): The index, indices, + or key to access a subset of the KeyPoints or an item from the data. + + Returns: + Union[KeyPoints, Any]: A subset of the KeyPoints object or an item from + the data field. + + Example: + ```python + import supervision as sv + + keypoints = sv.KeyPoints() + + first_detection = keypoints[0] + first_10_keypoints = keypoints[0:10] + some_keypoints = keypoints[[0, 2, 4]] + class_0_keypoints = keypoints[keypoints.class_id == 0] + high_confidence_keypoints = keypoints[keypoints.confidence > 0.5] + + feature_vector = keypoints['feature_vector'] + ``` + """ + if isinstance(index, str): + return self.data.get(index) + if isinstance(index, int): + index = [index] + return KeyPoints( + xy=self.xy[index], + 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, + data=get_data_item(self.data, index), + ) + + def __setitem__(self, key: str, value: Union[np.ndarray, List]): + """ + Set a value in the data dictionary of the KeyPoints object. + + Args: + key (str): The key in the data dictionary to set. + value (Union[np.ndarray, List]): The value to set for the key. + + Example: + ```python + import cv2 + import supervision as sv + from ultralytics import YOLO + + image = cv2.imread() + model = YOLO('yolov8s.pt') + + result = model(image)[0] + keypoints = sv.KeyPoints.from_ultralytics(result) + + keypoints['names'] = [ + model.model.names[class_id] + for class_id + in keypoints.class_id + ] + ``` + """ + if not isinstance(value, (np.ndarray, list)): + raise TypeError("Value must be a np.ndarray or a list") + + if isinstance(value, list): + value = np.array(value) + + self.data[key] = value + @classmethod def empty(cls) -> "KeyPoints": """ From 94e06ab0d6b71d87a3839207efe6b23d6bf74186 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 16:46:05 +0200 Subject: [PATCH 010/136] scale_image, resize_image changes --- docs/utils/image.md | 10 +- supervision/__init__.py | 4 +- supervision/annotators/core.py | 4 +- supervision/utils/image.py | 165 ++++++++++++++++++++++----------- 4 files changed, 124 insertions(+), 59 deletions(-) diff --git a/docs/utils/image.md b/docs/utils/image.md index 087aebd7..b61cddfc 100644 --- a/docs/utils/image.md +++ b/docs/utils/image.md @@ -18,10 +18,10 @@ status: new :::supervision.utils.image.crop_image
-

letterbox_image

+

scale_image

-:::supervision.utils.image.letterbox_image +:::supervision.utils.image.scale_image

resize_image

@@ -29,6 +29,12 @@ status: new :::supervision.utils.image.resize_image +
+

letterbox_image

+
+ +:::supervision.utils.image.letterbox_image +

place_image

diff --git a/supervision/__init__.py b/supervision/__init__.py index 8c3acc26..d6aeeab5 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -78,8 +78,8 @@ from supervision.utils.image import ( crop_image, letterbox_image, place_image, - resize_image, - resize_image_keeping_aspect_ratio, + scale_image, + resize_image ) from supervision.utils.notebook import plot_image, plot_images_grid from supervision.utils.video import ( diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index 9f6cdb36..38ddc442 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -13,7 +13,7 @@ from supervision.draw.color import Color, ColorPalette from supervision.draw.utils import draw_polygon from supervision.geometry.core import Position from supervision.utils.conversion import convert_for_annotation_method -from supervision.utils.image import crop_image, place_image, resize_image +from supervision.utils.image import crop_image, place_image, scale_image class BoundingBoxAnnotator(BaseAnnotator): @@ -1965,7 +1965,7 @@ class CropAnnotator(BaseAnnotator): crop_image(image=scene, xyxy=xyxy) for xyxy in detections.xyxy.astype(int) ] resized_crops = [ - resize_image(image=crop, scale_factor=self.scale_factor) for crop in crops + scale_image(image=crop, scale_factor=self.scale_factor) for crop in crops ] anchors = detections.get_anchors_coordinates(anchor=self.position).astype(int) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 954533a2..99fcb3f2 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -7,6 +7,7 @@ from typing import Callable, List, Literal, Optional, Tuple, Union import cv2 import numpy as np +import numpy.typing as npt from supervision.annotators.base import ImageType from supervision.draw.color import Color, unify_to_bgr @@ -26,7 +27,8 @@ MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3 @convert_for_image_processing def crop_image( - image: ImageType, xyxy: Union[np.ndarray, List[int], Tuple[int, int, int, int]] + image: ImageType, + xyxy: Union[npt.NDArray[int], List[int], Tuple[int, int, int, int]] ) -> ImageType: """ Crops the given image based on the given bounding box. @@ -35,23 +37,49 @@ def crop_image( image (ImageType): The image to be cropped. `ImageType` is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`. xyxy (Union[np.ndarray, List[int], Tuple[int, int, int, int]]): A bounding box - coordinates in the format (x_min, y_min, x_max, y_max), accepted as either - a numpy array, a list, or a tuple. + coordinates in the format `(x_min, y_min, x_max, y_max)`, accepted as either + a `numpy.ndarray`, a `list`, or a `tuple`. Returns: - (ImageType): The cropped image. + (ImageType): The cropped image. The type is determined by the input type and + may be either a `numpy.ndarray` or `PIL.Image.Image`. Examples: + + === "OpenCV" + ```python + import cv2 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=cropped_image) + image = cv2.imread() + image.shape + # (1080, 1920, 3) + + xyxy = [200, 400, 600, 800] + cropped_image = sv.crop_image(image=image, xyxy=xyxy) + cropped_image.shape + # (400, 400, 3) ``` - """ + + === "Pillow" + + ```python + from PIL import Image + import supervision as sv + + image = Image.open() + image.size + # (1920, 1080) + + xyxy = [200, 400, 600, 800] + cropped_image = sv.crop_image(image=image, xyxy=xyxy) + cropped_image.size + # (400, 400) + ``` + + ![crop_image](https://media.roboflow.com/supervision-docs/crop-image.png){ align=center width="800" } + """ # noqa E501 // docs if isinstance(xyxy, (list, tuple)): xyxy = np.array(xyxy) @@ -61,29 +89,62 @@ def crop_image( @convert_for_image_processing -def resize_image(image: np.ndarray, scale_factor: float) -> np.ndarray: +def scale_image(image: ImageType, scale_factor: float) -> ImageType: """ - Resizes an image by a given scale factor using cv2.INTER_LINEAR interpolation. + Scales the given image based on the given scale factor. Args: - image (np.ndarray): The input image to be resized. + image (ImageType): The image to be scaled. `ImageType` is a flexible type, + accepting either `numpy.ndarray` or `PIL.Image.Image`. scale_factor (float): The factor by which the image will be scaled. Scale - factor > 1.0 zooms in, < 1.0 zooms out. + factor > `1.0` zooms in, < `1.0` zooms out. Returns: - np.ndarray: The resized image. + (ImageType): The scaled image. The type is determined by the input type and + may be either a `numpy.ndarray` or `PIL.Image.Image`. Raises: ValueError: If the scale factor is non-positive. + + Examples: + + === "OpenCV" + + ```python + import cv2 + import supervision as sv + + image = cv2.imread() + image.shape + # (1080, 1920, 3) + + scaled_image = sv.scale_image(image=image, scale_factor=0.5) + scaled_image.shape + # (540, 960, 3) + ``` + + === "Pillow" + + ```python + from PIL import Image + import supervision as sv + + image = Image.open() + image.size + # (1920, 1080) + + scaled_image = sv.scale_image(image=image, scale_factor=0.5) + scaled_image.size + # (540, 960) + ``` """ if scale_factor <= 0: raise ValueError("Scale factor must be positive.") - old_width, old_height = image.shape[1], image.shape[0] - new_width = int(old_width * scale_factor) - new_height = int(old_height * scale_factor) - - return cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_LINEAR) + width_old, height_old = image.shape[1], image.shape[0] + width_new = int(width_old * scale_factor) + height_new = int(height_old * scale_factor) + return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR) def place_image( @@ -573,9 +634,10 @@ def letterbox_image( decorator if function was called with PIL.Image) """ color = unify_to_bgr(color=color) - resized_img = resize_image_keeping_aspect_ratio( + resized_img = resize_image( image=image, - desired_size=target_resolution_wh, + target_resolution_wh=target_resolution_wh, + keep_aspect_ratio=True ) new_height, new_width = resized_img.shape[:2] top_padding = (target_resolution_wh[1] - new_height) // 2 @@ -594,40 +656,37 @@ def letterbox_image( @convert_for_image_processing -def resize_image_keeping_aspect_ratio( - image: np.ndarray, - desired_size: Tuple[int, int], -) -> np.ndarray: +def resize_image( + image: ImageType, + target_resolution_wh: Tuple[int, int], + keep_aspect_ratio: bool = False +) -> ImageType: """ - Resize and pad image preserving its aspect ratio. + Resizes the given image to a specified resolution. Can maintain the original aspect + ratio or resize directly to the desired dimensions. - For example: input image is (640, 480) and we want to resize into - (1024, 1024). If this rectangular image is just resized naively - to square-shape output - aspect ratio would be altered. If we do not - want this to happen - we may resize bigger dimension (640) to 1024. - Ratio of change is 1.6. This ratio is later on used to calculate scaling - in the other dimension. As a result we have (1024, 768) image. - - Parameters: - - image (np.ndarray): Input image (type will be adjusted by decorator, - you can provide PIL.Image) - - desired_size (Tuple[int, int]): image size (width, height) representing the - target dimensions. Parameter will be used to dictate maximum size of - output image. Output size may be smaller - to preserve aspect ratio of original - image. + Args: + image (ImageType): The image to be resized. `ImageType` is a flexible type, + accepting either `numpy.ndarray` or `PIL.Image.Image`. + target_resolution_wh (Tuple[int, int]): The target resolution as + `(width, height)`. + keep_aspect_ratio (bool, optional): Flag to maintain the image's original + aspect ratio. Defaults to `False`. Returns: - np.ndarray: resized image (type may be adjusted to PIL.Image by decorator - if function was called with PIL.Image) + ImageType: The resized image. The type is determined by the input type and + may be either a `numpy.ndarray` or `PIL.Image.Image`. """ - if image.shape[:2] == desired_size[::-1]: - return image - img_ratio = image.shape[1] / image.shape[0] - desired_ratio = desired_size[0] / desired_size[1] - if img_ratio >= desired_ratio: - new_width = desired_size[0] - new_height = int(desired_size[0] / img_ratio) + if keep_aspect_ratio: + image_ratio = image.shape[1] / image.shape[0] + target_ratio = target_resolution_wh[0] / target_resolution_wh[1] + if image_ratio >= target_ratio: + width_new = target_resolution_wh[0] + height_new = int(target_resolution_wh[0] / image_ratio) + else: + height_new = target_resolution_wh[1] + width_new = int(target_resolution_wh[1] * image_ratio) else: - new_height = desired_size[1] - new_width = int(desired_size[1] * img_ratio) - return cv2.resize(image, (new_width, new_height)) + width_new, height_new = target_resolution_wh + + return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR) From ad946893624e8609282035af3443d361f06b5564 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:46:45 +0000 Subject: [PATCH 011/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/__init__.py | 2 +- supervision/utils/image.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index d6aeeab5..b14a37ab 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -78,8 +78,8 @@ from supervision.utils.image import ( crop_image, letterbox_image, place_image, + resize_image, scale_image, - resize_image ) from supervision.utils.notebook import plot_image, plot_images_grid from supervision.utils.video import ( diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 99fcb3f2..8fe25eea 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -28,7 +28,7 @@ MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3 @convert_for_image_processing def crop_image( image: ImageType, - xyxy: Union[npt.NDArray[int], List[int], Tuple[int, int, int, int]] + xyxy: Union[npt.NDArray[int], List[int], Tuple[int, int, int, int]], ) -> ImageType: """ Crops the given image based on the given bounding box. @@ -635,9 +635,7 @@ def letterbox_image( """ color = unify_to_bgr(color=color) resized_img = resize_image( - image=image, - target_resolution_wh=target_resolution_wh, - keep_aspect_ratio=True + image=image, target_resolution_wh=target_resolution_wh, keep_aspect_ratio=True ) new_height, new_width = resized_img.shape[:2] top_padding = (target_resolution_wh[1] - new_height) // 2 @@ -659,7 +657,7 @@ def letterbox_image( def resize_image( image: ImageType, target_resolution_wh: Tuple[int, int], - keep_aspect_ratio: bool = False + keep_aspect_ratio: bool = False, ) -> ImageType: """ Resizes the given image to a specified resolution. Can maintain the original aspect From 233d25d4c539eb180e2091856f3ebdddb6695c95 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 17:48:13 +0200 Subject: [PATCH 012/136] fix `resize_image` tests after rename --- supervision/utils/image.py | 116 ++++++++++++++++++++++++------------- test/utils/test_image.py | 16 ++--- 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 8fe25eea..4c5cc9c6 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -135,7 +135,7 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType: scaled_image = sv.scale_image(image=image, scale_factor=0.5) scaled_image.size - # (540, 960) + # (960, 540) ``` """ if scale_factor <= 0: @@ -147,6 +147,81 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType: return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR) +@convert_for_image_processing +def resize_image( + image: ImageType, + resolution_wh: Tuple[int, int], + keep_aspect_ratio: bool = False, +) -> ImageType: + """ + Resizes the given image to a specified resolution. Can maintain the original aspect + ratio or resize directly to the desired dimensions. + + Args: + image (ImageType): The image to be resized. `ImageType` is a flexible type, + accepting either `numpy.ndarray` or `PIL.Image.Image`. + resolution_wh (Tuple[int, int]): The target resolution as + `(width, height)`. + keep_aspect_ratio (bool, optional): Flag to maintain the image's original + aspect ratio. Defaults to `False`. + + Returns: + ImageType: The resized image. The type is determined by the input type and + may be either a `numpy.ndarray` or `PIL.Image.Image`. + + Examples: + + === "OpenCV" + + ```python + import cv2 + import supervision as sv + + image = cv2.imread() + image.shape + # (1080, 1920, 3) + + resized_image = sv.resize_image( + image=image, resolution_wh=(1000, 1000), keep_aspect_ratio=True + ) + resized_image.shape + # (562, 1000, 3) + ``` + + === "Pillow" + + ```python + from PIL import Image + import supervision as sv + + image = Image.open() + image.size + # (1920, 1080) + + resized_image = sv.resize_image( + image=image, resolution_wh=(1000, 1000), keep_aspect_ratio=True + ) + resized_image.size + # (1000, 562) + ``` + + ![resize_image](https://media.roboflow.com/supervision-docs/resize-image.png){ align=center width="800" } + """ # noqa E501 // docs + if keep_aspect_ratio: + image_ratio = image.shape[1] / image.shape[0] + target_ratio = resolution_wh[0] / resolution_wh[1] + if image_ratio >= target_ratio: + width_new = resolution_wh[0] + height_new = int(resolution_wh[0] / image_ratio) + else: + height_new = resolution_wh[1] + width_new = int(resolution_wh[1] * image_ratio) + else: + width_new, height_new = resolution_wh + + return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR) + + def place_image( scene: np.ndarray, image: np.ndarray, anchor: Tuple[int, int] ) -> np.ndarray: @@ -635,7 +710,7 @@ def letterbox_image( """ color = unify_to_bgr(color=color) resized_img = resize_image( - image=image, target_resolution_wh=target_resolution_wh, keep_aspect_ratio=True + image=image, resolution_wh=target_resolution_wh, keep_aspect_ratio=True ) new_height, new_width = resized_img.shape[:2] top_padding = (target_resolution_wh[1] - new_height) // 2 @@ -651,40 +726,3 @@ def letterbox_image( cv2.BORDER_CONSTANT, value=color, ) - - -@convert_for_image_processing -def resize_image( - image: ImageType, - target_resolution_wh: Tuple[int, int], - keep_aspect_ratio: bool = False, -) -> ImageType: - """ - Resizes the given image to a specified resolution. Can maintain the original aspect - ratio or resize directly to the desired dimensions. - - Args: - image (ImageType): The image to be resized. `ImageType` is a flexible type, - accepting either `numpy.ndarray` or `PIL.Image.Image`. - target_resolution_wh (Tuple[int, int]): The target resolution as - `(width, height)`. - keep_aspect_ratio (bool, optional): Flag to maintain the image's original - aspect ratio. Defaults to `False`. - - Returns: - ImageType: The resized image. The type is determined by the input type and - may be either a `numpy.ndarray` or `PIL.Image.Image`. - """ - if keep_aspect_ratio: - image_ratio = image.shape[1] / image.shape[0] - target_ratio = target_resolution_wh[0] / target_resolution_wh[1] - if image_ratio >= target_ratio: - width_new = target_resolution_wh[0] - height_new = int(target_resolution_wh[0] / image_ratio) - else: - height_new = target_resolution_wh[1] - width_new = int(target_resolution_wh[1] * image_ratio) - else: - width_new, height_new = target_resolution_wh - - return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR) diff --git a/test/utils/test_image.py b/test/utils/test_image.py index 50b6b5c1..b3e28a27 100644 --- a/test/utils/test_image.py +++ b/test/utils/test_image.py @@ -8,19 +8,20 @@ from supervision import Color, Point from supervision.utils.image import ( create_tiles, letterbox_image, - resize_image_keeping_aspect_ratio, + resize_image, ) -def test_resize_image_keeping_aspect_ratio_for_opencv_image() -> None: +def test_resize_image_for_opencv_image() -> None: # given image = np.zeros((480, 640, 3), dtype=np.uint8) expected_result = np.zeros((768, 1024, 3), dtype=np.uint8) # when - result = resize_image_keeping_aspect_ratio( + result = resize_image( image=image, - desired_size=(1024, 1024), + resolution_wh=(1024, 1024), + keep_aspect_ratio=True, ) # then @@ -29,15 +30,16 @@ def test_resize_image_keeping_aspect_ratio_for_opencv_image() -> None: ), "Expected output shape to be (w, h): (1024, 768)" -def test_resize_image_keeping_aspect_ratio_for_pillow_image() -> None: +def test_resize_image_for_pillow_image() -> None: # given image = Image.new(mode="RGB", size=(640, 480), color=(0, 0, 0)) expected_result = Image.new(mode="RGB", size=(1024, 768), color=(0, 0, 0)) # when - result = resize_image_keeping_aspect_ratio( + result = resize_image( image=image, - desired_size=(1024, 1024), + resolution_wh=(1024, 1024), + keep_aspect_ratio=True, ) # then From a7dccfcc0d3f168e36b9f4be06686206be1d3260 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:48:30 +0000 Subject: [PATCH 013/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/utils/image.py | 2 +- test/utils/test_image.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 4c5cc9c6..e04ea45e 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -204,7 +204,7 @@ def resize_image( resized_image.size # (1000, 562) ``` - + ![resize_image](https://media.roboflow.com/supervision-docs/resize-image.png){ align=center width="800" } """ # noqa E501 // docs if keep_aspect_ratio: diff --git a/test/utils/test_image.py b/test/utils/test_image.py index b3e28a27..a114d9aa 100644 --- a/test/utils/test_image.py +++ b/test/utils/test_image.py @@ -5,11 +5,7 @@ import pytest from PIL import Image, ImageChops from supervision import Color, Point -from supervision.utils.image import ( - create_tiles, - letterbox_image, - resize_image, -) +from supervision.utils.image import create_tiles, letterbox_image, resize_image def test_resize_image_for_opencv_image() -> None: From 774ac2ed4f82706c0a889741b2619cc8575b5ee0 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 17:59:19 +0200 Subject: [PATCH 014/136] update `letterbox_image` docs --- supervision/utils/image.py | 86 +++++++++++++++++++------------------- test/utils/test_image.py | 4 +- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index e04ea45e..274cef2f 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -222,6 +222,48 @@ def resize_image( return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR) +@convert_for_image_processing +def letterbox_image( + image: ImageType, + resolution_wh: Tuple[int, int], + color: Union[Tuple[int, int, int], Color] = (0, 0, 0), +) -> ImageType: + """ + Resizes and pads an image to a specified resolution with a given color, maintaining + the original aspect ratio. + + Args: + image (ImageType): The image to be resized. `ImageType` is a flexible type, + accepting either `numpy.ndarray` or `PIL.Image.Image`. + resolution_wh (Tuple[int, int]): The target resolution as + `(width, height)`. + color (Union[Tuple[int, int, int], Color]): The color to pad with. If tuple + provided it should be in BGR format. + + Returns: + ImageType: The resized image. The type is determined by the input type and + may be either a `numpy.ndarray` or `PIL.Image.Image`. + """ + color = unify_to_bgr(color=color) + resized_image = resize_image( + image=image, resolution_wh=resolution_wh, keep_aspect_ratio=True + ) + height_new, width_new = resized_image.shape[:2] + padding_top = (resolution_wh[1] - height_new) // 2 + padding_bottom = resolution_wh[1] - height_new - padding_top + padding_left = (resolution_wh[0] - width_new) // 2 + padding_right = resolution_wh[0] - width_new - padding_left + return cv2.copyMakeBorder( + resized_image, + padding_top, + padding_bottom, + padding_left, + padding_right, + cv2.BORDER_CONSTANT, + value=color, + ) + + def place_image( scene: np.ndarray, image: np.ndarray, anchor: Tuple[int, int] ) -> np.ndarray: @@ -434,7 +476,7 @@ def create_tiles( single_tile_size = _aggregate_images_shape(images=images, mode=tile_scaling) resized_images = [ letterbox_image( - image=i, target_resolution_wh=single_tile_size, color=tile_padding_color + image=i, resolution_wh=single_tile_size, color=tile_padding_color ) for i in images ] @@ -684,45 +726,3 @@ def _generate_color_image( shape: Tuple[int, int], color: Tuple[int, int, int] ) -> np.ndarray: return np.ones(shape[::-1] + (3,), dtype=np.uint8) * color - - -@convert_for_image_processing -def letterbox_image( - image: ImageType, - target_resolution_wh: Tuple[int, int], - color: Union[Tuple[int, int, int], Color] = (0, 0, 0), -) -> np.ndarray: - """ - Resize and pad image to fit the desired size, preserving its aspect - ratio, adding padding of given color if needed to maintain aspect ratio. - - Args: - image (ImageType): Input image (type will be adjusted by decorator, - you can provide PIL.Image) - target_resolution_wh (Tuple[int, int]): image size (width, height) representing - the target dimensions. - color (Union[Tuple[int, int, int], Color]): the color to pad with - If - tuple provided - should be BGR. - - Returns: - ImageType: letterboxed image (type may be adjusted to PIL.Image by - decorator if function was called with PIL.Image) - """ - color = unify_to_bgr(color=color) - resized_img = resize_image( - image=image, resolution_wh=target_resolution_wh, keep_aspect_ratio=True - ) - new_height, new_width = resized_img.shape[:2] - top_padding = (target_resolution_wh[1] - new_height) // 2 - bottom_padding = target_resolution_wh[1] - new_height - top_padding - left_padding = (target_resolution_wh[0] - new_width) // 2 - right_padding = target_resolution_wh[0] - new_width - left_padding - return cv2.copyMakeBorder( - resized_img, - top_padding, - bottom_padding, - left_padding, - right_padding, - cv2.BORDER_CONSTANT, - value=color, - ) diff --git a/test/utils/test_image.py b/test/utils/test_image.py index a114d9aa..487434ae 100644 --- a/test/utils/test_image.py +++ b/test/utils/test_image.py @@ -60,7 +60,7 @@ def test_letterbox_image_for_opencv_image() -> None: # when result = letterbox_image( - image=image, target_resolution_wh=(1024, 1024), color=(255, 255, 255) + image=image, resolution_wh=(1024, 1024), color=(255, 255, 255) ) # then @@ -86,7 +86,7 @@ def test_letterbox_image_for_pillow_image() -> None: # when result = letterbox_image( - image=image, target_resolution_wh=(1024, 1024), color=(255, 255, 255) + image=image, resolution_wh=(1024, 1024), color=(255, 255, 255) ) # then From 079e62fb0c544248cc6511d79d6be664928aa81e Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 21:23:23 +0200 Subject: [PATCH 015/136] rename `place_image` to overlay `image` --- docs/utils/image.md | 14 ++--- supervision/__init__.py | 2 +- supervision/annotators/core.py | 4 +- supervision/utils/image.py | 96 +++++++++++++++++++++++++--------- 4 files changed, 81 insertions(+), 35 deletions(-) diff --git a/docs/utils/image.md b/docs/utils/image.md index b61cddfc..16a18dc4 100644 --- a/docs/utils/image.md +++ b/docs/utils/image.md @@ -5,12 +5,6 @@ status: new # Image Utils -
-

ImageSink

-
- -:::supervision.utils.image.ImageSink -

crop_image

@@ -39,4 +33,10 @@ status: new

place_image

-:::supervision.utils.image.place_image +:::supervision.utils.image.overlay_image + +
+

ImageSink

+
+ +:::supervision.utils.image.ImageSink diff --git a/supervision/__init__.py b/supervision/__init__.py index b14a37ab..9ff09557 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -77,7 +77,7 @@ from supervision.utils.image import ( create_tiles, crop_image, letterbox_image, - place_image, + overlay_image, resize_image, scale_image, ) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index 38ddc442..854a9ece 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -13,7 +13,7 @@ from supervision.draw.color import Color, ColorPalette from supervision.draw.utils import draw_polygon from supervision.geometry.core import Position from supervision.utils.conversion import convert_for_annotation_method -from supervision.utils.image import crop_image, place_image, scale_image +from supervision.utils.image import crop_image, overlay_image, scale_image class BoundingBoxAnnotator(BaseAnnotator): @@ -1974,7 +1974,7 @@ class CropAnnotator(BaseAnnotator): (x1, y1), (x2, y2) = self.calculate_crop_coordinates( anchor=anchor, crop_wh=crop_wh, position=self.position ) - scene = place_image(scene=scene, image=resized_crop, anchor=(x1, y1)) + scene = overlay_image(scene=scene, inserted_image=resized_crop, anchor=(x1, y1)) color = resolve_color( color=self.border_color, detections=detections, diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 274cef2f..b579ba75 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -243,7 +243,41 @@ def letterbox_image( Returns: ImageType: The resized image. The type is determined by the input type and may be either a `numpy.ndarray` or `PIL.Image.Image`. - """ + + ## Examples: + + === "OpenCV" + + ```python + import cv2 + import supervision as sv + + image = cv2.imread() + image.shape + # (1080, 1920, 3) + + letterboxed_image = sv.letterbox_image(image=image, resolution_wh=(1000, 1000)) + letterboxed_image.shape + # (1000, 1000, 3) + ``` + + === "Pillow" + + ```python + from PIL import Image + import supervision as sv + + image = Image.open() + image.size + # (1920, 1080) + + letterboxed_image = sv.letterbox_image(image=image, resolution_wh=(1000, 1000)) + letterboxed_image.size + # (1000, 1000) + ``` + + ![letterbox_image](https://media.roboflow.com/supervision-docs/letterbox-image.png){ align=center width="800" } + """ # noqa E501 // docs color = unify_to_bgr(color=color) resized_image = resize_image( image=image, resolution_wh=resolution_wh, keep_aspect_ratio=True @@ -264,48 +298,60 @@ def letterbox_image( ) -def place_image( - scene: np.ndarray, image: np.ndarray, anchor: Tuple[int, int] -) -> np.ndarray: +def overlay_image( + image: npt.NDArray[np.uint8], + overlay: npt.NDArray[np.uint8], + anchor: Tuple[int, int] +) -> npt.NDArray[np.uint8]: """ Places an image onto a scene at a given anchor point, handling cases where the image's position is partially or completely outside the scene's bounds. Args: - scene (np.ndarray): The background scene onto which the image is placed. - image (np.ndarray): The image to be placed onto the scene. - anchor (Tuple[int, int]): The (x, y) coordinates in the scene where the + image (np.ndarray): The background scene onto which the image is placed. + overlay (np.ndarray): The image to be placed onto the scene. + anchor (Tuple[int, int]): The `(x, y)` coordinates in the scene where the top-left corner of the image will be placed. Returns: - np.ndarray: The modified scene with the image placed at the anchor point, - or unchanged if the image placement is completely outside the scene. + np.ndarray: The result image with overlay. + + Examples: + ```python + import cv2 + import numpy as np + import supervision as sv + + image = cv2.imread() + overlay = np.zeros((200, 200, 3), dtype=np.uint8) + result_image = sv.letterbox_image( + image=image, overlay=overlay, anchor=(200, 400)) + ``` """ - scene_height, scene_width = scene.shape[:2] - image_height, image_width = image.shape[:2] + scene_height, scene_width = image.shape[:2] + image_height, image_width = overlay.shape[:2] anchor_x, anchor_y = anchor is_out_horizontally = anchor_x + image_width <= 0 or anchor_x >= scene_width is_out_vertically = anchor_y + image_height <= 0 or anchor_y >= scene_height if is_out_horizontally or is_out_vertically: - return scene + return image - start_y = max(anchor_y, 0) - start_x = max(anchor_x, 0) - end_y = min(scene_height, anchor_y + image_height) - end_x = min(scene_width, anchor_x + image_width) + x_min = max(anchor_x, 0) + y_min = max(anchor_y, 0) + x_max = min(scene_width, anchor_x + image_width) + y_max = min(scene_height, anchor_y + image_height) - crop_start_y = max(-anchor_y, 0) - crop_start_x = max(-anchor_x, 0) - crop_end_y = image_height - max((anchor_y + image_height) - scene_height, 0) - crop_end_x = image_width - max((anchor_x + image_width) - scene_width, 0) + crop_x_min = max(-anchor_x, 0) + crop_y_min = max(-anchor_y, 0) + crop_x_max = image_width - max((anchor_x + image_width) - scene_width, 0) + crop_y_max = image_height - max((anchor_y + image_height) - scene_height, 0) - scene[start_y:end_y, start_x:end_x] = image[ - crop_start_y:crop_end_y, crop_start_x:crop_end_x - ] + image[y_min:y_max, x_min:x_max] = \ + overlay[crop_y_min:crop_y_max, crop_x_min:crop_x_max] - return scene + return image class ImageSink: @@ -335,7 +381,7 @@ class ImageSink: source_path='source_video.mp4', stride=2): sink.save_image(image=image) ``` - """ + """ # noqa E501 // docs self.target_dir_path = target_dir_path self.overwrite = overwrite From f872fddaffe2d34c11f2fcc62b6d1b53a28aec85 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:24:02 +0000 Subject: [PATCH 016/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/annotators/core.py | 4 +++- supervision/utils/image.py | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index 854a9ece..ac901862 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -1974,7 +1974,9 @@ class CropAnnotator(BaseAnnotator): (x1, y1), (x2, y2) = self.calculate_crop_coordinates( anchor=anchor, crop_wh=crop_wh, position=self.position ) - scene = overlay_image(scene=scene, inserted_image=resized_crop, anchor=(x1, y1)) + scene = overlay_image( + scene=scene, inserted_image=resized_crop, anchor=(x1, y1) + ) color = resolve_color( color=self.border_color, detections=detections, diff --git a/supervision/utils/image.py b/supervision/utils/image.py index b579ba75..f6eef9ca 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -275,7 +275,7 @@ def letterbox_image( letterboxed_image.size # (1000, 1000) ``` - + ![letterbox_image](https://media.roboflow.com/supervision-docs/letterbox-image.png){ align=center width="800" } """ # noqa E501 // docs color = unify_to_bgr(color=color) @@ -301,7 +301,7 @@ def letterbox_image( def overlay_image( image: npt.NDArray[np.uint8], overlay: npt.NDArray[np.uint8], - anchor: Tuple[int, int] + anchor: Tuple[int, int], ) -> npt.NDArray[np.uint8]: """ Places an image onto a scene at a given anchor point, handling cases where @@ -348,8 +348,9 @@ def overlay_image( crop_x_max = image_width - max((anchor_x + image_width) - scene_width, 0) crop_y_max = image_height - max((anchor_y + image_height) - scene_height, 0) - image[y_min:y_max, x_min:x_max] = \ - overlay[crop_y_min:crop_y_max, crop_x_min:crop_x_max] + image[y_min:y_max, x_min:x_max] = overlay[ + crop_y_min:crop_y_max, crop_x_min:crop_x_max + ] return image From c30e377eb5393aed81b182d6a66217e73c6d439a Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 22:07:12 +0200 Subject: [PATCH 017/136] more docs improvements --- docs/utils/image.md | 2 +- supervision/__init__.py | 1 + supervision/utils/conversion.py | 4 ++-- supervision/utils/image.py | 31 ++++++++++++------------------- supervision/utils/iterables.py | 4 ++-- 5 files changed, 18 insertions(+), 24 deletions(-) diff --git a/docs/utils/image.md b/docs/utils/image.md index 16a18dc4..87fd1943 100644 --- a/docs/utils/image.md +++ b/docs/utils/image.md @@ -30,7 +30,7 @@ status: new :::supervision.utils.image.letterbox_image
-

place_image

+

overlay_image

:::supervision.utils.image.overlay_image diff --git a/supervision/__init__.py b/supervision/__init__.py index 9ff09557..6cdb23ca 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -34,6 +34,7 @@ from supervision.dataset.core import ( ClassificationDataset, DetectionDataset, ) +from supervision.utils.conversion import pillow_to_cv2, cv2_to_pillow from supervision.detection.annotate import BoxAnnotator from supervision.detection.core import Detections from supervision.detection.line_zone import LineZone, LineZoneAnnotator diff --git a/supervision/utils/conversion.py b/supervision/utils/conversion.py index 608104bc..8ddce969 100644 --- a/supervision/utils/conversion.py +++ b/supervision/utils/conversion.py @@ -81,7 +81,7 @@ def pillow_to_cv2(image: Image.Image) -> np.ndarray: image (Image.Image): Pillow image (in RGB format). Returns: - np.ndarray: Input image converted to OpenCV format. + (np.ndarray): Input image converted to OpenCV format. """ scene = np.array(image) scene = cv2.cvtColor(scene, cv2.COLOR_RGB2BGR) @@ -97,7 +97,7 @@ def cv2_to_pillow(image: np.ndarray) -> Image.Image: image (np.ndarray): OpenCV image (in BGR format). Returns: - Image.Image: Input image converted to Pillow format. + (Image.Image): Input image converted to Pillow format. """ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) return Image.fromarray(image) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index b579ba75..488a9d36 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -44,8 +44,6 @@ def crop_image( (ImageType): The cropped image. The type is determined by the input type and may be either a `numpy.ndarray` or `PIL.Image.Image`. - Examples: - === "OpenCV" ```python @@ -106,8 +104,6 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType: Raises: ValueError: If the scale factor is non-positive. - Examples: - === "OpenCV" ```python @@ -166,11 +162,9 @@ def resize_image( aspect ratio. Defaults to `False`. Returns: - ImageType: The resized image. The type is determined by the input type and + (ImageType): The resized image. The type is determined by the input type and may be either a `numpy.ndarray` or `PIL.Image.Image`. - Examples: - === "OpenCV" ```python @@ -226,7 +220,7 @@ def resize_image( def letterbox_image( image: ImageType, resolution_wh: Tuple[int, int], - color: Union[Tuple[int, int, int], Color] = (0, 0, 0), + color: Union[Tuple[int, int, int], Color] = Color.BLACK, ) -> ImageType: """ Resizes and pads an image to a specified resolution with a given color, maintaining @@ -241,11 +235,9 @@ def letterbox_image( provided it should be in BGR format. Returns: - ImageType: The resized image. The type is determined by the input type and + (ImageType): The resized image. The type is determined by the input type and may be either a `numpy.ndarray` or `PIL.Image.Image`. - ## Examples: - === "OpenCV" ```python @@ -314,7 +306,7 @@ def overlay_image( top-left corner of the image will be placed. Returns: - np.ndarray: The result image with overlay. + (np.ndarray): The result image with overlay. Examples: ```python @@ -323,10 +315,11 @@ def overlay_image( import supervision as sv image = cv2.imread() - overlay = np.zeros((200, 200, 3), dtype=np.uint8) - result_image = sv.letterbox_image( - image=image, overlay=overlay, anchor=(200, 400)) + overlay = np.zeros((400, 400, 3), dtype=np.uint8) + result_image = sv.overlay_image(image=image, overlay=overlay, anchor=(200, 400)) ``` + + ![overlay_image](https://media.roboflow.com/supervision-docs/overlay-image.png){ align=center width="800" } """ scene_height, scene_width = image.shape[:2] image_height, image_width = overlay.shape[:2] @@ -374,11 +367,11 @@ class ImageSink: Examples: ```python import supervision as sv + + frames_generator = sv.get_video_frames_generator(, stride=2) - 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): + with sv.ImageSink(target_dir_path=) as sink: + for image in frames_generator: sink.save_image(image=image) ``` """ # noqa E501 // docs diff --git a/supervision/utils/iterables.py b/supervision/utils/iterables.py index ad570379..52bfbeb6 100644 --- a/supervision/utils/iterables.py +++ b/supervision/utils/iterables.py @@ -16,7 +16,7 @@ def create_batches( batch_size (int): The expected size of a batch. Returns: - Generator[List[V], None, None]: A generator that yields chunks + (Generator[List[V], None, None]): A generator that yields chunks of `sequence` of size `batch_size`, up to the length of the input `sequence`. @@ -54,7 +54,7 @@ def fill(sequence: List[V], desired_size: int, content: V) -> List[V]: `sequence` as padding. Returns: - List[V]: A padded version of the input `sequence` (if needed). + (List[V]): A padded version of the input `sequence` (if needed). Examples: ```python From 6248fa7d170ce765e5d59d2bce839836171841f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:08:10 +0000 Subject: [PATCH 018/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/__init__.py | 2 +- supervision/utils/image.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index 6cdb23ca..f8e7a832 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -34,7 +34,6 @@ from supervision.dataset.core import ( ClassificationDataset, DetectionDataset, ) -from supervision.utils.conversion import pillow_to_cv2, cv2_to_pillow from supervision.detection.annotate import BoxAnnotator from supervision.detection.core import Detections from supervision.detection.line_zone import LineZone, LineZoneAnnotator @@ -72,6 +71,7 @@ from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision from supervision.tracker.byte_tracker.core import ByteTrack +from supervision.utils.conversion import cv2_to_pillow, pillow_to_cv2 from supervision.utils.file import list_files_with_extensions from supervision.utils.image import ( ImageSink, diff --git a/supervision/utils/image.py b/supervision/utils/image.py index dd3316ec..44ccbeac 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -368,7 +368,7 @@ class ImageSink: Examples: ```python import supervision as sv - + frames_generator = sv.get_video_frames_generator(, stride=2) with sv.ImageSink(target_dir_path=) as sink: From d929d6f58b989a0fd38a359ca4b12b40921cccf2 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 22:44:10 +0200 Subject: [PATCH 019/136] `How to Save Detections` guide update --- docs/how_to/save_detections.md | 222 ++++++++++++++++++++++++++++----- 1 file changed, 191 insertions(+), 31 deletions(-) diff --git a/docs/how_to/save_detections.md b/docs/how_to/save_detections.md index 81de930e..e0233f9b 100644 --- a/docs/how_to/save_detections.md +++ b/docs/how_to/save_detections.md @@ -9,48 +9,64 @@ TODO ## Run Detection +First, you'll need to obtain predictions from your object detection or segmentation +model. You can learn more on this topic in our +[How to Detect and Annotate](/latest/how_to/detect_and_annotate.md) guide. + === "Inference" ```python - import cv2 + import supervision as sv from inference import get_model model = get_model(model_id="yolov8n-640") - image = cv2.imread() - results = model.infer(image)[0] + frames_generator = sv.get_video_frames_generator() + + for frame in frames_generator: + + results = model.infer(image)[0] + detections = sv.Detections.from_inference(results) ``` === "Ultralytics" ```python - import cv2 + import supervision as sv from ultralytics import YOLO model = YOLO("yolov8n.pt") - image = cv2.imread() - results = model(image)[0] + frames_generator = sv.get_video_frames_generator() + + for frame in frames_generator: + + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) ``` === "Transformers" ```python import torch - from PIL import Image + import supervision as sv from transformers import DetrImageProcessor, DetrForObjectDetection processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") + frames_generator = sv.get_video_frames_generator() - image = Image.open() - inputs = processor(images=image, return_tensors="pt") + for frame in frames_generator: - with torch.no_grad(): - outputs = model(**inputs) + frame = sv.cv2_to_pillow(frame) + inputs = processor(images=frame, return_tensors="pt") - width, height = image.size - target_size = torch.tensor([[height, width]]) - results = processor.post_process_object_detection( - outputs=outputs, target_sizes=target_size)[0] + with torch.no_grad(): + outputs = model(**inputs) + + width, height = frame.size + target_size = torch.tensor([[height, width]]) + results = processor.post_process_object_detection( + outputs=outputs, target_sizes=target_size)[0] + detections = sv.Detections.from_transformers(results) ``` ## Save Detections as CSV @@ -59,14 +75,15 @@ TODO === "Inference" - ```python + ```{ .py hl_lines="7 12" } import supervision as sv from inference import get_model model = get_model(model_id="yolov8n-640") + frames_generator = sv.get_video_frames_generator() with sv.CSVSink() as sink: - for frame in sv.get_video_frames_generator(): + for frame in frames_generator: results = model.infer(image)[0] detections = sv.Detections.from_inference(results) @@ -75,14 +92,15 @@ TODO === "Ultralytics" - ```python + ```{ .py hl_lines="7 12" } import supervision as sv from ultralytics import YOLO model = YOLO("yolov8n.pt") + frames_generator = sv.get_video_frames_generator() with sv.CSVSink() as sink: - for frame in sv.get_video_frames_generator(): + for frame in frames_generator: results = model(frame)[0] detections = sv.Detections.from_ultralytics(results) @@ -91,34 +109,176 @@ TODO === "Transformers" - ```python + ```{ .py hl_lines="9 23" } import torch - from PIL import Image + import supervision as sv from transformers import DetrImageProcessor, DetrForObjectDetection processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") + frames_generator = sv.get_video_frames_generator() - image = Image.open() - inputs = processor(images=image, return_tensors="pt") + with sv.CSVSink() as sink: + for frame in frames_generator: - with torch.no_grad(): - outputs = model(**inputs) + frame = sv.cv2_to_pillow(frame) + inputs = processor(images=frame, return_tensors="pt") - width, height = image.size - target_size = torch.tensor([[height, width]]) - results = processor.post_process_object_detection( - outputs=outputs, target_sizes=target_size)[0] + with torch.no_grad(): + outputs = model(**inputs) + + width, height = frame.size + target_size = torch.tensor([[height, width]]) + results = processor.post_process_object_detection( + outputs=outputs, target_sizes=target_size)[0] + detections = sv.Detections.from_transformers(results) + sink.append(detections, {}) ``` +TODO + +| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name | +|---------|----------|---------|----------|----------|------------|------------|------------| +| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car | +| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck | +| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car | + ## Custom Fields TODO +=== "Inference" + + ```{ .py hl_lines="8 12" } + import supervision as sv + from inference import get_model + + model = get_model(model_id="yolov8n-640") + frames_generator = sv.get_video_frames_generator() + + with sv.CSVSink() as sink: + for frame_index, frame in enumerate(frames_generator): + + results = model.infer(image)[0] + detections = sv.Detections.from_inference(results) + sink.append(detections, {"frame_index": frame_index}) + ``` + +=== "Ultralytics" + + ```{ .py hl_lines="8 12" } + import supervision as sv + from ultralytics import YOLO + + model = YOLO("yolov8n.pt") + frames_generator = sv.get_video_frames_generator() + + with sv.CSVSink() as sink: + for frame_index, frame in enumerate(frames_generator): + + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) + sink.append(detections, {"frame_index": frame_index}) + ``` + +=== "Transformers" + + ```{ .py hl_lines="10 23" } + import torch + import supervision as sv + from transformers import DetrImageProcessor, DetrForObjectDetection + + processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") + model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") + frames_generator = sv.get_video_frames_generator() + + with sv.CSVSink() as sink: + for frame_index, frame in enumerate(frames_generator): + + frame = sv.cv2_to_pillow(frame) + inputs = processor(images=frame, return_tensors="pt") + + with torch.no_grad(): + outputs = model(**inputs) + + width, height = frame.size + target_size = torch.tensor([[height, width]]) + results = processor.post_process_object_detection( + outputs=outputs, target_sizes=target_size)[0] + detections = sv.Detections.from_transformers(results) + sink.append(detections, {"frame_index": frame_index}) + ``` + +TODO + +| x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name | frame_index | +|---------|----------|---------|----------|----------|------------|------------|------------|-------------| +| 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car | 0 | +| 944.889 | 899.641 | 1235.42 | 1308.80 | 7 | 0.6752 | | truck | 0 | +| 1439.78 | 1077.79 | 1621.27 | 1231.40 | 2 | 0.6450 | | car | 0 | + ## Save Detections as JSON TODO -## Process Video and Save Detections +=== "Inference" -TODO + ```{ .py hl_lines="7" } + import supervision as sv + from inference import get_model + + model = get_model(model_id="yolov8n-640") + frames_generator = sv.get_video_frames_generator() + + with sv.JSONSink() as sink: + for frame_index, frame in enumerate(frames_generator): + + results = model.infer(image)[0] + detections = sv.Detections.from_inference(results) + sink.append(detections, {"frame_index": frame_index}) + ``` + +=== "Ultralytics" + + ```{ .py hl_lines="7" } + import supervision as sv + from ultralytics import YOLO + + model = YOLO("yolov8n.pt") + frames_generator = sv.get_video_frames_generator() + + with sv.JSONSink() as sink: + for frame_index, frame in enumerate(frames_generator): + + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) + sink.append(detections, {"frame_index": frame_index}) + ``` + +=== "Transformers" + + ```{ .py hl_lines="9" } + import torch + import supervision as sv + from transformers import DetrImageProcessor, DetrForObjectDetection + + processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") + model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") + frames_generator = sv.get_video_frames_generator() + + with sv.JSONSink() as sink: + for frame_index, frame in enumerate(frames_generator): + + frame = sv.cv2_to_pillow(frame) + inputs = processor(images=frame, return_tensors="pt") + + with torch.no_grad(): + outputs = model(**inputs) + + width, height = frame.size + target_size = torch.tensor([[height, width]]) + results = processor.post_process_object_detection( + outputs=outputs, target_sizes=target_size)[0] + detections = sv.Detections.from_transformers(results) + sink.append(detections, {"frame_index": frame_index}) + ``` \ No newline at end of file From 99fbd4b601657e7a25ed68a2ae805413788cd1ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:45:06 +0000 Subject: [PATCH 020/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/how_to/save_detections.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/how_to/save_detections.md b/docs/how_to/save_detections.md index e0233f9b..5d0dc61a 100644 --- a/docs/how_to/save_detections.md +++ b/docs/how_to/save_detections.md @@ -9,8 +9,8 @@ TODO ## Run Detection -First, you'll need to obtain predictions from your object detection or segmentation -model. You can learn more on this topic in our +First, you'll need to obtain predictions from your object detection or segmentation +model. You can learn more on this topic in our [How to Detect and Annotate](/latest/how_to/detect_and_annotate.md) guide. === "Inference" @@ -281,4 +281,4 @@ TODO outputs=outputs, target_sizes=target_size)[0] detections = sv.Detections.from_transformers(results) sink.append(detections, {"frame_index": frame_index}) - ``` \ No newline at end of file + ``` From e0d5f00ae3439bdece59cb1bd8d07ada53d91d78 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 23:15:49 +0200 Subject: [PATCH 021/136] initial version of `How to Save Detections` guide done --- docs/how_to/save_detections.md | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/how_to/save_detections.md b/docs/how_to/save_detections.md index e0233f9b..f432c939 100644 --- a/docs/how_to/save_detections.md +++ b/docs/how_to/save_detections.md @@ -5,7 +5,13 @@ status: new # Save Detections -TODO +Supervision enables an easy way to save detections in .CSV and .JSON files for offline +processing. This guide demonstrates how to perform video inference using the +[Inference](https://github.com/roboflow/inference), +[Ultralytics](https://github.com/ultralytics/ultralytics) or +[Transformers](https://github.com/huggingface/transformers) packages and save their results with +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) and +[`sv.JSONSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink). ## Run Detection @@ -71,7 +77,11 @@ model. You can learn more on this topic in our ## Save Detections as CSV -TODO +To save detections to a `.CSV` file, open our +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) +and then pass the +[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections) +object resulting from the inference to it. Its fields are parsed and saved on disk. === "Inference" @@ -135,8 +145,6 @@ TODO sink.append(detections, {}) ``` -TODO - | x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name | |---------|----------|---------|----------|----------|------------|------------|------------| | 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car | @@ -145,7 +153,12 @@ TODO ## Custom Fields -TODO +Besides regular fields in +[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections), +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) +also allows you to add custom information to each row, which can be passed via the +`custom_data` dictionary. Let's utilize this feature to save information about the +frame index from which the detections originate. === "Inference" @@ -209,8 +222,6 @@ TODO sink.append(detections, {"frame_index": frame_index}) ``` -TODO - | x_min | y_min | x_max | y_max | class_id | confidence | tracker_id | class_name | frame_index | |---------|----------|---------|----------|----------|------------|------------|------------|-------------| | 2941.14 | 1269.31 | 3220.77 | 1500.67 | 2 | 0.8517 | | car | 0 | @@ -219,7 +230,11 @@ TODO ## Save Detections as JSON -TODO +If you prefer to save the result in a `.JSON` file instead of a `.CSV` file, all you +need to do is replace +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) +with +[`sv.JSONSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink). === "Inference" From c27d9eaf22cb90efd8f88c91d555e9c32935ece2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:16:18 +0000 Subject: [PATCH 022/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/how_to/save_detections.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/how_to/save_detections.md b/docs/how_to/save_detections.md index 2c9250ea..94de6c61 100644 --- a/docs/how_to/save_detections.md +++ b/docs/how_to/save_detections.md @@ -5,12 +5,12 @@ status: new # Save Detections -Supervision enables an easy way to save detections in .CSV and .JSON files for offline -processing. This guide demonstrates how to perform video inference using the +Supervision enables an easy way to save detections in .CSV and .JSON files for offline +processing. This guide demonstrates how to perform video inference using the [Inference](https://github.com/roboflow/inference), [Ultralytics](https://github.com/ultralytics/ultralytics) or -[Transformers](https://github.com/huggingface/transformers) packages and save their results with -[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) and +[Transformers](https://github.com/huggingface/transformers) packages and save their results with +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) and [`sv.JSONSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink). ## Run Detection @@ -77,10 +77,10 @@ model. You can learn more on this topic in our ## Save Detections as CSV -To save detections to a `.CSV` file, open our -[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) -and then pass the -[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections) +To save detections to a `.CSV` file, open our +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) +and then pass the +[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections) object resulting from the inference to it. Its fields are parsed and saved on disk. === "Inference" @@ -153,11 +153,11 @@ object resulting from the inference to it. Its fields are parsed and saved on di ## Custom Fields -Besides regular fields in -[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections), -[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) -also allows you to add custom information to each row, which can be passed via the -`custom_data` dictionary. Let's utilize this feature to save information about the +Besides regular fields in +[`sv.Detections`](/latest/detection/core/#supervision.detection.core.Detections), +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) +also allows you to add custom information to each row, which can be passed via the +`custom_data` dictionary. Let's utilize this feature to save information about the frame index from which the detections originate. === "Inference" @@ -230,10 +230,10 @@ frame index from which the detections originate. ## Save Detections as JSON -If you prefer to save the result in a `.JSON` file instead of a `.CSV` file, all you -need to do is replace -[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) -with +If you prefer to save the result in a `.JSON` file instead of a `.CSV` file, all you +need to do is replace +[`sv.CSVSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) +with [`sv.JSONSink`](/latest/detection/tools/save_detections/#supervision.detection.tools.csv_sink.JSONSink). === "Inference" From eb1c7716ef0e9835f873133dd41e3ae40c9442df Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 9 Apr 2024 23:18:02 +0200 Subject: [PATCH 023/136] make ruff happy --- supervision/utils/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/utils/image.py b/supervision/utils/image.py index 44ccbeac..9f2e1783 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -320,7 +320,7 @@ def overlay_image( ``` ![overlay_image](https://media.roboflow.com/supervision-docs/overlay-image.png){ align=center width="800" } - """ + """ # noqa E501 // docs scene_height, scene_width = image.shape[:2] image_height, image_width = overlay.shape[:2] anchor_x, anchor_y = anchor From 2fd54521815f0267f746e14782c59916180e8b95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 00:14:47 +0000 Subject: [PATCH 024/136] :arrow_up: Bump mkdocs-jupyter from 0.24.3 to 0.24.7 Bumps [mkdocs-jupyter](https://github.com/danielfrg/mkdocs-jupyter) from 0.24.3 to 0.24.7. - [Changelog](https://github.com/danielfrg/mkdocs-jupyter/blob/main/CHANGELOG.md) - [Commits](https://github.com/danielfrg/mkdocs-jupyter/compare/0.24.3...0.24.7) --- updated-dependencies: - dependency-name: mkdocs-jupyter dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index 65a9122f..40e19b9c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2162,13 +2162,12 @@ pytz = "*" [[package]] name = "mkdocs-jupyter" -version = "0.24.3" +version = "0.24.7" description = "Use Jupyter in mkdocs websites" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs_jupyter-0.24.3-py3-none-any.whl", hash = "sha256:904262a8678a5e5920b7c3c03b5010b36301a69d0a38f2fcbf430493adf6879e"}, - {file = "mkdocs_jupyter-0.24.3.tar.gz", hash = "sha256:3d81da9aea27480e93bab22438910c4f0b9630613e74f85b576590d78e0e8b14"}, + {file = "mkdocs_jupyter-0.24.7-py3-none-any.whl", hash = "sha256:893d04bea1e007479a46e4e72852cd4d280c4d358ce4a0445250f3f80c639723"}, ] [package.dependencies] @@ -2179,9 +2178,6 @@ mkdocs-material = ">9.0.0" nbconvert = ">=7.2.9,<8" pygments = ">2.12.0" -[package.extras] -test = ["coverage[toml]", "pymdown-extensions", "pytest", "pytest-cov"] - [[package]] name = "mkdocs-material" version = "9.5.17" From 98b64f5e7d23386a78017522fb35ea10207570cf Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 10 Apr 2024 09:34:44 +0300 Subject: [PATCH 025/136] Add beach drone footage asset --- supervision/assets/list.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/supervision/assets/list.py b/supervision/assets/list.py index 83518cdb..77b1e461 100644 --- a/supervision/assets/list.py +++ b/supervision/assets/list.py @@ -18,6 +18,7 @@ class VideoAssets(Enum): | `SUBWAY` | `subway.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/subway.mp4) | | `MARKET_SQUARE` | `market-square.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/market-square.mp4) | | `PEOPLE_WALKING` | `people-walking.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/people-walking.mp4) | + | `BEACH` | `beach-1.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/beach-1.mp4) | """ # noqa: E501 // docs VEHICLES = "vehicles.mp4" @@ -27,6 +28,7 @@ class VideoAssets(Enum): SUBWAY = "subway.mp4" MARKET_SQUARE = "market-square.mp4" PEOPLE_WALKING = "people-walking.mp4" + BEACH = "beach-1.mp4" @classmethod def list(cls): @@ -62,4 +64,8 @@ VIDEO_ASSETS: Dict[str, Tuple[str, str]] = { f"{BASE_VIDEO_URL}{VideoAssets.PEOPLE_WALKING.value}", "0574c053c8686c3f1dc0aa3743e45cb9", ), + VideoAssets.BEACH.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.BEACH.value}", + "4175d42fec4d450ed081523fd39e0cf8", + ), } From eaff97f9027fc9b1b67c92156d29d36407b80128 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Wed, 10 Apr 2024 17:33:16 -0400 Subject: [PATCH 026/136] add external and internal track_ids to keep valid track ids sequential --- supervision/tracker/byte_tracker/core.py | 46 +++++++++++++++--------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 6475bf50..bf82044a 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -12,6 +12,7 @@ from supervision.utils.internal import deprecated_parameter class STrack(BaseTrack): shared_kalman = KalmanFilter() + _external_count = 0 def __init__(self, tlwh, score, class_ids, minimum_consecutive_frames): # wait activate @@ -24,6 +25,8 @@ class STrack(BaseTrack): self.class_ids = class_ids self.tracklet_len = 0 + self.external_track_id = -1 + self.minimum_consecutive_frames = minimum_consecutive_frames def predict(self): @@ -55,7 +58,7 @@ class STrack(BaseTrack): def activate(self, kalman_filter, frame_id): """Start a new tracklet""" self.kalman_filter = kalman_filter - self.track_id = self.next_id() + self.internal_track_id = self.next_id() self.mean, self.covariance = self.kalman_filter.initiate( self.tlwh_to_xyah(self._tlwh) ) @@ -76,7 +79,7 @@ class STrack(BaseTrack): self.frame_id = frame_id if new_id: - self.track_id = self.next_id() + self.internal_track_id = self.next_id() self.score = new_track.score def update(self, new_track, frame_id): @@ -95,8 +98,9 @@ class STrack(BaseTrack): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if self.tracklet_len >= self.minimum_consecutive_frames: + if self.tracklet_len == self.minimum_consecutive_frames: self.is_activated = True + self.external_track_id = self.next_external_id() self.score = new_track.score @@ -133,6 +137,15 @@ class STrack(BaseTrack): def to_xyah(self): return self.tlwh_to_xyah(self.tlwh) + + @staticmethod + def next_external_id(): + STrack._external_count += 1 + return STrack._external_count + + @staticmethod + def reset_external_counter(): + STrack._external_count = 0 @staticmethod def tlbr_to_tlwh(tlbr): @@ -147,7 +160,7 @@ class STrack(BaseTrack): return ret def __repr__(self): - return "OT_{}_({}-{})".format(self.track_id, self.start_frame, self.end_frame) + return "OT_{}_({}-{})".format(self.internal_track_id, self.start_frame, self.end_frame) def detections2boxes(detections: Detections) -> np.ndarray: @@ -294,7 +307,7 @@ class ByteTrack: matches, _, _ = matching.linear_assignment(iou_costs, 0.5) detections.tracker_id = np.full(len(detections), -1, dtype=int) for i_detection, i_track in matches: - detections.tracker_id[i_detection] = int(tracks[i_track].track_id) + detections.tracker_id[i_detection] = int(tracks[i_track].external_track_id) return detections[detections.tracker_id != -1] @@ -318,6 +331,7 @@ class ByteTrack: self.lost_tracks: List[STrack] = [] self.removed_tracks: List[STrack] = [] BaseTrack.reset_counter() + STrack.reset_external_counter() def update_with_tensors(self, tensors: np.ndarray) -> List[STrack]: """ @@ -478,22 +492,22 @@ def joint_tracks( ) -> List[STrack]: """ Joins two lists of tracks, ensuring that the resulting list does not - contain tracks with duplicate track_id values. + contain tracks with duplicate internal_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). + track_list_a: First list of tracks (with internal_track_id attribute). + track_list_b: Second list of tracks (with internal_track_id attribute). Returns: Combined list of tracks from track_list_a and track_list_b - without duplicate track_id values. + without duplicate internal_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) + if track.internal_track_id not in seen_track_ids: + seen_track_ids.add(track.internal_track_id) result.append(track) return result @@ -502,17 +516,17 @@ def joint_tracks( 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. + that share the same internal_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 + track_list_a: List of tracks (with internal_track_id attribute). + track_list_b: List of tracks (with internal_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} + tracks = {track.internal_track_id: track for track in track_list_a} + track_ids_b = {track.internal_track_id for track in track_list_b} for track_id in track_ids_b: tracks.pop(track_id, None) From a2ab113f214d92e785bc5ddba5d0f64e669fe39f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:37:41 +0000 Subject: [PATCH 027/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index bf82044a..8e8104f4 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -137,7 +137,7 @@ class STrack(BaseTrack): def to_xyah(self): return self.tlwh_to_xyah(self.tlwh) - + @staticmethod def next_external_id(): STrack._external_count += 1 @@ -160,7 +160,9 @@ class STrack(BaseTrack): return ret def __repr__(self): - return "OT_{}_({}-{})".format(self.internal_track_id, self.start_frame, self.end_frame) + return "OT_{}_({}-{})".format( + self.internal_track_id, self.start_frame, self.end_frame + ) def detections2boxes(detections: Detections) -> np.ndarray: @@ -307,7 +309,9 @@ class ByteTrack: matches, _, _ = matching.linear_assignment(iou_costs, 0.5) detections.tracker_id = np.full(len(detections), -1, dtype=int) for i_detection, i_track in matches: - detections.tracker_id[i_detection] = int(tracks[i_track].external_track_id) + detections.tracker_id[i_detection] = int( + tracks[i_track].external_track_id + ) return detections[detections.tracker_id != -1] From 2c9a1f69c5ad1f460c2c5a413d802b2cef967d64 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:54:23 +0000 Subject: [PATCH 028/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 8e8104f4..1fa98864 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -142,7 +142,7 @@ class STrack(BaseTrack): def next_external_id(): STrack._external_count += 1 return STrack._external_count - + @staticmethod def reset_external_counter(): STrack._external_count = 0 From ed225187b248dad4dbcebc6385508a561d70a629 Mon Sep 17 00:00:00 2001 From: Piotr Skalski Date: Thu, 11 Apr 2024 14:09:02 +0200 Subject: [PATCH 029/136] Update README.md --- README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7d280b57..beed655c 100644 --- a/README.md +++ b/README.md @@ -217,19 +217,23 @@ len(dataset) ## 🎬 tutorials -

-Speed Estimation & Vehicle Tracking | Computer Vision | Open Source -Speed Estimation & Vehicle Tracking | Computer Vision | Open Source -

Created: 11 Jan 2024 | Updated: 11 Jan 2024
-
Learn how to track and estimate the speed of vehicles using YOLO, ByteTrack, and Roboflow Inference. This comprehensive tutorial covers object detection, multi-object tracking, filtering detections, perspective transformation, speed estimation, visualization improvements, and more.

+Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](https://github.com/roboflow/supervision/tree/develop/examples), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!

-Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking -Traffic Analysis with YOLOv8 and ByteTrack - Vehicle Detection and Tracking -

Created: 6 Sep 2023 | Updated: 6 Sep 2023
-
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...

+Dwell Time Analysis with Computer Vision | Real-Time Stream Processing +Dwell Time Analysis with Computer Vision | Real-Time Stream Processing +
Created: 5 Apr 2024
+
Learn how to use computer vision to analyze wait times and optimize processes. This tutorial covers object detection, tracking, and calculating time spent in designated zones. Use these techniques to improve customer experience in retail, traffic management, or other scenarios.

+ +
+ +

+Speed Estimation & Vehicle Tracking | Computer Vision | Open Source +Speed Estimation & Vehicle Tracking | Computer Vision | Open Source +

Created: 11 Jan 2024
+
Learn how to track and estimate the speed of vehicles using YOLO, ByteTrack, and Roboflow Inference. This comprehensive tutorial covers object detection, multi-object tracking, filtering detections, perspective transformation, speed estimation, visualization improvements, and more.

## 💜 built with supervision From 6166d924588b8ca3fe6cf821f02a098a5fc34f67 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 08:21:26 -0400 Subject: [PATCH 030/136] fix reassigning track_id after a track is re-activated. --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 1fa98864..f35e62df 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -98,7 +98,7 @@ class STrack(BaseTrack): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if self.tracklet_len == self.minimum_consecutive_frames: + if self.tracklet_len == self.minimum_consecutive_frames and not self.is_activated: self.is_activated = True self.external_track_id = self.next_external_id() From 755f292ed1ef87f593cf2f057b9a95e7e84a2b59 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:21:44 +0000 Subject: [PATCH 031/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index f35e62df..10b7f0fe 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -98,7 +98,10 @@ class STrack(BaseTrack): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if self.tracklet_len == self.minimum_consecutive_frames and not self.is_activated: + if ( + self.tracklet_len == self.minimum_consecutive_frames + and not self.is_activated + ): self.is_activated = True self.external_track_id = self.next_external_id() From 7924a13dc59dd26f7cd2cc9e83c098fd4f453da5 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 08:28:08 -0400 Subject: [PATCH 032/136] only assign external track_id once --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 10b7f0fe..6c320073 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -100,7 +100,7 @@ class STrack(BaseTrack): self.state = TrackState.Tracked if ( self.tracklet_len == self.minimum_consecutive_frames - and not self.is_activated + and self.external_track_id == -1 ): self.is_activated = True self.external_track_id = self.next_external_id() From 263421d145d2d5836714365b59e537e75f21ee30 Mon Sep 17 00:00:00 2001 From: Piotr Skalski Date: Thu, 11 Apr 2024 14:40:43 +0200 Subject: [PATCH 033/136] Update README.md --- examples/README.md | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/examples/README.md b/examples/README.md index 81c8054b..b723a247 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,8 +1,8 @@ # 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. +Here, you'll find end-to-end examples that show how to solve common computer vision problems using Supervision. + +For more information and examples, visit our [documentation](https://supervision.roboflow.com/develop/annotators/) and explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/) and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/). Join our [Discord](https://discord.com/invite/GbfgXGJ8Bk) and meet other Supervision power users! - [tracking](./tracking) by [@SkalskiP](https://github.com/SkalskiP) - [count people in zone](./count_people_in_zone) by [@SkalskiP](https://github.com/SkalskiP) @@ -10,23 +10,3 @@ interfaces with diverse applications. - [speed estimation](./speed_estimation) by [@SkalskiP](https://github.com/SkalskiP) - [time in zone](./time_in_zone) by [@SkalskiP](https://github.com/SkalskiP) - [heatmap and track](./heatmap_and_track/) by [@HinePo](https://github.com/HinePo) - -## 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. From 9284bfdcd900f4ebe4b30f375c0f1d4de2ef2b41 Mon Sep 17 00:00:00 2001 From: Piotr Skalski Date: Thu, 11 Apr 2024 14:44:50 +0200 Subject: [PATCH 034/136] Update README.md --- examples/count_people_in_zone/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/count_people_in_zone/README.md b/examples/count_people_in_zone/README.md index 2e14a80b..d9d3cdb2 100644 --- a/examples/count_people_in_zone/README.md +++ b/examples/count_people_in_zone/README.md @@ -1,5 +1,8 @@ # count people in zone +[![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) +[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://www.youtube.com/watch?v=l_kf9CfZ_8M) + ## 👋 hello This demo is a video analysis tool that counts and highlights objects in specific zones From d3dbe7c59780307dec01ebb302f0f5762eaa3241 Mon Sep 17 00:00:00 2001 From: Piotr Skalski Date: Thu, 11 Apr 2024 14:54:22 +0200 Subject: [PATCH 035/136] Update README.md --- examples/time_in_zone/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/time_in_zone/README.md b/examples/time_in_zone/README.md index 2bfd6c7a..98587999 100644 --- a/examples/time_in_zone/README.md +++ b/examples/time_in_zone/README.md @@ -1,5 +1,7 @@ # time in zone +[![YouTube](https://badges.aleen42.com/src/youtube.svg)](https://www.youtube.com/watch?v=hAWpsIuem10) + ## 👋 hello Practical demonstration on leveraging computer vision for analyzing wait times and From b1d5d4faf07f28d3f99fcb36dc2e65eb65167776 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 09:21:32 -0400 Subject: [PATCH 036/136] fix to exactly match existing release version when minimum_consecutive_frames = 1 --- supervision/tracker/byte_tracker/core.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 6c320073..d7dc2461 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -65,8 +65,12 @@ class STrack(BaseTrack): self.tracklet_len = 0 self.state = TrackState.Tracked - if frame_id == 1 and self.minimum_consecutive_frames == 1: + if frame_id == 1: self.is_activated = True + + if self.minimum_consecutive_frames == 1: + self.external_track_id = self.next_external_id() + self.frame_id = frame_id self.start_frame = frame_id From bcb31f880958af32e2b7aa788f1afe37389458bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:21:48 +0000 Subject: [PATCH 037/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index d7dc2461..410c1050 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -70,7 +70,7 @@ class STrack(BaseTrack): if self.minimum_consecutive_frames == 1: self.external_track_id = self.next_external_id() - + self.frame_id = frame_id self.start_frame = frame_id From f98ded49a226557bc474cbc46253689023f9664b Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 09:27:24 -0400 Subject: [PATCH 038/136] fix --- supervision/tracker/byte_tracker/core.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index d7dc2461..8b072fa2 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -102,12 +102,10 @@ class STrack(BaseTrack): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if ( - self.tracklet_len == self.minimum_consecutive_frames - and self.external_track_id == -1 - ): + if (self.tracklet_len == self.minimum_consecutive_frames): self.is_activated = True - self.external_track_id = self.next_external_id() + if (self.external_track_id == -1): + self.external_track_id = self.next_external_id() self.score = new_track.score From b7607c6f152d73ee5556c67bdb6ff94b54d29847 Mon Sep 17 00:00:00 2001 From: Jeslin P James Date: Thu, 11 Apr 2024 18:57:30 +0530 Subject: [PATCH 039/136] Updated PolyZone class to compute frame_resolution_wh based on polygon coordinates --- supervision/detection/tools/polygon_zone.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index 00b746aa..f4825125 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -2,6 +2,7 @@ from dataclasses import replace from typing import Iterable, Optional, Tuple import cv2 +import warnings import numpy as np import numpy.typing as npt @@ -11,7 +12,7 @@ from supervision.draw.color import Color from supervision.draw.utils import draw_polygon, draw_text from supervision.geometry.core import Position from supervision.geometry.utils import get_polygon_center -from supervision.utils.internal import deprecated_parameter +from supervision.utils.internal import deprecated_parameter, SupervisionWarnings class PolygonZone: @@ -41,18 +42,24 @@ class PolygonZone: def __init__( self, polygon: npt.NDArray[np.int64], - frame_resolution_wh: Tuple[int, int], triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,), + frame_resolution_wh: Optional[Tuple[int, int]] = None, ): + if frame_resolution_wh is not None: + warnings.warn( + "The `frame_resolution_wh` parameter is no longer required and will be dropped in version supervision-0.24.0. The mask resolution is now calculated automatically based on the polygon coordinates.", + category=SupervisionWarnings, + ) + self.polygon = polygon.astype(int) - self.frame_resolution_wh = frame_resolution_wh self.triggering_anchors = triggering_anchors self.current_count = 0 - width, height = frame_resolution_wh + x_max, y_max = np.max(polygon, axis=0) + self.frame_resolution_wh = (x_max , y_max) self.mask = polygon_to_mask( - polygon=polygon, resolution_wh=(width + 1, height + 1) + polygon=polygon, resolution_wh=(x_max + 1, y_max + 1) ) def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]: From 361e444f53f2281e2b0297f5b267f66136292d5f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:27:40 +0000 Subject: [PATCH 040/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index cfaae130..55db6293 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -102,9 +102,9 @@ class STrack(BaseTrack): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if (self.tracklet_len == self.minimum_consecutive_frames): + if self.tracklet_len == self.minimum_consecutive_frames: self.is_activated = True - if (self.external_track_id == -1): + if self.external_track_id == -1: self.external_track_id = self.next_external_id() self.score = new_track.score From 28baf18cb6dcd6005fa030e35390028adfe350e8 Mon Sep 17 00:00:00 2001 From: Jeslin P James Date: Thu, 11 Apr 2024 18:59:48 +0530 Subject: [PATCH 041/136] updated PolygonZone Attributes docs --- supervision/detection/tools/polygon_zone.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index f4825125..72306e7e 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -22,13 +22,16 @@ class PolygonZone: Attributes: 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_anchors (Iterable[sv.Position]): A list of positions specifying which anchors of the detections bounding box to consider when deciding on whether the detection fits within the PolygonZone (default: (sv.Position.BOTTOM_CENTER,)). current_count (int): The current count of detected objects within the zone mask (np.ndarray): The 2D bool mask for the polygon zone + frame_resolution_wh (Optional[Tuple[int, int]], optional): DEPRECATED. The frame resolution + (width, height). This parameter is no longer required and will be dropped in version + supervision-0.24.0. The mask resolution is now calculated automatically based on the + polygon coordinates. Defaults to None. """ @deprecated_parameter( From b64c61c3885fdd449e0dfae9080c1c6073c75aed Mon Sep 17 00:00:00 2001 From: Jeslin P James Date: Thu, 11 Apr 2024 19:04:06 +0530 Subject: [PATCH 042/136] formated code --- supervision/detection/tools/polygon_zone.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index 72306e7e..ccc6ec95 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -50,9 +50,10 @@ class PolygonZone: ): if frame_resolution_wh is not None: warnings.warn( - "The `frame_resolution_wh` parameter is no longer required and will be dropped in version supervision-0.24.0. The mask resolution is now calculated automatically based on the polygon coordinates.", - category=SupervisionWarnings, - ) + "The `frame_resolution_wh` parameter is no longer required and will be dropped in version " + "supervision-0.24.0. The mask resolution is now calculated automatically based on the polygon coordinates.", + category=SupervisionWarnings, + ) self.polygon = polygon.astype(int) self.triggering_anchors = triggering_anchors @@ -60,7 +61,7 @@ class PolygonZone: self.current_count = 0 x_max, y_max = np.max(polygon, axis=0) - self.frame_resolution_wh = (x_max , y_max) + self.frame_resolution_wh = (x_max, y_max) self.mask = polygon_to_mask( polygon=polygon, resolution_wh=(x_max + 1, y_max + 1) ) @@ -100,6 +101,7 @@ class PolygonZone: return is_in_zone.astype(bool) + class PolygonZoneAnnotator: """ A class for annotating a polygon-shaped zone within a From 743e42080bcc4ba60ff7ffda03f8c50e52a6edfc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:57:48 +0000 Subject: [PATCH 043/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/tools/polygon_zone.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index ccc6ec95..f78bd609 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -1,8 +1,8 @@ +import warnings from dataclasses import replace from typing import Iterable, Optional, Tuple import cv2 -import warnings import numpy as np import numpy.typing as npt @@ -12,7 +12,7 @@ from supervision.draw.color import Color from supervision.draw.utils import draw_polygon, draw_text from supervision.geometry.core import Position from supervision.geometry.utils import get_polygon_center -from supervision.utils.internal import deprecated_parameter, SupervisionWarnings +from supervision.utils.internal import SupervisionWarnings, deprecated_parameter class PolygonZone: @@ -101,7 +101,6 @@ class PolygonZone: return is_in_zone.astype(bool) - class PolygonZoneAnnotator: """ A class for annotating a polygon-shaped zone within a From 7126adc74a38d080c430af76643af249d5a624b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:13:05 +0000 Subject: [PATCH 044/136] :arrow_up: Bump ruff from 0.3.5 to 0.3.6 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.5 to 0.3.6. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.3.5...v0.3.6) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index 40e19b9c..3bd87ec3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3643,28 +3643,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.5" +version = "0.3.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"}, - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"}, - {file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"}, - {file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"}, - {file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"}, - {file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"}, + {file = "ruff-0.3.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:732ef99984275534f9466fbc01121523caf72aa8c2bdeb36fd2edf2bc294a992"}, + {file = "ruff-0.3.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:93699d61116807edc5ca1cdf9d2d22cf8d93335d59e3ff0ca7aee62c1818a736"}, + {file = "ruff-0.3.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc4006cbc6c11fefc25f122d2eb4731d7a3d815dc74d67c54991cc3f99c90177"}, + {file = "ruff-0.3.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:878ef1a55ce931f3ca23b690b159cd0659f495a4c231a847b00ca55e4c688baf"}, + {file = "ruff-0.3.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb87788284af96725643eae9ab3ac746d8cc09aad140268523b019f7ac3cd98"}, + {file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b2e79f8e1b6bd5411d7ddad3f2abff3f9d371beda29daef86400d416dedb7e02"}, + {file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf48ec2c4bfae7837dc325c431a2932dc23a1485e71c59591c1df471ba234e0e"}, + {file = "ruff-0.3.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c466a52c522e6a08df0af018f550902f154f5649ad09e7f0d43da766e7399ebc"}, + {file = "ruff-0.3.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ccf3fb6d1162a73cd286c63a5e4d885f46a1f99f0b392924bc95ccbd18ea8f"}, + {file = "ruff-0.3.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b11e09439d9df6cc12d9f622065834654417c40216d271f639512d80e80e3e53"}, + {file = "ruff-0.3.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:647f1fb5128a3e24ce68878b8050bb55044c45bb3f3ae4710d4da9ca96ede5cb"}, + {file = "ruff-0.3.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2b0c4c70578ef1871a9ac5c85ed7a8c33470e976c73ba9211a111d2771b5f787"}, + {file = "ruff-0.3.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e3da499ded004d0b956ab04248b2ae17e54a67ffc81353514ac583af5959a255"}, + {file = "ruff-0.3.6-py3-none-win32.whl", hash = "sha256:4056480f5cf38ad278667c31b0ef334c29acdfcea617cb89c4ccbc7d96f1637f"}, + {file = "ruff-0.3.6-py3-none-win_amd64.whl", hash = "sha256:f1aa621beed533f46e9c7d6fe00e7f6e4570155b61d8f020387b72ace2b42e04"}, + {file = "ruff-0.3.6-py3-none-win_arm64.whl", hash = "sha256:7c8a2a0e0cab077a07465259ffe3b3c090e747ca8097c5dc4c36ca0fdaaac90d"}, + {file = "ruff-0.3.6.tar.gz", hash = "sha256:26071fb530038602b984e3bbe1443ef82a38450c4dcb1344a9caf67234ff9756"}, ] [[package]] From 89d84d1f7437bb9561ef2527fea5dac4770e2d53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 04:14:49 +0000 Subject: [PATCH 045/136] :arrow_up: Bump idna from 3.6 to 3.7 Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7) --- updated-dependencies: - dependency-name: idna dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 40e19b9c..932c51bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1122,13 +1122,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] From 510df5457255aa70cdb8a8daa24aedaa624d9dd9 Mon Sep 17 00:00:00 2001 From: Jeslin P James Date: Fri, 12 Apr 2024 16:16:48 +0530 Subject: [PATCH 046/136] Updated order of parameters in PolygonZone __init__() --- supervision/detection/tools/polygon_zone.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index ccc6ec95..fa71b124 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -1,8 +1,8 @@ +import warnings from dataclasses import replace from typing import Iterable, Optional, Tuple import cv2 -import warnings import numpy as np import numpy.typing as npt @@ -12,7 +12,7 @@ from supervision.draw.color import Color from supervision.draw.utils import draw_polygon, draw_text from supervision.geometry.core import Position from supervision.geometry.utils import get_polygon_center -from supervision.utils.internal import deprecated_parameter, SupervisionWarnings +from supervision.utils.internal import SupervisionWarnings, deprecated_parameter class PolygonZone: @@ -45,8 +45,8 @@ class PolygonZone: def __init__( self, polygon: npt.NDArray[np.int64], - triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,), frame_resolution_wh: Optional[Tuple[int, int]] = None, + triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,), ): if frame_resolution_wh is not None: warnings.warn( @@ -101,7 +101,6 @@ class PolygonZone: return is_in_zone.astype(bool) - class PolygonZoneAnnotator: """ A class for annotating a polygon-shaped zone within a From 0ad3796571e04b3435d1a31d4adec483a18412e0 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 12 Apr 2024 16:13:09 +0200 Subject: [PATCH 047/136] bug fix --- supervision/detection/tools/polygon_zone.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index fa71b124..244b793b 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -61,9 +61,9 @@ class PolygonZone: self.current_count = 0 x_max, y_max = np.max(polygon, axis=0) - self.frame_resolution_wh = (x_max, y_max) + self.frame_resolution_wh = (x_max + 1, y_max + 1) self.mask = polygon_to_mask( - polygon=polygon, resolution_wh=(x_max + 1, y_max + 1) + polygon=polygon, resolution_wh=(x_max + 2, y_max + 2) ) def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]: From 2c62f0b26dfdac73ce941e425c2325ad87f2499b Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 12 Apr 2024 16:15:10 +0200 Subject: [PATCH 048/136] drop `frame_resolution_wh` from docs --- supervision/detection/tools/polygon_zone.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index 244b793b..561cb9ce 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -28,10 +28,6 @@ class PolygonZone: (default: (sv.Position.BOTTOM_CENTER,)). current_count (int): The current count of detected objects within the zone mask (np.ndarray): The 2D bool mask for the polygon zone - frame_resolution_wh (Optional[Tuple[int, int]], optional): DEPRECATED. The frame resolution - (width, height). This parameter is no longer required and will be dropped in version - supervision-0.24.0. The mask resolution is now calculated automatically based on the - polygon coordinates. Defaults to None. """ @deprecated_parameter( From dfd9fc8e6d696eec671445136b8a50a5c2c901bd Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 12 Apr 2024 16:17:29 +0200 Subject: [PATCH 049/136] fix too long string --- supervision/detection/tools/polygon_zone.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index 561cb9ce..a1997212 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -46,8 +46,9 @@ class PolygonZone: ): if frame_resolution_wh is not None: warnings.warn( - "The `frame_resolution_wh` parameter is no longer required and will be dropped in version " - "supervision-0.24.0. The mask resolution is now calculated automatically based on the polygon coordinates.", + "The `frame_resolution_wh` parameter is no longer required and will be " + "dropped in version supervision-0.24.0. The mask resolution is now " + "calculated automatically based on the polygon coordinates.", category=SupervisionWarnings, ) From 26c2a7ef9cb17332795501de2f2f867bccd8c8e8 Mon Sep 17 00:00:00 2001 From: Jeslin P James Date: Sat, 13 Apr 2024 10:18:30 +0530 Subject: [PATCH 050/136] updated examples --- examples/count_people_in_zone/inference_example.py | 2 +- examples/count_people_in_zone/ultralytics_example.py | 2 +- examples/speed_estimation/inference_example.py | 4 +--- examples/speed_estimation/ultralytics_example.py | 4 +--- examples/speed_estimation/yolo_nas_example.py | 4 +--- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/examples/count_people_in_zone/inference_example.py b/examples/count_people_in_zone/inference_example.py index 6fd0f340..163f1771 100644 --- a/examples/count_people_in_zone/inference_example.py +++ b/examples/count_people_in_zone/inference_example.py @@ -46,7 +46,7 @@ def initiate_annotators( box_annotators = [] for index, polygon in enumerate(polygons): - zone = sv.PolygonZone(polygon=polygon, frame_resolution_wh=resolution_wh) + zone = sv.PolygonZone(polygon=polygon) zone_annotator = sv.PolygonZoneAnnotator( zone=zone, color=COLORS.by_idx(index), diff --git a/examples/count_people_in_zone/ultralytics_example.py b/examples/count_people_in_zone/ultralytics_example.py index cfd37bcc..861db951 100644 --- a/examples/count_people_in_zone/ultralytics_example.py +++ b/examples/count_people_in_zone/ultralytics_example.py @@ -44,7 +44,7 @@ def initiate_annotators( box_annotators = [] for index, polygon in enumerate(polygons): - zone = sv.PolygonZone(polygon=polygon, frame_resolution_wh=resolution_wh) + zone = sv.PolygonZone(polygon=polygon) zone_annotator = sv.PolygonZoneAnnotator( zone=zone, color=COLORS.by_idx(index), diff --git a/examples/speed_estimation/inference_example.py b/examples/speed_estimation/inference_example.py index b0ff84dd..a435261c 100644 --- a/examples/speed_estimation/inference_example.py +++ b/examples/speed_estimation/inference_example.py @@ -116,9 +116,7 @@ if __name__ == "__main__": frame_generator = sv.get_video_frames_generator(source_path=args.source_video_path) - polygon_zone = sv.PolygonZone( - polygon=SOURCE, frame_resolution_wh=video_info.resolution_wh - ) + polygon_zone = sv.PolygonZone(polygon=SOURCE) view_transformer = ViewTransformer(source=SOURCE, target=TARGET) coordinates = defaultdict(lambda: deque(maxlen=video_info.fps)) diff --git a/examples/speed_estimation/ultralytics_example.py b/examples/speed_estimation/ultralytics_example.py index 4b0436a0..3218d268 100644 --- a/examples/speed_estimation/ultralytics_example.py +++ b/examples/speed_estimation/ultralytics_example.py @@ -94,9 +94,7 @@ if __name__ == "__main__": frame_generator = sv.get_video_frames_generator(source_path=args.source_video_path) - polygon_zone = sv.PolygonZone( - polygon=SOURCE, frame_resolution_wh=video_info.resolution_wh - ) + polygon_zone = sv.PolygonZone(polygon=SOURCE) view_transformer = ViewTransformer(source=SOURCE, target=TARGET) coordinates = defaultdict(lambda: deque(maxlen=video_info.fps)) diff --git a/examples/speed_estimation/yolo_nas_example.py b/examples/speed_estimation/yolo_nas_example.py index 77f1fd0f..18798166 100644 --- a/examples/speed_estimation/yolo_nas_example.py +++ b/examples/speed_estimation/yolo_nas_example.py @@ -95,9 +95,7 @@ if __name__ == "__main__": frame_generator = sv.get_video_frames_generator(source_path=args.source_video_path) - polygon_zone = sv.PolygonZone( - polygon=SOURCE, frame_resolution_wh=video_info.resolution_wh - ) + polygon_zone = sv.PolygonZone(polygon=SOURCE) view_transformer = ViewTransformer(source=SOURCE, target=TARGET) coordinates = defaultdict(lambda: deque(maxlen=video_info.fps)) From 15b1473c2f71c6ff9e4c318efe95b73c168b3dc0 Mon Sep 17 00:00:00 2001 From: Jeslin P James Date: Sat, 13 Apr 2024 10:28:11 +0530 Subject: [PATCH 051/136] Updated examples to remove frame_resolution_wh from PolygonZone --- examples/time_in_zone/inference_file_example.py | 4 ---- examples/time_in_zone/inference_naive_stream_example.py | 4 ---- examples/time_in_zone/inference_stream_example.py | 2 -- examples/time_in_zone/ultralytics_file_example.py | 4 ---- examples/time_in_zone/ultralytics_naive_stream_example.py | 4 ---- examples/time_in_zone/ultralytics_stream_example.py | 2 -- examples/traffic_analysis/inference_example.py | 6 ++---- examples/traffic_analysis/ultralytics_example.py | 6 ++---- 8 files changed, 4 insertions(+), 28 deletions(-) diff --git a/examples/time_in_zone/inference_file_example.py b/examples/time_in_zone/inference_file_example.py index 5feb1d83..f4955464 100644 --- a/examples/time_in_zone/inference_file_example.py +++ b/examples/time_in_zone/inference_file_example.py @@ -29,14 +29,10 @@ def main( video_info = sv.VideoInfo.from_video_path(video_path=source_video_path) frames_generator = sv.get_video_frames_generator(source_video_path) - frame = next(frames_generator) - resolution_wh = frame.shape[1], frame.shape[0] - polygons = load_zones_config(file_path=zone_configuration_path) zones = [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=resolution_wh, triggering_anchors=(sv.Position.CENTER,), ) for polygon in polygons diff --git a/examples/time_in_zone/inference_naive_stream_example.py b/examples/time_in_zone/inference_naive_stream_example.py index dd2d68a5..21880269 100644 --- a/examples/time_in_zone/inference_naive_stream_example.py +++ b/examples/time_in_zone/inference_naive_stream_example.py @@ -29,14 +29,10 @@ def main( frames_generator = get_stream_frames_generator(rtsp_url=rtsp_url) fps_monitor = sv.FPSMonitor() - frame = next(frames_generator) - resolution_wh = frame.shape[1], frame.shape[0] - polygons = load_zones_config(file_path=zone_configuration_path) zones = [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=resolution_wh, triggering_anchors=(sv.Position.CENTER,), ) for polygon in polygons diff --git a/examples/time_in_zone/inference_stream_example.py b/examples/time_in_zone/inference_stream_example.py index e1fae57f..604aade4 100644 --- a/examples/time_in_zone/inference_stream_example.py +++ b/examples/time_in_zone/inference_stream_example.py @@ -28,11 +28,9 @@ class CustomSink: def on_prediction(self, result: dict, frame: VideoFrame) -> None: if self.zones is None: - resolution_wh = frame.image.shape[1], frame.image.shape[0] self.zones = [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=resolution_wh, triggering_anchors=(sv.Position.CENTER,), ) for polygon in self.polygons diff --git a/examples/time_in_zone/ultralytics_file_example.py b/examples/time_in_zone/ultralytics_file_example.py index fe8ce58d..b470b7a3 100644 --- a/examples/time_in_zone/ultralytics_file_example.py +++ b/examples/time_in_zone/ultralytics_file_example.py @@ -30,14 +30,10 @@ def main( video_info = sv.VideoInfo.from_video_path(video_path=source_video_path) frames_generator = sv.get_video_frames_generator(source_video_path) - frame = next(frames_generator) - resolution_wh = frame.shape[1], frame.shape[0] - polygons = load_zones_config(file_path=zone_configuration_path) zones = [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=resolution_wh, triggering_anchors=(sv.Position.CENTER,), ) for polygon in polygons diff --git a/examples/time_in_zone/ultralytics_naive_stream_example.py b/examples/time_in_zone/ultralytics_naive_stream_example.py index 1cc82b44..d6922143 100644 --- a/examples/time_in_zone/ultralytics_naive_stream_example.py +++ b/examples/time_in_zone/ultralytics_naive_stream_example.py @@ -30,14 +30,10 @@ def main( frames_generator = get_stream_frames_generator(rtsp_url=rtsp_url) fps_monitor = sv.FPSMonitor() - frame = next(frames_generator) - resolution_wh = frame.shape[1], frame.shape[0] - polygons = load_zones_config(file_path=zone_configuration_path) zones = [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=resolution_wh, triggering_anchors=(sv.Position.CENTER,), ) for polygon in polygons diff --git a/examples/time_in_zone/ultralytics_stream_example.py b/examples/time_in_zone/ultralytics_stream_example.py index 25dc874f..6262d594 100644 --- a/examples/time_in_zone/ultralytics_stream_example.py +++ b/examples/time_in_zone/ultralytics_stream_example.py @@ -29,11 +29,9 @@ class CustomSink: def on_prediction(self, detections: sv.Detections, frame: VideoFrame) -> None: if self.zones is None: - resolution_wh = frame.image.shape[1], frame.image.shape[0] self.zones = [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=resolution_wh, triggering_anchors=(sv.Position.CENTER,), ) for polygon in self.polygons diff --git a/examples/traffic_analysis/inference_example.py b/examples/traffic_analysis/inference_example.py index 7da5f37c..76096be2 100644 --- a/examples/traffic_analysis/inference_example.py +++ b/examples/traffic_analysis/inference_example.py @@ -60,13 +60,11 @@ class DetectionsManager: def initiate_polygon_zones( polygons: List[np.ndarray], - frame_resolution_wh: Tuple[int, int], triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER], ) -> List[sv.PolygonZone]: return [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=frame_resolution_wh, triggering_anchors=triggering_anchors, ) for polygon in polygons @@ -93,10 +91,10 @@ class VideoProcessor: self.video_info = sv.VideoInfo.from_video_path(source_video_path) self.zones_in = initiate_polygon_zones( - ZONE_IN_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER] + ZONE_IN_POLYGONS, [sv.Position.CENTER] ) self.zones_out = initiate_polygon_zones( - ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER] + ZONE_OUT_POLYGONS, [sv.Position.CENTER] ) self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS) diff --git a/examples/traffic_analysis/ultralytics_example.py b/examples/traffic_analysis/ultralytics_example.py index 78189d29..0872100e 100644 --- a/examples/traffic_analysis/ultralytics_example.py +++ b/examples/traffic_analysis/ultralytics_example.py @@ -58,13 +58,11 @@ class DetectionsManager: def initiate_polygon_zones( polygons: List[np.ndarray], - frame_resolution_wh: Tuple[int, int], triggering_anchors: Iterable[sv.Position] = [sv.Position.CENTER], ) -> List[sv.PolygonZone]: return [ sv.PolygonZone( polygon=polygon, - frame_resolution_wh=frame_resolution_wh, triggering_anchors=triggering_anchors, ) for polygon in polygons @@ -90,10 +88,10 @@ class VideoProcessor: self.video_info = sv.VideoInfo.from_video_path(source_video_path) self.zones_in = initiate_polygon_zones( - ZONE_IN_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER] + ZONE_IN_POLYGONS, [sv.Position.CENTER] ) self.zones_out = initiate_polygon_zones( - ZONE_OUT_POLYGONS, self.video_info.resolution_wh, [sv.Position.CENTER] + ZONE_OUT_POLYGONS, [sv.Position.CENTER] ) self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS) From d531c59f85333fcdcb969741261c2e5b705f0fe3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 13 Apr 2024 05:39:02 +0000 Subject: [PATCH 052/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/traffic_analysis/inference_example.py | 10 +++------- examples/traffic_analysis/ultralytics_example.py | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/examples/traffic_analysis/inference_example.py b/examples/traffic_analysis/inference_example.py index 76096be2..3cf750ad 100644 --- a/examples/traffic_analysis/inference_example.py +++ b/examples/traffic_analysis/inference_example.py @@ -1,6 +1,6 @@ import argparse import os -from typing import Dict, Iterable, List, Set, Tuple +from typing import Dict, Iterable, List, Set import cv2 import numpy as np @@ -90,12 +90,8 @@ class VideoProcessor: self.tracker = sv.ByteTrack() self.video_info = sv.VideoInfo.from_video_path(source_video_path) - self.zones_in = initiate_polygon_zones( - ZONE_IN_POLYGONS, [sv.Position.CENTER] - ) - self.zones_out = initiate_polygon_zones( - ZONE_OUT_POLYGONS, [sv.Position.CENTER] - ) + self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER]) + self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER]) self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS) self.label_annotator = sv.LabelAnnotator( diff --git a/examples/traffic_analysis/ultralytics_example.py b/examples/traffic_analysis/ultralytics_example.py index 0872100e..16ba6d8c 100644 --- a/examples/traffic_analysis/ultralytics_example.py +++ b/examples/traffic_analysis/ultralytics_example.py @@ -1,5 +1,5 @@ import argparse -from typing import Dict, Iterable, List, Set, Tuple +from typing import Dict, Iterable, List, Set import cv2 import numpy as np @@ -87,12 +87,8 @@ class VideoProcessor: self.tracker = sv.ByteTrack() self.video_info = sv.VideoInfo.from_video_path(source_video_path) - self.zones_in = initiate_polygon_zones( - ZONE_IN_POLYGONS, [sv.Position.CENTER] - ) - self.zones_out = initiate_polygon_zones( - ZONE_OUT_POLYGONS, [sv.Position.CENTER] - ) + self.zones_in = initiate_polygon_zones(ZONE_IN_POLYGONS, [sv.Position.CENTER]) + self.zones_out = initiate_polygon_zones(ZONE_OUT_POLYGONS, [sv.Position.CENTER]) self.bounding_box_annotator = sv.BoundingBoxAnnotator(color=COLORS) self.label_annotator = sv.LabelAnnotator( From 77d3276ad6a94dcbd728254153f3741397fc33aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 01:13:38 +0000 Subject: [PATCH 053/136] :arrow_up: Bump ruff from 0.3.6 to 0.3.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.6 to 0.3.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.3.6...v0.3.7) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7910b9f6..76edc310 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3643,28 +3643,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.6" +version = "0.3.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:732ef99984275534f9466fbc01121523caf72aa8c2bdeb36fd2edf2bc294a992"}, - {file = "ruff-0.3.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:93699d61116807edc5ca1cdf9d2d22cf8d93335d59e3ff0ca7aee62c1818a736"}, - {file = "ruff-0.3.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc4006cbc6c11fefc25f122d2eb4731d7a3d815dc74d67c54991cc3f99c90177"}, - {file = "ruff-0.3.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:878ef1a55ce931f3ca23b690b159cd0659f495a4c231a847b00ca55e4c688baf"}, - {file = "ruff-0.3.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb87788284af96725643eae9ab3ac746d8cc09aad140268523b019f7ac3cd98"}, - {file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b2e79f8e1b6bd5411d7ddad3f2abff3f9d371beda29daef86400d416dedb7e02"}, - {file = "ruff-0.3.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf48ec2c4bfae7837dc325c431a2932dc23a1485e71c59591c1df471ba234e0e"}, - {file = "ruff-0.3.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c466a52c522e6a08df0af018f550902f154f5649ad09e7f0d43da766e7399ebc"}, - {file = "ruff-0.3.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28ccf3fb6d1162a73cd286c63a5e4d885f46a1f99f0b392924bc95ccbd18ea8f"}, - {file = "ruff-0.3.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b11e09439d9df6cc12d9f622065834654417c40216d271f639512d80e80e3e53"}, - {file = "ruff-0.3.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:647f1fb5128a3e24ce68878b8050bb55044c45bb3f3ae4710d4da9ca96ede5cb"}, - {file = "ruff-0.3.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2b0c4c70578ef1871a9ac5c85ed7a8c33470e976c73ba9211a111d2771b5f787"}, - {file = "ruff-0.3.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e3da499ded004d0b956ab04248b2ae17e54a67ffc81353514ac583af5959a255"}, - {file = "ruff-0.3.6-py3-none-win32.whl", hash = "sha256:4056480f5cf38ad278667c31b0ef334c29acdfcea617cb89c4ccbc7d96f1637f"}, - {file = "ruff-0.3.6-py3-none-win_amd64.whl", hash = "sha256:f1aa621beed533f46e9c7d6fe00e7f6e4570155b61d8f020387b72ace2b42e04"}, - {file = "ruff-0.3.6-py3-none-win_arm64.whl", hash = "sha256:7c8a2a0e0cab077a07465259ffe3b3c090e747ca8097c5dc4c36ca0fdaaac90d"}, - {file = "ruff-0.3.6.tar.gz", hash = "sha256:26071fb530038602b984e3bbe1443ef82a38450c4dcb1344a9caf67234ff9756"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, + {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, + {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, + {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, + {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, + {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, + {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, + {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, ] [[package]] From 2af86e56f09c675db2f89471c60abe17d040a3c3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:52:01 +0000 Subject: [PATCH 054/136] =?UTF-8?q?chore(pre=5Fcommit):=20=E2=AC=86=20pre?= =?UTF-8?q?=5Fcommit=20autoupdate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.5 → v0.3.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.5...v0.3.7) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c2b1a7e..b016ca8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 + rev: v0.3.7 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From b3f022b15abc5644aa122a8ddfd527ef99f1bb0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 00:29:27 +0000 Subject: [PATCH 055/136] :arrow_up: Bump mkdocs-material from 9.5.17 to 9.5.18 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.17 to 9.5.18. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.17...9.5.18) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 76edc310..b5ce75b4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2180,13 +2180,13 @@ pygments = ">2.12.0" [[package]] name = "mkdocs-material" -version = "9.5.17" +version = "9.5.18" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.17-py3-none-any.whl", hash = "sha256:14a2a60119a785e70e765dd033e6211367aca9fc70230e577c1cf6a326949571"}, - {file = "mkdocs_material-9.5.17.tar.gz", hash = "sha256:06ae1275a72db1989cf6209de9e9ecdfbcfdbc24c58353877b2bb927dbe413e4"}, + {file = "mkdocs_material-9.5.18-py3-none-any.whl", hash = "sha256:1e0e27fc9fe239f9064318acf548771a4629d5fd5dfd45444fd80a953fe21eb4"}, + {file = "mkdocs_material-9.5.18.tar.gz", hash = "sha256:a43f470947053fa2405c33995f282d24992c752a50114f23f30da9d8d0c57e62"}, ] [package.dependencies] From 53eb76922de4783c41502e41c967d1fb30efdd8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 00:42:40 +0000 Subject: [PATCH 056/136] :arrow_up: Bump notebook from 7.1.2 to 7.1.3 Bumps [notebook](https://github.com/jupyter/notebook) from 7.1.2 to 7.1.3. - [Release notes](https://github.com/jupyter/notebook/releases) - [Changelog](https://github.com/jupyter/notebook/blob/@jupyter-notebook/tree@7.1.3/CHANGELOG.md) - [Commits](https://github.com/jupyter/notebook/compare/@jupyter-notebook/tree@7.1.2...@jupyter-notebook/tree@7.1.3) --- updated-dependencies: - dependency-name: notebook dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 76edc310..33e1f935 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2466,13 +2466,13 @@ setuptools = "*" [[package]] name = "notebook" -version = "7.1.2" +version = "7.1.3" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.8" files = [ - {file = "notebook-7.1.2-py3-none-any.whl", hash = "sha256:fc6c24b9aef18d0cd57157c9c47e95833b9b0bdc599652639acf0bdb61dc7d5f"}, - {file = "notebook-7.1.2.tar.gz", hash = "sha256:efc2c80043909e0faa17fce9e9b37c059c03af0ec99a4d4db84cb21d9d2e936a"}, + {file = "notebook-7.1.3-py3-none-any.whl", hash = "sha256:919b911e59f41f6e3857ce93c9d93535ba66bb090059712770e5968c07e1004d"}, + {file = "notebook-7.1.3.tar.gz", hash = "sha256:41fcebff44cf7bb9377180808bcbae066629b55d8c7722f1ebbe75ca44f9cfc1"}, ] [package.dependencies] From a1a66e7078fb32e5c4eba0ffa755d3a39c068e32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 00:17:17 +0000 Subject: [PATCH 057/136] :arrow_up: Bump ruff from 0.3.7 to 0.4.0 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.7 to 0.4.0. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.3.7...v0.4.0) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index ed0facf0..04cb6e95 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3643,28 +3643,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.7" +version = "0.4.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, - {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, - {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, - {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, - {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, + {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:70b8c620cf2212744eabd6d69c4f839f2be0d8880d27beaeb0adb6aa0b316aa8"}, + {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa3e3ff53be05a8c5570c1585ea1e089f6b399ca99fcb78598d4a8234f248db"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5616cca501d1d16b932b7e607d7e1fd1b8c8c51d6ee484b7940fc1adc5bea541"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46eff08dd480b5d9b540846159fe134d70e3c45a3c913c600047cbf7f0e4e308"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d546f511431fff2b17adcf7110f3b2c2c0c8d33b0e10e5fd27fd340bc617efc"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7b6b6b38e216036284c5779b6aa14acbf5664e3b5872533219cf93daf40ddfb"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e1cf8b064bb2a6b4922af7274fe2dffcb552d96ba716b2fbe5e2c970ed7de18"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9911c9046b94253e1fa844c0192bb764b86866a881502dee324686474d498c17"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ca7a971c8f1a0b6f5ff4a819c0d1c2619536530bbd5a289af725d8b2ef1013d"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:752e0f77f421141dd470a0b1bed4fd8f763aebabe32c80ed3580f740ef4ba807"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:84f2a5dd8f33964d826c5377e094f7ce11e55e432cd42d3bf64efe4384224a03"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b20e7db4a672495320a8a18149b7febf4e4f97509a4657367144569ce0915fd"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b0eddd339e24dc4f7719b1cde4967f6b6bc0ad948cc183711ba8910f14aeafe"}, + {file = "ruff-0.4.0-py3-none-win32.whl", hash = "sha256:e70befd488271a2c28c80bd427f73d8855dd222fc549fa1e9967d287c5cfe781"}, + {file = "ruff-0.4.0-py3-none-win_amd64.whl", hash = "sha256:8584b9361900997ccf8d7aaa4dc4ab43e258a853ca7189d98ac929dc9ee50875"}, + {file = "ruff-0.4.0-py3-none-win_arm64.whl", hash = "sha256:fea4ec813c965e40af29ee627a1579ee1d827d77e81d54b85bdd7b42d1540cdd"}, + {file = "ruff-0.4.0.tar.gz", hash = "sha256:7457308d9ebf00d6a1c9a26aa755e477787a636c90b823f91cd7d4bea9e89260"}, ] [[package]] From 98493c2ae1d15ee773f22b98a517101394d7769a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 00:45:54 +0000 Subject: [PATCH 058/136] :arrow_up: Bump ruff from 0.4.0 to 0.4.1 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.0 to 0.4.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.0...v0.4.1) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04cb6e95..95cbef01 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3643,28 +3643,28 @@ files = [ [[package]] name = "ruff" -version = "0.4.0" +version = "0.4.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:70b8c620cf2212744eabd6d69c4f839f2be0d8880d27beaeb0adb6aa0b316aa8"}, - {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa3e3ff53be05a8c5570c1585ea1e089f6b399ca99fcb78598d4a8234f248db"}, - {file = "ruff-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5616cca501d1d16b932b7e607d7e1fd1b8c8c51d6ee484b7940fc1adc5bea541"}, - {file = "ruff-0.4.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46eff08dd480b5d9b540846159fe134d70e3c45a3c913c600047cbf7f0e4e308"}, - {file = "ruff-0.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d546f511431fff2b17adcf7110f3b2c2c0c8d33b0e10e5fd27fd340bc617efc"}, - {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7b6b6b38e216036284c5779b6aa14acbf5664e3b5872533219cf93daf40ddfb"}, - {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e1cf8b064bb2a6b4922af7274fe2dffcb552d96ba716b2fbe5e2c970ed7de18"}, - {file = "ruff-0.4.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9911c9046b94253e1fa844c0192bb764b86866a881502dee324686474d498c17"}, - {file = "ruff-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ca7a971c8f1a0b6f5ff4a819c0d1c2619536530bbd5a289af725d8b2ef1013d"}, - {file = "ruff-0.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:752e0f77f421141dd470a0b1bed4fd8f763aebabe32c80ed3580f740ef4ba807"}, - {file = "ruff-0.4.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:84f2a5dd8f33964d826c5377e094f7ce11e55e432cd42d3bf64efe4384224a03"}, - {file = "ruff-0.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b20e7db4a672495320a8a18149b7febf4e4f97509a4657367144569ce0915fd"}, - {file = "ruff-0.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b0eddd339e24dc4f7719b1cde4967f6b6bc0ad948cc183711ba8910f14aeafe"}, - {file = "ruff-0.4.0-py3-none-win32.whl", hash = "sha256:e70befd488271a2c28c80bd427f73d8855dd222fc549fa1e9967d287c5cfe781"}, - {file = "ruff-0.4.0-py3-none-win_amd64.whl", hash = "sha256:8584b9361900997ccf8d7aaa4dc4ab43e258a853ca7189d98ac929dc9ee50875"}, - {file = "ruff-0.4.0-py3-none-win_arm64.whl", hash = "sha256:fea4ec813c965e40af29ee627a1579ee1d827d77e81d54b85bdd7b42d1540cdd"}, - {file = "ruff-0.4.0.tar.gz", hash = "sha256:7457308d9ebf00d6a1c9a26aa755e477787a636c90b823f91cd7d4bea9e89260"}, + {file = "ruff-0.4.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2d9ef6231e3fbdc0b8c72404a1a0c46fd0dcea84efca83beb4681c318ea6a953"}, + {file = "ruff-0.4.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9485f54a7189e6f7433e0058cf8581bee45c31a25cd69009d2a040d1bd4bfaef"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2921ac03ce1383e360e8a95442ffb0d757a6a7ddd9a5be68561a671e0e5807e"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eec8d185fe193ad053eda3a6be23069e0c8ba8c5d20bc5ace6e3b9e37d246d3f"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa27d9d72a94574d250f42b7640b3bd2edc4c58ac8ac2778a8c82374bb27984"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f1ee41580bff1a651339eb3337c20c12f4037f6110a36ae4a2d864c52e5ef954"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0926cefb57fc5fced629603fbd1a23d458b25418681d96823992ba975f050c2b"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6e37f2e3cd74496a74af9a4fa67b547ab3ca137688c484749189bf3a686ceb"}, + {file = "ruff-0.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd703a5975ac1998c2cc5e9494e13b28f31e66c616b0a76e206de2562e0843c"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b92f03b4aa9fa23e1799b40f15f8b95cdc418782a567d6c43def65e1bbb7f1cf"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c859f294f8633889e7d77de228b203eb0e9a03071b72b5989d89a0cf98ee262"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b34510141e393519a47f2d7b8216fec747ea1f2c81e85f076e9f2910588d4b64"}, + {file = "ruff-0.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6e68d248ed688b9d69fd4d18737edcbb79c98b251bba5a2b031ce2470224bdf9"}, + {file = "ruff-0.4.1-py3-none-win32.whl", hash = "sha256:b90506f3d6d1f41f43f9b7b5ff845aeefabed6d2494307bc7b178360a8805252"}, + {file = "ruff-0.4.1-py3-none-win_amd64.whl", hash = "sha256:c7d391e5936af5c9e252743d767c564670dc3889aff460d35c518ee76e4b26d7"}, + {file = "ruff-0.4.1-py3-none-win_arm64.whl", hash = "sha256:a1eaf03d87e6a7cd5e661d36d8c6e874693cb9bc3049d110bc9a97b350680c43"}, + {file = "ruff-0.4.1.tar.gz", hash = "sha256:d592116cdbb65f8b1b7e2a2b48297eb865f6bdc20641879aa9d7b9c11d86db79"}, ] [[package]] From feae6d3f78844a32ec03723eb91d6d37d6258d02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:43:51 +0000 Subject: [PATCH 059/136] =?UTF-8?q?chore(pre=5Fcommit):=20=E2=AC=86=20pre?= =?UTF-8?q?=5Fcommit=20autoupdate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.7 → v0.4.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.7...v0.4.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b016ca8d..ef3620b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.7 + rev: v0.4.1 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From a6b563cf4def3dbdca8dfeae60a5cfdbf87ff8b5 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Tue, 23 Apr 2024 17:18:34 +0300 Subject: [PATCH 060/136] Add keypoints skeleton, 2x annotators --- supervision/keypoints/annotate.py | 102 ++++++++++++++++++++++++++++++ supervision/keypoints/core.py | 43 ++++++++++++- 2 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 supervision/keypoints/annotate.py diff --git a/supervision/keypoints/annotate.py b/supervision/keypoints/annotate.py new file mode 100644 index 00000000..4c37b248 --- /dev/null +++ b/supervision/keypoints/annotate.py @@ -0,0 +1,102 @@ +from abc import ABC, abstractmethod + +import cv2 +import numpy as np + +from supervision.annotators.base import ImageType +from supervision.annotators.utils import scene_to_annotator_img_type +from supervision.draw.color import Color +from supervision.keypoints.core import KeyPoints, Skeleton + + +class BaseKeyPointAnnotator(ABC): + @abstractmethod + def annotate(self, scene: ImageType, detections: KeyPoints) -> ImageType: + pass + + +class PointAnnotator(BaseKeyPointAnnotator): + def __init__( + self, + color: Color = Color.GREEN, + radius: int = 4, + ) -> None: + """ + Most basic keypoint annotator. + """ + self.color = color + self.radius = radius + + @scene_to_annotator_img_type + def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: + # TODO: I'm getting shape [1, N, 2] here - not [N, 2]. + # Seems like it accounts for more than 1 person in an image. + if len(keypoints) == 0: + return scene + + xy = keypoints.xy[0] + for i, (x, y) in enumerate(xy): + cv2.circle( + img=scene, + center=(int(x), int(y)), + radius=self.radius, + color=self.color.as_bgr(), + thickness=-1, + ) + + return scene + + +class SkeletonAnnotator(BaseKeyPointAnnotator): + def __init__( + self, + point_color: Color = Color.GREEN, + point_radius: int = 4, + limb_color: Color = Color.GREEN, + limb_thickness: int = 2, + ) -> None: + self.point_color = point_color + self.point_radius = point_radius + self.limb_color = limb_color + self.limb_thickness = limb_thickness + + @scene_to_annotator_img_type + def annotate( + self, scene: ImageType, keypoints: KeyPoints, skeleton: Skeleton + ) -> ImageType: + if len(keypoints) == 0: + return scene + if keypoints.class_id is None: + raise ValueError("KeyPoints must have class_id to annotate a skeleton") + + # print(keypoints) + + xy = keypoints.xy[0] + class_id = keypoints.class_id[0] + + for i, (x, y) in enumerate(xy): + cv2.circle( + img=scene, + center=(int(x), int(y)), + radius=self.point_radius, + color=self.point_color.as_bgr(), + thickness=-1, + ) + + for class_a, class_b in skeleton.limbs: + xy_a = xy[class_a - 1] + xy_b = xy[class_b - 1] + missing_a = np.allclose(xy_a, 0) + missing_b = np.allclose(xy_b, 0) + if missing_a or missing_b: + continue + + cv2.line( + img=scene, + pt1=(int(xy_a[0]), int(xy_a[1])), + pt2=(int(xy_b[0]), int(xy_b[1])), + color=self.limb_color.as_bgr(), + thickness=self.limb_thickness, + ) + + return scene diff --git a/supervision/keypoints/core.py b/supervision/keypoints/core.py index 646b99db..25f14fae 100644 --- a/supervision/keypoints/core.py +++ b/supervision/keypoints/core.py @@ -132,14 +132,15 @@ class KeyPoints: result = model(image)[0] keypoints = sv.KeyPoints.from_ultralytics(result) ``` - """ # + """ + if len(ultralytics_results.keypoints.xy) == 0: + return cls.empty() + xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) class_names = np.array([ultralytics_results.names[i] for i in class_id]) - xy = ultralytics_results.keypoints.xy.cpu().numpy() confidence = ultralytics_results.keypoints.conf.cpu().numpy() - class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) data = {CLASS_NAME_DATA_FIELD: class_names} return cls(xy, class_id, confidence, data) @@ -238,3 +239,39 @@ class KeyPoints: ``` """ return cls(xy=np.empty((0, 0, 2), dtype=np.float32)) + + +class Skeleton: + """ + The `Skeleton` class connects keypoints to form a skeleton. + + It provides utility methods to compute angles within itself, compare to skeletons, etc. + """ + + limbs: List[Tuple[int, int]] + + def __init__(self, limbs: List[Tuple[int, int]]): + self.limbs = limbs + + +YOLO_V8_SKELETON = Skeleton( + [ + (1, 2), + (1, 3), + (2, 3), + (2, 4), + (3, 5), + (6, 12), + (6, 7), + (6, 8), + (7, 13), + (7, 9), + (8, 10), + (9, 11), + (12, 13), + (14, 12), + (15, 13), + (16, 14), + (17, 15), + ] +) From d079ce92df781fb43ee46d8047faa795fb066983 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Tue, 23 Apr 2024 19:26:12 +0300 Subject: [PATCH 061/136] Keypoints: Separate-out skeleton annotator --- supervision/keypoints/annotate.py | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/supervision/keypoints/annotate.py b/supervision/keypoints/annotate.py index 4c37b248..b82ea071 100644 --- a/supervision/keypoints/annotate.py +++ b/supervision/keypoints/annotate.py @@ -50,15 +50,11 @@ class PointAnnotator(BaseKeyPointAnnotator): class SkeletonAnnotator(BaseKeyPointAnnotator): def __init__( self, - point_color: Color = Color.GREEN, - point_radius: int = 4, - limb_color: Color = Color.GREEN, - limb_thickness: int = 2, + color: Color = Color.GREEN, + thickness: int = 2, ) -> None: - self.point_color = point_color - self.point_radius = point_radius - self.limb_color = limb_color - self.limb_thickness = limb_thickness + self.color = color + self.thickness = thickness @scene_to_annotator_img_type def annotate( @@ -67,21 +63,10 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): if len(keypoints) == 0: return scene if keypoints.class_id is None: - raise ValueError("KeyPoints must have class_id to annotate a skeleton") - - # print(keypoints) + raise ValueError( + "KeyPoints must have class_id to annotate a skeleton") xy = keypoints.xy[0] - class_id = keypoints.class_id[0] - - for i, (x, y) in enumerate(xy): - cv2.circle( - img=scene, - center=(int(x), int(y)), - radius=self.point_radius, - color=self.point_color.as_bgr(), - thickness=-1, - ) for class_a, class_b in skeleton.limbs: xy_a = xy[class_a - 1] @@ -95,8 +80,8 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): img=scene, pt1=(int(xy_a[0]), int(xy_a[1])), pt2=(int(xy_b[0]), int(xy_b[1])), - color=self.limb_color.as_bgr(), - thickness=self.limb_thickness, + color=self.color.as_bgr(), + thickness=self.thickness, ) return scene From a00604bfb24c609727a4b26cea923d62602a07fc Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Tue, 23 Apr 2024 19:28:34 +0300 Subject: [PATCH 062/136] Keypoints: Expose KeyPoints, annotators in __init__ --- supervision/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/supervision/__init__.py b/supervision/__init__.py index df04534d..ad02202d 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -56,6 +56,8 @@ from supervision.detection.utils import ( polygon_to_xyxy, scale_boxes, ) +from supervision.keypoints.core import KeyPoints, YOLO_V8_SKELETON +from supervision.keypoints.annotate import PointAnnotator, SkeletonAnnotator from supervision.draw.color import Color, ColorPalette from supervision.draw.utils import ( calculate_optimal_line_thickness, From 7f0ecbbd0a2f76f9a54a36449a48033d87df83fc Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Tue, 23 Apr 2024 20:19:28 +0300 Subject: [PATCH 063/136] Keypoints: Smart skellies --- supervision/__init__.py | 5 +- supervision/keypoints/annotate.py | 30 +++++++-- supervision/keypoints/core.py | 42 ++----------- supervision/keypoints/skeletons.py | 97 ++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 supervision/keypoints/skeletons.py diff --git a/supervision/__init__.py b/supervision/__init__.py index ad02202d..549e870e 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -56,8 +56,9 @@ from supervision.detection.utils import ( polygon_to_xyxy, scale_boxes, ) -from supervision.keypoints.core import KeyPoints, YOLO_V8_SKELETON -from supervision.keypoints.annotate import PointAnnotator, SkeletonAnnotator +from supervision.keypoints.core import KeyPoints +from supervision.keypoints.skeletons import KnownSkeletons, Skeleton +from supervision.keypoints.annotate import KeyPointAnnotator, SkeletonAnnotator from supervision.draw.color import Color, ColorPalette from supervision.draw.utils import ( calculate_optimal_line_thickness, diff --git a/supervision/keypoints/annotate.py b/supervision/keypoints/annotate.py index b82ea071..0511def4 100644 --- a/supervision/keypoints/annotate.py +++ b/supervision/keypoints/annotate.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from typing import Optional import cv2 import numpy as np @@ -6,7 +7,8 @@ import numpy as np from supervision.annotators.base import ImageType from supervision.annotators.utils import scene_to_annotator_img_type from supervision.draw.color import Color -from supervision.keypoints.core import KeyPoints, Skeleton +from supervision.keypoints.core import KeyPoints +from supervision.keypoints.skeletons import Skeleton, KnownSkeletons class BaseKeyPointAnnotator(ABC): @@ -15,14 +17,18 @@ class BaseKeyPointAnnotator(ABC): pass -class PointAnnotator(BaseKeyPointAnnotator): +class KeyPointAnnotator(BaseKeyPointAnnotator): def __init__( self, - color: Color = Color.GREEN, + color: Color = Color.ROBOFLOW, radius: int = 4, ) -> None: """ Most basic keypoint annotator. + + Args: + color (Color, optional): The color of the keypoint. + radius (int, optional): The radius of the keypoint. """ self.color = color self.radius = radius @@ -50,15 +56,26 @@ class PointAnnotator(BaseKeyPointAnnotator): class SkeletonAnnotator(BaseKeyPointAnnotator): def __init__( self, - color: Color = Color.GREEN, + color: Color = Color.ROBOFLOW, thickness: int = 2, + skeleton: Optional[Skeleton] = None, ) -> None: + """ + Draw the lines between points of the image. + + Args: + color (Color, optional): The color of the lines. + thickness (int, optional): The thickness of the lines. + skeleton (Skeleton, optional): The skeleton to draw. + If set to `None`, will attempt to select automatically. + """ self.color = color self.thickness = thickness + self.skeleton = skeleton @scene_to_annotator_img_type def annotate( - self, scene: ImageType, keypoints: KeyPoints, skeleton: Skeleton + self, scene: ImageType, keypoints: KeyPoints ) -> ImageType: if len(keypoints) == 0: return scene @@ -67,6 +84,9 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): "KeyPoints must have class_id to annotate a skeleton") xy = keypoints.xy[0] + skeleton = self.skeleton + if not skeleton: + skeleton = KnownSkeletons().get_skeleton(len(xy)) for class_a, class_b in skeleton.limbs: xy_a = xy[class_a - 1] diff --git a/supervision/keypoints/core.py b/supervision/keypoints/core.py index 25f14fae..5aa0a4d1 100644 --- a/supervision/keypoints/core.py +++ b/supervision/keypoints/core.py @@ -58,7 +58,8 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List] + ] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -138,7 +139,8 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] for i in class_id]) + class_names = np.array([ultralytics_results.names[i] + for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} @@ -239,39 +241,3 @@ class KeyPoints: ``` """ return cls(xy=np.empty((0, 0, 2), dtype=np.float32)) - - -class Skeleton: - """ - The `Skeleton` class connects keypoints to form a skeleton. - - It provides utility methods to compute angles within itself, compare to skeletons, etc. - """ - - limbs: List[Tuple[int, int]] - - def __init__(self, limbs: List[Tuple[int, int]]): - self.limbs = limbs - - -YOLO_V8_SKELETON = Skeleton( - [ - (1, 2), - (1, 3), - (2, 3), - (2, 4), - (3, 5), - (6, 12), - (6, 7), - (6, 8), - (7, 13), - (7, 9), - (8, 10), - (9, 11), - (12, 13), - (14, 12), - (15, 13), - (16, 14), - (17, 15), - ] -) diff --git a/supervision/keypoints/skeletons.py b/supervision/keypoints/skeletons.py new file mode 100644 index 00000000..a9d8d01f --- /dev/null +++ b/supervision/keypoints/skeletons.py @@ -0,0 +1,97 @@ + + +from typing import List, Tuple + + +class Skeleton: + """ + The `Skeleton` class connects keypoints to form a skeleton. + + Args: + limbs: (List[Tuple[int, int]]): List of (`class_id_1`, + `class_id_2`) representing connected points of the skeleton. + """ + + limbs: List[Tuple[int, int]] + + def __init__(self, limbs: List[Tuple[int, int]]): + self.limbs = limbs + + +NO_SKELETON = Skeleton([]) +YOLO_V8_SKELETON = Skeleton( + [ + (1, 2), + (1, 3), + (2, 3), + (2, 4), + (3, 5), + (6, 12), + (6, 7), + (6, 8), + (7, 13), + (7, 9), + (8, 10), + (9, 11), + (12, 13), + (14, 12), + (15, 13), + (16, 14), + (17, 15), + ] +) + + +def _make_sequential_skeleton(limb_count: int) -> Skeleton: + """ + Create a skeleton where neighboring keypoints are connected sequentially. + + Args: + limb_count (int): The number of limbs to connect. + + Returns: + Skeleton: The skeleton connecting all keypoints. + """ + limbs = [] + for i in range(0, limb_count - 1): + limbs.append((i, i + 1)) + limbs.append((0, limb_count - 1)) + return Skeleton(limbs) + + +class KnownSkeletons: + """ + Helps automatically determine which skeleton to use. + + The `KnownSkeletons` class contains a collection of predefined skeletons + for various keypoint models, to be distinguished by limb counts. + + Example: + # Suppose you have a model returning 15 keypoints. + skeleton = KnownSkeletons().get_skeleton(15) + """ + _instance = None + _skeletons: dict[int, Skeleton] = {} + + def __new__(cls): + if cls._instance is None: + cls._instance = super(KnownSkeletons, cls).__new__(cls) + return cls._instance + + def add_skeleton(self, skeleton: Skeleton) -> None: + len_limbs = len(skeleton.limbs) + if len_limbs in self._skeletons: + raise ValueError( + f"A skeleton with {len_limbs} limbs already exists") + self._skeletons[len_limbs] = skeleton + + def get_skeleton(self, limb_count: int) -> Skeleton: + if limb_count not in self._skeletons: + print( + f"Warning: No skeleton found with {limb_count} limbs. Will create one now.") + self.add_skeleton(_make_sequential_skeleton(limb_count)) + return self._skeletons[limb_count] + + +KnownSkeletons().add_skeleton(NO_SKELETON) +KnownSkeletons().add_skeleton(YOLO_V8_SKELETON) From 07482a347cfcbbfb7bf7d130541146c211c8f896 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Tue, 23 Apr 2024 20:43:14 +0300 Subject: [PATCH 064/136] Keypoints: Multi-person annotators --- supervision/keypoints/annotate.py | 56 ++++++++++++++++--------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/supervision/keypoints/annotate.py b/supervision/keypoints/annotate.py index 0511def4..e051fa8e 100644 --- a/supervision/keypoints/annotate.py +++ b/supervision/keypoints/annotate.py @@ -40,15 +40,16 @@ class KeyPointAnnotator(BaseKeyPointAnnotator): if len(keypoints) == 0: return scene - xy = keypoints.xy[0] - for i, (x, y) in enumerate(xy): - cv2.circle( - img=scene, - center=(int(x), int(y)), - radius=self.radius, - color=self.color.as_bgr(), - thickness=-1, - ) + xy_all = keypoints.xy + for xy in xy_all: + for x, y in xy: + cv2.circle( + img=scene, + center=(int(x), int(y)), + radius=self.radius, + color=self.color.as_bgr(), + thickness=-1, + ) return scene @@ -83,25 +84,26 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): raise ValueError( "KeyPoints must have class_id to annotate a skeleton") - xy = keypoints.xy[0] - skeleton = self.skeleton - if not skeleton: - skeleton = KnownSkeletons().get_skeleton(len(xy)) + xy_all = keypoints.xy + for xy in xy_all: + skeleton = self.skeleton + if not skeleton: + skeleton = KnownSkeletons().get_skeleton(len(xy)) - for class_a, class_b in skeleton.limbs: - xy_a = xy[class_a - 1] - xy_b = xy[class_b - 1] - missing_a = np.allclose(xy_a, 0) - missing_b = np.allclose(xy_b, 0) - if missing_a or missing_b: - continue + for class_a, class_b in skeleton.limbs: + xy_a = xy[class_a - 1] + xy_b = xy[class_b - 1] + missing_a = np.allclose(xy_a, 0) + missing_b = np.allclose(xy_b, 0) + if missing_a or missing_b: + continue - cv2.line( - img=scene, - pt1=(int(xy_a[0]), int(xy_a[1])), - pt2=(int(xy_b[0]), int(xy_b[1])), - color=self.color.as_bgr(), - thickness=self.thickness, - ) + cv2.line( + img=scene, + pt1=(int(xy_a[0]), int(xy_a[1])), + pt2=(int(xy_b[0]), int(xy_b[1])), + color=self.color.as_bgr(), + thickness=self.thickness, + ) return scene From 409dcbd9da5d1aeb5c3a7746163be87bfaf3628d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 20:49:03 +0000 Subject: [PATCH 065/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/__init__.py | 5 ++--- supervision/keypoints/annotate.py | 9 +++------ supervision/keypoints/core.py | 6 ++---- supervision/keypoints/skeletons.py | 9 ++++----- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index 549e870e..c4883335 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -56,9 +56,6 @@ from supervision.detection.utils import ( polygon_to_xyxy, scale_boxes, ) -from supervision.keypoints.core import KeyPoints -from supervision.keypoints.skeletons import KnownSkeletons, Skeleton -from supervision.keypoints.annotate import KeyPointAnnotator, SkeletonAnnotator from supervision.draw.color import Color, ColorPalette from supervision.draw.utils import ( calculate_optimal_line_thickness, @@ -72,7 +69,9 @@ from supervision.draw.utils import ( ) from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center +from supervision.keypoints.annotate import KeyPointAnnotator, SkeletonAnnotator from supervision.keypoints.core import KeyPoints +from supervision.keypoints.skeletons import KnownSkeletons, Skeleton from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision from supervision.tracker.byte_tracker.core import ByteTrack from supervision.utils.file import list_files_with_extensions diff --git a/supervision/keypoints/annotate.py b/supervision/keypoints/annotate.py index e051fa8e..978c1e8d 100644 --- a/supervision/keypoints/annotate.py +++ b/supervision/keypoints/annotate.py @@ -8,7 +8,7 @@ from supervision.annotators.base import ImageType from supervision.annotators.utils import scene_to_annotator_img_type from supervision.draw.color import Color from supervision.keypoints.core import KeyPoints -from supervision.keypoints.skeletons import Skeleton, KnownSkeletons +from supervision.keypoints.skeletons import KnownSkeletons, Skeleton class BaseKeyPointAnnotator(ABC): @@ -75,14 +75,11 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): self.skeleton = skeleton @scene_to_annotator_img_type - def annotate( - self, scene: ImageType, keypoints: KeyPoints - ) -> ImageType: + def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: if len(keypoints) == 0: return scene if keypoints.class_id is None: - raise ValueError( - "KeyPoints must have class_id to annotate a skeleton") + raise ValueError("KeyPoints must have class_id to annotate a skeleton") xy_all = keypoints.xy for xy in xy_all: diff --git a/supervision/keypoints/core.py b/supervision/keypoints/core.py index 5aa0a4d1..2b432be4 100644 --- a/supervision/keypoints/core.py +++ b/supervision/keypoints/core.py @@ -58,8 +58,7 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List] - ] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -139,8 +138,7 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] - for i in class_id]) + class_names = np.array([ultralytics_results.names[i] for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} diff --git a/supervision/keypoints/skeletons.py b/supervision/keypoints/skeletons.py index a9d8d01f..03a4f335 100644 --- a/supervision/keypoints/skeletons.py +++ b/supervision/keypoints/skeletons.py @@ -1,5 +1,3 @@ - - from typing import List, Tuple @@ -70,6 +68,7 @@ class KnownSkeletons: # Suppose you have a model returning 15 keypoints. skeleton = KnownSkeletons().get_skeleton(15) """ + _instance = None _skeletons: dict[int, Skeleton] = {} @@ -81,14 +80,14 @@ class KnownSkeletons: def add_skeleton(self, skeleton: Skeleton) -> None: len_limbs = len(skeleton.limbs) if len_limbs in self._skeletons: - raise ValueError( - f"A skeleton with {len_limbs} limbs already exists") + raise ValueError(f"A skeleton with {len_limbs} limbs already exists") self._skeletons[len_limbs] = skeleton def get_skeleton(self, limb_count: int) -> Skeleton: if limb_count not in self._skeletons: print( - f"Warning: No skeleton found with {limb_count} limbs. Will create one now.") + f"Warning: No skeleton found with {limb_count} limbs. Will create one now." + ) self.add_skeleton(_make_sequential_skeleton(limb_count)) return self._skeletons[limb_count] From 0274c45dbc086b75144a85a30af0630adf94fadf Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 23 Apr 2024 23:12:54 +0200 Subject: [PATCH 066/136] solve CI tests --- supervision/keypoints/annotate.py | 6 +++--- supervision/keypoints/skeletons.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/supervision/keypoints/annotate.py b/supervision/keypoints/annotate.py index 978c1e8d..0a837946 100644 --- a/supervision/keypoints/annotate.py +++ b/supervision/keypoints/annotate.py @@ -5,10 +5,10 @@ import cv2 import numpy as np from supervision.annotators.base import ImageType -from supervision.annotators.utils import scene_to_annotator_img_type from supervision.draw.color import Color from supervision.keypoints.core import KeyPoints from supervision.keypoints.skeletons import KnownSkeletons, Skeleton +from supervision.utils.conversion import convert_for_annotation_method class BaseKeyPointAnnotator(ABC): @@ -33,7 +33,7 @@ class KeyPointAnnotator(BaseKeyPointAnnotator): self.color = color self.radius = radius - @scene_to_annotator_img_type + @convert_for_annotation_method def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: # TODO: I'm getting shape [1, N, 2] here - not [N, 2]. # Seems like it accounts for more than 1 person in an image. @@ -74,7 +74,7 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): self.thickness = thickness self.skeleton = skeleton - @scene_to_annotator_img_type + @convert_for_annotation_method def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: if len(keypoints) == 0: return scene diff --git a/supervision/keypoints/skeletons.py b/supervision/keypoints/skeletons.py index 03a4f335..d150b661 100644 --- a/supervision/keypoints/skeletons.py +++ b/supervision/keypoints/skeletons.py @@ -86,7 +86,8 @@ class KnownSkeletons: def get_skeleton(self, limb_count: int) -> Skeleton: if limb_count not in self._skeletons: print( - f"Warning: No skeleton found with {limb_count} limbs. Will create one now." + f"Warning: No skeleton found with {limb_count} limbs. " + f"Will create one now." ) self.add_skeleton(_make_sequential_skeleton(limb_count)) return self._skeletons[limb_count] From 9789ff4043b0c6cf7a70b62a9deac1bceb807be5 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 23 Apr 2024 23:12:54 +0200 Subject: [PATCH 067/136] solve CI tests --- supervision/keypoints/annotate.py | 6 +++--- supervision/keypoints/skeletons.py | 7 ++++--- supervision/keypoints/utils.py | 0 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 supervision/keypoints/utils.py diff --git a/supervision/keypoints/annotate.py b/supervision/keypoints/annotate.py index 978c1e8d..0a837946 100644 --- a/supervision/keypoints/annotate.py +++ b/supervision/keypoints/annotate.py @@ -5,10 +5,10 @@ import cv2 import numpy as np from supervision.annotators.base import ImageType -from supervision.annotators.utils import scene_to_annotator_img_type from supervision.draw.color import Color from supervision.keypoints.core import KeyPoints from supervision.keypoints.skeletons import KnownSkeletons, Skeleton +from supervision.utils.conversion import convert_for_annotation_method class BaseKeyPointAnnotator(ABC): @@ -33,7 +33,7 @@ class KeyPointAnnotator(BaseKeyPointAnnotator): self.color = color self.radius = radius - @scene_to_annotator_img_type + @convert_for_annotation_method def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: # TODO: I'm getting shape [1, N, 2] here - not [N, 2]. # Seems like it accounts for more than 1 person in an image. @@ -74,7 +74,7 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): self.thickness = thickness self.skeleton = skeleton - @scene_to_annotator_img_type + @convert_for_annotation_method def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: if len(keypoints) == 0: return scene diff --git a/supervision/keypoints/skeletons.py b/supervision/keypoints/skeletons.py index 03a4f335..5b072275 100644 --- a/supervision/keypoints/skeletons.py +++ b/supervision/keypoints/skeletons.py @@ -1,4 +1,4 @@ -from typing import List, Tuple +from typing import List, Tuple, Dict class Skeleton: @@ -70,7 +70,7 @@ class KnownSkeletons: """ _instance = None - _skeletons: dict[int, Skeleton] = {} + _skeletons: Dict[int, Skeleton] = {} def __new__(cls): if cls._instance is None: @@ -86,7 +86,8 @@ class KnownSkeletons: def get_skeleton(self, limb_count: int) -> Skeleton: if limb_count not in self._skeletons: print( - f"Warning: No skeleton found with {limb_count} limbs. Will create one now." + f"Warning: No skeleton found with {limb_count} limbs. " + f"Will create one now." ) self.add_skeleton(_make_sequential_skeleton(limb_count)) return self._skeletons[limb_count] diff --git a/supervision/keypoints/utils.py b/supervision/keypoints/utils.py deleted file mode 100644 index e69de29b..00000000 From e2fabc1cfe38ea815100b6651c6ea5d359c138f5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 21:57:28 +0000 Subject: [PATCH 068/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoints/skeletons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/keypoints/skeletons.py b/supervision/keypoints/skeletons.py index 5b072275..dd8c7ec8 100644 --- a/supervision/keypoints/skeletons.py +++ b/supervision/keypoints/skeletons.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Dict +from typing import Dict, List, Tuple class Skeleton: From 2745c70ae0e558c18afef7a0c67d0d88d6311f89 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 00:21:19 +0200 Subject: [PATCH 069/136] docs plugged into `mkdocs.yml` --- docs/keypoint/annotate.md | 10 ++++++++++ docs/keypoint/core.md | 8 ++++++++ mkdocs.yml | 3 +++ supervision/__init__.py | 6 +++--- supervision/{keypoints => keypoint}/__init__.py | 0 supervision/{keypoints => keypoint}/annotate.py | 8 +++----- supervision/{keypoints => keypoint}/core.py | 8 +++++--- supervision/{keypoints => keypoint}/skeletons.py | 0 8 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 docs/keypoint/annotate.md create mode 100644 docs/keypoint/core.md rename supervision/{keypoints => keypoint}/__init__.py (100%) rename supervision/{keypoints => keypoint}/annotate.py (90%) rename supervision/{keypoints => keypoint}/core.py (97%) rename supervision/{keypoints => keypoint}/skeletons.py (100%) diff --git a/docs/keypoint/annotate.md b/docs/keypoint/annotate.md new file mode 100644 index 00000000..8507f54e --- /dev/null +++ b/docs/keypoint/annotate.md @@ -0,0 +1,10 @@ +--- +comments: true +status: new +--- + +# Annotate + +:::supervision.keypoint.annotate.VertexAnnotator + +:::supervision.keypoint.annotate.SkeletonAnnotator \ No newline at end of file diff --git a/docs/keypoint/core.md b/docs/keypoint/core.md new file mode 100644 index 00000000..7354baba --- /dev/null +++ b/docs/keypoint/core.md @@ -0,0 +1,8 @@ +--- +comments: true +status: new +--- + +# Keypoint Detection + +:::supervision.keypoint.core.KeyPoints diff --git a/mkdocs.yml b/mkdocs.yml index dd6feb8d..256c6bef 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,6 +50,9 @@ nav: - Detections: - Core: detection/core.md - Utils: detection/utils.md + - KeyPoints: + - Core: keypoint/core.md + - Annotators: keypoint/annotate.md - Tools: - Line Zone: detection/tools/line_zone.md - Polygon Zone: detection/tools/polygon_zone.md diff --git a/supervision/__init__.py b/supervision/__init__.py index c25ec791..70c912c0 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -69,9 +69,9 @@ from supervision.draw.utils import ( ) from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center -from supervision.keypoints.annotate import KeyPointAnnotator, SkeletonAnnotator -from supervision.keypoints.core import KeyPoints -from supervision.keypoints.skeletons import KnownSkeletons, Skeleton +from supervision.keypoint.annotate import VertexAnnotator, SkeletonAnnotator +from supervision.keypoint.core import KeyPoints +from supervision.keypoint.skeletons import KnownSkeletons, Skeleton from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision from supervision.tracker.byte_tracker.core import ByteTrack from supervision.utils.conversion import cv2_to_pillow, pillow_to_cv2 diff --git a/supervision/keypoints/__init__.py b/supervision/keypoint/__init__.py similarity index 100% rename from supervision/keypoints/__init__.py rename to supervision/keypoint/__init__.py diff --git a/supervision/keypoints/annotate.py b/supervision/keypoint/annotate.py similarity index 90% rename from supervision/keypoints/annotate.py rename to supervision/keypoint/annotate.py index 0a837946..802199f3 100644 --- a/supervision/keypoints/annotate.py +++ b/supervision/keypoint/annotate.py @@ -6,8 +6,8 @@ import numpy as np from supervision.annotators.base import ImageType from supervision.draw.color import Color -from supervision.keypoints.core import KeyPoints -from supervision.keypoints.skeletons import KnownSkeletons, Skeleton +from supervision.keypoint.core import KeyPoints +from supervision.keypoint.skeletons import KnownSkeletons, Skeleton from supervision.utils.conversion import convert_for_annotation_method @@ -17,7 +17,7 @@ class BaseKeyPointAnnotator(ABC): pass -class KeyPointAnnotator(BaseKeyPointAnnotator): +class VertexAnnotator(BaseKeyPointAnnotator): def __init__( self, color: Color = Color.ROBOFLOW, @@ -35,8 +35,6 @@ class KeyPointAnnotator(BaseKeyPointAnnotator): @convert_for_annotation_method def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: - # TODO: I'm getting shape [1, N, 2] here - not [N, 2]. - # Seems like it accounts for more than 1 person in an image. if len(keypoints) == 0: return scene diff --git a/supervision/keypoints/core.py b/supervision/keypoint/core.py similarity index 97% rename from supervision/keypoints/core.py rename to supervision/keypoint/core.py index 2b432be4..aca50ad5 100644 --- a/supervision/keypoints/core.py +++ b/supervision/keypoint/core.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass, field from typing import Any, Dict, Iterator, List, Optional, Tuple, Union @@ -98,7 +100,7 @@ class KeyPoints: get_data_item(self.data, i), ) - def __eq__(self, other: "KeyPoints") -> bool: + def __eq__(self, other: KeyPoints) -> bool: return all( [ np.array_equal(self.xy, other.xy), @@ -109,7 +111,7 @@ class KeyPoints: ) @classmethod - def from_ultralytics(cls, ultralytics_results) -> "KeyPoints": + def from_ultralytics(cls, ultralytics_results) -> KeyPoints: """ Creates a Keypoints instance from a [YOLOv8](https://github.com/ultralytics/ultralytics) inference result. @@ -225,7 +227,7 @@ class KeyPoints: self.data[key] = value @classmethod - def empty(cls) -> "KeyPoints": + def empty(cls) -> KeyPoints: """ Create an empty Keypoints object with no keypoints. diff --git a/supervision/keypoints/skeletons.py b/supervision/keypoint/skeletons.py similarity index 100% rename from supervision/keypoints/skeletons.py rename to supervision/keypoint/skeletons.py From e2865ecda73e99ed072ebdae3dd65147e551f2be Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:21:47 +0000 Subject: [PATCH 070/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/keypoint/annotate.md | 2 +- supervision/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/keypoint/annotate.md b/docs/keypoint/annotate.md index 8507f54e..476ae918 100644 --- a/docs/keypoint/annotate.md +++ b/docs/keypoint/annotate.md @@ -7,4 +7,4 @@ status: new :::supervision.keypoint.annotate.VertexAnnotator -:::supervision.keypoint.annotate.SkeletonAnnotator \ No newline at end of file +:::supervision.keypoint.annotate.SkeletonAnnotator diff --git a/supervision/__init__.py b/supervision/__init__.py index 70c912c0..53948d1a 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -69,7 +69,7 @@ from supervision.draw.utils import ( ) from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center -from supervision.keypoint.annotate import VertexAnnotator, SkeletonAnnotator +from supervision.keypoint.annotate import SkeletonAnnotator, VertexAnnotator from supervision.keypoint.core import KeyPoints from supervision.keypoint.skeletons import KnownSkeletons, Skeleton from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision From 397b2249a776c2321d18b89b5a984ec38773b498 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 00:24:21 +0200 Subject: [PATCH 071/136] fix typo --- test/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_utils.py b/test/test_utils.py index d3624845..b676cb54 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -3,7 +3,7 @@ from typing import Any, Dict, List, Optional import numpy as np from supervision.detection.core import Detections -from supervision.keypoints.core import KeyPoints +from supervision.keypoint.core import KeyPoints def mock_detections( From 102c5adc386c12a53ef7ddec583bfe68f171ccd1 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 00:43:46 +0200 Subject: [PATCH 072/136] test simplified skeleton API --- supervision/__init__.py | 2 +- supervision/keypoint/annotate.py | 10 ++-- supervision/keypoint/skeletons.py | 88 ++++++------------------------- 3 files changed, 21 insertions(+), 79 deletions(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index 53948d1a..63235df8 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -71,7 +71,7 @@ from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center from supervision.keypoint.annotate import SkeletonAnnotator, VertexAnnotator from supervision.keypoint.core import KeyPoints -from supervision.keypoint.skeletons import KnownSkeletons, Skeleton +from supervision.keypoint.skeletons import Skeleton from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision from supervision.tracker.byte_tracker.core import ByteTrack from supervision.utils.conversion import cv2_to_pillow, pillow_to_cv2 diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index 802199f3..b3a29b4a 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Optional +from typing import Optional, List, Tuple import cv2 import numpy as np @@ -7,7 +7,7 @@ import numpy as np from supervision.annotators.base import ImageType from supervision.draw.color import Color from supervision.keypoint.core import KeyPoints -from supervision.keypoint.skeletons import KnownSkeletons, Skeleton +from supervision.keypoint.skeletons import resolve_skeleton_by_vertex_count from supervision.utils.conversion import convert_for_annotation_method @@ -57,7 +57,7 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): self, color: Color = Color.ROBOFLOW, thickness: int = 2, - skeleton: Optional[Skeleton] = None, + skeleton: Optional[List[Tuple[int, int]]] = None, ) -> None: """ Draw the lines between points of the image. @@ -83,9 +83,9 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): for xy in xy_all: skeleton = self.skeleton if not skeleton: - skeleton = KnownSkeletons().get_skeleton(len(xy)) + skeleton = resolve_skeleton_by_vertex_count(len(xy)) - for class_a, class_b in skeleton.limbs: + for class_a, class_b in skeleton: xy_a = xy[class_a - 1] xy_b = xy[class_b - 1] missing_a = np.allclose(xy_a, 0) diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index dd8c7ec8..31ddbab7 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -1,24 +1,9 @@ -from typing import Dict, List, Tuple +from enum import Enum +from typing import Optional, Tuple, List -class Skeleton: - """ - The `Skeleton` class connects keypoints to form a skeleton. - - Args: - limbs: (List[Tuple[int, int]]): List of (`class_id_1`, - `class_id_2`) representing connected points of the skeleton. - """ - - limbs: List[Tuple[int, int]] - - def __init__(self, limbs: List[Tuple[int, int]]): - self.limbs = limbs - - -NO_SKELETON = Skeleton([]) -YOLO_V8_SKELETON = Skeleton( - [ +class Skeleton(Enum): + COCO = [ (1, 2), (1, 3), (2, 3), @@ -37,61 +22,18 @@ YOLO_V8_SKELETON = Skeleton( (16, 14), (17, 15), ] -) -def _make_sequential_skeleton(limb_count: int) -> Skeleton: - """ - Create a skeleton where neighboring keypoints are connected sequentially. - - Args: - limb_count (int): The number of limbs to connect. - - Returns: - Skeleton: The skeleton connecting all keypoints. - """ - limbs = [] - for i in range(0, limb_count - 1): - limbs.append((i, i + 1)) - limbs.append((0, limb_count - 1)) - return Skeleton(limbs) +def resolve_skeleton_by_vertex_count(count: int) -> Optional[List[Tuple[int, int]]]: + for skeleton in Skeleton: + unique_vertices = set(vertex for edge in skeleton.value for vertex in edge) + if len(unique_vertices) == count: + return skeleton.value + return None -class KnownSkeletons: - """ - Helps automatically determine which skeleton to use. - - The `KnownSkeletons` class contains a collection of predefined skeletons - for various keypoint models, to be distinguished by limb counts. - - Example: - # Suppose you have a model returning 15 keypoints. - skeleton = KnownSkeletons().get_skeleton(15) - """ - - _instance = None - _skeletons: Dict[int, Skeleton] = {} - - def __new__(cls): - if cls._instance is None: - cls._instance = super(KnownSkeletons, cls).__new__(cls) - return cls._instance - - def add_skeleton(self, skeleton: Skeleton) -> None: - len_limbs = len(skeleton.limbs) - if len_limbs in self._skeletons: - raise ValueError(f"A skeleton with {len_limbs} limbs already exists") - self._skeletons[len_limbs] = skeleton - - def get_skeleton(self, limb_count: int) -> Skeleton: - if limb_count not in self._skeletons: - print( - f"Warning: No skeleton found with {limb_count} limbs. " - f"Will create one now." - ) - self.add_skeleton(_make_sequential_skeleton(limb_count)) - return self._skeletons[limb_count] - - -KnownSkeletons().add_skeleton(NO_SKELETON) -KnownSkeletons().add_skeleton(YOLO_V8_SKELETON) +def resolve_skeleton_by_edge_count(count: int) -> Optional[List[Tuple[int, int]]]: + for skeleton in Skeleton: + if len(skeleton.value) == count: + return skeleton.value + return None From 9ad35bcfb9b46daa47ece8a8c815217048a15d07 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:44:00 +0000 Subject: [PATCH 073/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/annotate.py | 2 +- supervision/keypoint/skeletons.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index b3a29b4a..adae6745 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Optional, List, Tuple +from typing import List, Optional, Tuple import cv2 import numpy as np diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index 31ddbab7..d2882f1b 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Optional, Tuple, List +from typing import List, Optional, Tuple class Skeleton(Enum): From d8681b8b4f1277b9f5da5a43b97c2561136717b2 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 24 Apr 2024 09:51:40 +0300 Subject: [PATCH 074/136] remove xy_all, turn to `for xy in keypoints.xy` --- supervision/keypoint/annotate.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index adae6745..15f0eb4e 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -38,8 +38,7 @@ class VertexAnnotator(BaseKeyPointAnnotator): if len(keypoints) == 0: return scene - xy_all = keypoints.xy - for xy in xy_all: + for xy in keypoints.xy: for x, y in xy: cv2.circle( img=scene, @@ -77,10 +76,10 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): if len(keypoints) == 0: return scene if keypoints.class_id is None: - raise ValueError("KeyPoints must have class_id to annotate a skeleton") + raise ValueError( + "KeyPoints must have class_id to annotate a skeleton") - xy_all = keypoints.xy - for xy in xy_all: + for xy in keypoints.xy: skeleton = self.skeleton if not skeleton: skeleton = resolve_skeleton_by_vertex_count(len(xy)) From 0749efed8f38273ab03b66274091d149746953f6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 06:52:17 +0000 Subject: [PATCH 075/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/annotate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index 15f0eb4e..1589f02e 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -76,8 +76,7 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): if len(keypoints) == 0: return scene if keypoints.class_id is None: - raise ValueError( - "KeyPoints must have class_id to annotate a skeleton") + raise ValueError("KeyPoints must have class_id to annotate a skeleton") for xy in keypoints.xy: skeleton = self.skeleton From 1c27bf7fea1173f2a72088036925f5802ff3b604 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 24 Apr 2024 12:44:29 +0300 Subject: [PATCH 076/136] EdgeAnnotator, warn and not crash when None --- supervision/__init__.py | 2 +- supervision/keypoint/annotate.py | 22 ++++++++++++---------- supervision/keypoint/skeletons.py | 3 ++- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index 63235df8..b716275d 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -69,7 +69,7 @@ from supervision.draw.utils import ( ) from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center -from supervision.keypoint.annotate import SkeletonAnnotator, VertexAnnotator +from supervision.keypoint.annotate import EdgeAnnotator, VertexAnnotator from supervision.keypoint.core import KeyPoints from supervision.keypoint.skeletons import Skeleton from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index 1589f02e..49c07bd0 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod from typing import List, Optional, Tuple +from logging import warn import cv2 import numpy as np @@ -51,12 +52,12 @@ class VertexAnnotator(BaseKeyPointAnnotator): return scene -class SkeletonAnnotator(BaseKeyPointAnnotator): +class EdgeAnnotator(BaseKeyPointAnnotator): def __init__( self, color: Color = Color.ROBOFLOW, thickness: int = 2, - skeleton: Optional[List[Tuple[int, int]]] = None, + edges: Optional[List[Tuple[int, int]]] = None, ) -> None: """ Draw the lines between points of the image. @@ -64,26 +65,27 @@ class SkeletonAnnotator(BaseKeyPointAnnotator): Args: color (Color, optional): The color of the lines. thickness (int, optional): The thickness of the lines. - skeleton (Skeleton, optional): The skeleton to draw. + edge (Optional[List[Tuple[int, int]]]): The edges to draw. If set to `None`, will attempt to select automatically. """ self.color = color self.thickness = thickness - self.skeleton = skeleton + self.edges = edges @convert_for_annotation_method def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: if len(keypoints) == 0: return scene - if keypoints.class_id is None: - raise ValueError("KeyPoints must have class_id to annotate a skeleton") for xy in keypoints.xy: - skeleton = self.skeleton - if not skeleton: - skeleton = resolve_skeleton_by_vertex_count(len(xy)) + edges = self.edges + if not edges: + edges = resolve_skeleton_by_vertex_count(len(xy)) + if not edges: + warn(f"No skeleton found with {len(xy)} vertices") + return scene - for class_a, class_b in skeleton: + for class_a, class_b in edges: xy_a = xy[class_a - 1] xy_b = xy[class_b - 1] missing_a = np.allclose(xy_a, 0) diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index d2882f1b..750580dd 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -26,7 +26,8 @@ class Skeleton(Enum): def resolve_skeleton_by_vertex_count(count: int) -> Optional[List[Tuple[int, int]]]: for skeleton in Skeleton: - unique_vertices = set(vertex for edge in skeleton.value for vertex in edge) + unique_vertices = set( + vertex for edge in skeleton.value for vertex in edge) if len(unique_vertices) == count: return skeleton.value return None From 0f6ad236ebf304d7ebb86d3902c076edb1c000c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 09:45:44 +0000 Subject: [PATCH 077/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/annotate.py | 2 +- supervision/keypoint/skeletons.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index 49c07bd0..b683b45f 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from typing import List, Optional, Tuple from logging import warn +from typing import List, Optional, Tuple import cv2 import numpy as np diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index 750580dd..d2882f1b 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -26,8 +26,7 @@ class Skeleton(Enum): def resolve_skeleton_by_vertex_count(count: int) -> Optional[List[Tuple[int, int]]]: for skeleton in Skeleton: - unique_vertices = set( - vertex for edge in skeleton.value for vertex in edge) + unique_vertices = set(vertex for edge in skeleton.value for vertex in edge) if len(unique_vertices) == count: return skeleton.value return None From d1c103ee16742dc7e09e486412385f99fad6970a Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 24 Apr 2024 12:50:19 +0300 Subject: [PATCH 078/136] Best skeleton cache ever, obviously --- supervision/keypoint/annotate.py | 4 ++-- supervision/keypoint/skeletons.py | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index b683b45f..1e7cf193 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -8,7 +8,7 @@ import numpy as np from supervision.annotators.base import ImageType from supervision.draw.color import Color from supervision.keypoint.core import KeyPoints -from supervision.keypoint.skeletons import resolve_skeleton_by_vertex_count +from supervision.keypoint.skeletons import skeletons_by_vertex_count from supervision.utils.conversion import convert_for_annotation_method @@ -80,7 +80,7 @@ class EdgeAnnotator(BaseKeyPointAnnotator): for xy in keypoints.xy: edges = self.edges if not edges: - edges = resolve_skeleton_by_vertex_count(len(xy)) + edges = skeletons_by_vertex_count.get(len(xy)) if not edges: warn(f"No skeleton found with {len(xy)} vertices") return scene diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index d2882f1b..373b8fcc 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -1,5 +1,7 @@ from enum import Enum -from typing import List, Optional, Tuple +from typing import Dict, List, Tuple + +Edges = List[Tuple[int, int]] class Skeleton(Enum): @@ -24,16 +26,12 @@ class Skeleton(Enum): ] -def resolve_skeleton_by_vertex_count(count: int) -> Optional[List[Tuple[int, int]]]: - for skeleton in Skeleton: - unique_vertices = set(vertex for edge in skeleton.value for vertex in edge) - if len(unique_vertices) == count: - return skeleton.value - return None +skeletons_by_edge_count: Dict[int, Edges] = {} +skeletons_by_vertex_count: Dict[int, Edges] = {} +for skeleton in Skeleton: + skeletons_by_edge_count[len(skeleton.value)] = skeleton.value -def resolve_skeleton_by_edge_count(count: int) -> Optional[List[Tuple[int, int]]]: - for skeleton in Skeleton: - if len(skeleton.value) == count: - return skeleton.value - return None + unique_vertices = set( + vertex for edge in skeleton.value for vertex in edge) + skeletons_by_vertex_count[len(unique_vertices)] = skeleton.value From 08d7a6b4cdc5646888ebfe032e4f1a432898380b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 09:53:33 +0000 Subject: [PATCH 079/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/skeletons.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index 373b8fcc..1c785d78 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -32,6 +32,5 @@ skeletons_by_vertex_count: Dict[int, Edges] = {} for skeleton in Skeleton: skeletons_by_edge_count[len(skeleton.value)] = skeleton.value - unique_vertices = set( - vertex for edge in skeleton.value for vertex in edge) + unique_vertices = set(vertex for edge in skeleton.value for vertex in edge) skeletons_by_vertex_count[len(unique_vertices)] = skeleton.value From 741309fd099f47e89a748a00784b8f9c2c6d3612 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 24 Apr 2024 13:13:10 +0300 Subject: [PATCH 080/136] Remove Skeleton from __init__, small renames, consts --- docs/keypoint/annotate.md | 2 +- supervision/__init__.py | 1 - supervision/keypoint/annotate.py | 4 ++-- supervision/keypoint/skeletons.py | 8 ++++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/keypoint/annotate.md b/docs/keypoint/annotate.md index 476ae918..4f9cd861 100644 --- a/docs/keypoint/annotate.md +++ b/docs/keypoint/annotate.md @@ -7,4 +7,4 @@ status: new :::supervision.keypoint.annotate.VertexAnnotator -:::supervision.keypoint.annotate.SkeletonAnnotator +:::supervision.keypoint.annotate.EdgeAnnotator diff --git a/supervision/__init__.py b/supervision/__init__.py index b716275d..053e3718 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -71,7 +71,6 @@ from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center from supervision.keypoint.annotate import EdgeAnnotator, VertexAnnotator from supervision.keypoint.core import KeyPoints -from supervision.keypoint.skeletons import Skeleton from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision from supervision.tracker.byte_tracker.core import ByteTrack from supervision.utils.conversion import cv2_to_pillow, pillow_to_cv2 diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py index 1e7cf193..b1f3530f 100644 --- a/supervision/keypoint/annotate.py +++ b/supervision/keypoint/annotate.py @@ -8,7 +8,7 @@ import numpy as np from supervision.annotators.base import ImageType from supervision.draw.color import Color from supervision.keypoint.core import KeyPoints -from supervision.keypoint.skeletons import skeletons_by_vertex_count +from supervision.keypoint.skeletons import SKELETONS_BY_VERTEX_COUNT from supervision.utils.conversion import convert_for_annotation_method @@ -80,7 +80,7 @@ class EdgeAnnotator(BaseKeyPointAnnotator): for xy in keypoints.xy: edges = self.edges if not edges: - edges = skeletons_by_vertex_count.get(len(xy)) + edges = SKELETONS_BY_VERTEX_COUNT.get(len(xy)) if not edges: warn(f"No skeleton found with {len(xy)} vertices") return scene diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index 1c785d78..6c110854 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -26,11 +26,11 @@ class Skeleton(Enum): ] -skeletons_by_edge_count: Dict[int, Edges] = {} -skeletons_by_vertex_count: Dict[int, Edges] = {} +SKELETONS_BY_EDGE_COUNT: Dict[int, Edges] = {} +SKELETONS_BY_VERTEX_COUNT: Dict[int, Edges] = {} for skeleton in Skeleton: - skeletons_by_edge_count[len(skeleton.value)] = skeleton.value + SKELETONS_BY_EDGE_COUNT[len(skeleton.value)] = skeleton.value unique_vertices = set(vertex for edge in skeleton.value for vertex in edge) - skeletons_by_vertex_count[len(unique_vertices)] = skeleton.value + SKELETONS_BY_VERTEX_COUNT[len(unique_vertices)] = skeleton.value From 4b89bece4fdc849a7ed7f0e1e1bf49bb1c12f17e Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 24 Apr 2024 14:58:52 +0300 Subject: [PATCH 081/136] Fix keypoint crash with no detections --- supervision/keypoint/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index aca50ad5..6749f845 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -135,7 +135,7 @@ class KeyPoints: keypoints = sv.KeyPoints.from_ultralytics(result) ``` """ - if len(ultralytics_results.keypoints.xy) == 0: + if ultralytics_results.keypoints.xy.numel() == 0: return cls.empty() xy = ultralytics_results.keypoints.xy.cpu().numpy() From 48809c13322553ab6b87bf05aa10987e472e3f08 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 24 Apr 2024 16:37:41 +0300 Subject: [PATCH 082/136] Add a basic from_supervision function to KeyPoints --- supervision/keypoint/core.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 6749f845..87f0a20b 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -110,6 +110,37 @@ class KeyPoints: ] ) + @classmethod + def from_inference(cls, inference_result: dict) -> KeyPoints: + if not inference_result.get("predictions"): + return cls.empty() + + xy = [] + confidence = [] + class_id = [] + class_names = [] + + for prediction in inference_result["predictions"]: + prediction_xy = [] + prediction_confidence = [] + for keypoint in prediction["keypoints"]: + prediction_xy.append([keypoint["x"], keypoint["y"]]) + prediction_confidence.append(keypoint["confidence"]) + xy.append(prediction_xy) + confidence.append(prediction_confidence) + + class_id.append(prediction["class_id"]) + class_names.append(prediction["class"]) + + data = {CLASS_NAME_DATA_FIELD: np.array(class_names)} + + return cls( + xy=np.array(xy, dtype=np.float32), + confidence=np.array(confidence, dtype=np.float32), + class_id=np.array(class_id, dtype=int), + data=data, + ) + @classmethod def from_ultralytics(cls, ultralytics_results) -> KeyPoints: """ From 77793571b094f646eaec2bebd99369e203b87159 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 15:39:08 +0200 Subject: [PATCH 083/136] major docs refactor --- README.md | 7 +- docs/deprecated.md | 2 +- docs/{ => detection}/annotators.md | 0 docs/detection/metrics.md | 21 +++ docs/draw/color.md | 13 -- docs/how_to/detect_and_annotate.md | 28 +-- docs/how_to/detect_small_objects.md | 16 +- docs/keypoint/annotate.md | 10 -- docs/keypoint/annotators.md | 10 ++ docs/metrics/detection.md | 22 --- docs/{draw/utils.md => utils/draw.md} | 13 ++ docs/{geometry/core.md => utils/geometry.md} | 0 mkdocs.yml | 22 +-- supervision/__init__.py | 2 +- supervision/assets/list.py | 5 + supervision/detection/core.py | 127 ++++++++------ supervision/keypoint/annotate.py | 104 ------------ supervision/keypoint/annotators.py | 170 +++++++++++++++++++ supervision/keypoint/core.py | 43 ++--- 19 files changed, 348 insertions(+), 267 deletions(-) rename docs/{ => detection}/annotators.md (100%) create mode 100644 docs/detection/metrics.md delete mode 100644 docs/draw/color.md delete mode 100644 docs/keypoint/annotate.md create mode 100644 docs/keypoint/annotators.md delete mode 100644 docs/metrics/detection.md rename docs/{draw/utils.md => utils/draw.md} (83%) rename docs/{geometry/core.md => utils/geometry.md} (100%) delete mode 100644 supervision/keypoint/annotate.py create mode 100644 supervision/keypoint/annotators.py diff --git a/README.md b/README.md index beed655c..0894a6a4 100644 --- a/README.md +++ b/README.md @@ -71,16 +71,15 @@ len(detections) ```python import cv2 import supervision as sv - from inference.models.utils import get_roboflow_model + from inference import get_model image = cv2.imread(...) - model = get_roboflow_model(model_id="yolov8s-640", api_key=) + model = get_model(model_id="yolov8s-640", api_key=) result = model.infer(image)[0] detections = sv.Detections.from_inference(result) len(detections) - # 5 - + # 5 ``` diff --git a/docs/deprecated.md b/docs/deprecated.md index 35c332e4..cc1e9649 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -12,7 +12,7 @@ These features are phased out due to better alternatives or potential issues in - The method `Color.green()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.GREEN` instead. - The method `Color.blue()` is deprecated and will be removed in `supervision-0.22.0`. Use the constant `Color.BLUE` instead. - The method [`ColorPalette.default()`](draw/color.md/#supervision.draw.color.ColorPalette.default) is deprecated and will be removed in `supervision-0.22.0`. Use the constant [`ColorPalette.DEFAULT`](draw/color.md/#supervision.draw.color.ColorPalette.DEFAULT) instead. -- `BoxAnnotator` is deprecated and will be removed in `supervision-0.22.0`. Use [`BoundingBoxAnnotator`](annotators.md/#supervision.annotators.core.BoundingBoxAnnotator) and [`LabelAnnotator`](annotators.md/#supervision.annotators.core.LabelAnnotator) instead. +- `BoxAnnotator` is deprecated and will be removed in `supervision-0.22.0`. Use [`BoundingBoxAnnotator`](detection/annotators.md/#supervision.annotators.core.BoundingBoxAnnotator) and [`LabelAnnotator`](detection/annotators.md/#supervision.annotators.core.LabelAnnotator) instead. - The method [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) is deprecated and will be removed in `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead. - The `track_buffer`, `track_thresh`, and `match_thresh` parameters in [`ByterTrack`](trackers.md/#supervision.tracker.byte_tracker.core.ByteTrack) are deprecated and will be removed in `supervision-0.23.0`. Use `lost_track_buffer,` `track_activation_threshold`, and `minimum_matching_threshold` instead. - The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.23.0`. Use `triggering_anchors ` instead. diff --git a/docs/annotators.md b/docs/detection/annotators.md similarity index 100% rename from docs/annotators.md rename to docs/detection/annotators.md diff --git a/docs/detection/metrics.md b/docs/detection/metrics.md new file mode 100644 index 00000000..981f399c --- /dev/null +++ b/docs/detection/metrics.md @@ -0,0 +1,21 @@ +--- +comments: true +--- + +# Metrics + +!!! 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`. + + + +:::supervision.metrics.detection.ConfusionMatrix + + + +:::supervision.metrics.detection.MeanAveragePrecision diff --git a/docs/draw/color.md b/docs/draw/color.md deleted file mode 100644 index c702814a..00000000 --- a/docs/draw/color.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -comments: true ---- - -# Color - -:::supervision.draw.color.Color - -
-

ColorPalette

-
- -:::supervision.draw.color.ColorPalette diff --git a/docs/how_to/detect_and_annotate.md b/docs/how_to/detect_and_annotate.md index adea95cf..a9a4405e 100644 --- a/docs/how_to/detect_and_annotate.md +++ b/docs/how_to/detect_and_annotate.md @@ -27,7 +27,7 @@ model. from inference import get_model model = get_model(model_id="yolov8n-640") - image = cv2.imread() + image = cv2.imread() results = model.infer(image)[0] ``` @@ -38,7 +38,7 @@ model. from ultralytics import YOLO model = YOLO("yolov8n.pt") - image = cv2.imread() + image = cv2.imread() results = model(image)[0] ``` @@ -52,7 +52,7 @@ model. processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") - image = Image.open() + image = Image.open() inputs = processor(images=image, return_tensors="pt") with torch.no_grad(): @@ -78,7 +78,7 @@ Now that we have predictions from a model, we can load them into Supervision. from inference import get_model model = get_model(model_id="yolov8n-640") - image = cv2.imread() + image = cv2.imread() results = model.infer(image)[0] detections = sv.Detections.from_inference(results) ``` @@ -93,7 +93,7 @@ Now that we have predictions from a model, we can load them into Supervision. from ultralytics import YOLO model = YOLO("yolov8n.pt") - image = cv2.imread() + image = cv2.imread() results = model(image)[0] detections = sv.Detections.from_ultralytics(results) ``` @@ -111,7 +111,7 @@ Now that we have predictions from a model, we can load them into Supervision. processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") - image = Image.open() + image = Image.open() inputs = processor(images=image, return_tensors="pt") with torch.no_grad(): @@ -146,7 +146,7 @@ Finally, we can annotate the image with the predictions. Since we are working wi from inference import get_model model = get_model(model_id="yolov8n-640") - image = cv2.imread() + image = cv2.imread() results = model.infer(image)[0] detections = sv.Detections.from_inference(results) @@ -167,7 +167,7 @@ Finally, we can annotate the image with the predictions. Since we are working wi from ultralytics import YOLO model = YOLO("yolov8n.pt") - image = cv2.imread() + image = cv2.imread() results = model(image)[0] detections = sv.Detections.from_ultralytics(results) @@ -191,7 +191,7 @@ Finally, we can annotate the image with the predictions. Since we are working wi processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") - image = Image.open() + image = Image.open() inputs = processor(images=image, return_tensors="pt") with torch.no_grad(): @@ -230,7 +230,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me from inference import get_model model = get_model(model_id="yolov8n-640") - image = cv2.imread() + image = cv2.imread() results = model.infer(image)[0] detections = sv.Detections.from_inference(results) @@ -257,7 +257,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me from ultralytics import YOLO model = YOLO("yolov8n.pt") - image = cv2.imread() + image = cv2.imread() results = model(image)[0] detections = sv.Detections.from_ultralytics(results) @@ -287,7 +287,7 @@ override this behavior by passing a list of custom `labels` to the `annotate` me processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") - image = Image.open() + image = Image.open() inputs = processor(images=image, return_tensors="pt") with torch.no_grad(): @@ -334,7 +334,7 @@ that will allow you to draw masks instead of boxes. from inference import get_model model = get_model(model_id="yolov8n-seg-640") - image = cv2.imread() + image = cv2.imread() results = model.infer(image)[0] detections = sv.Detections.from_inference(results) @@ -355,7 +355,7 @@ that will allow you to draw masks instead of boxes. from ultralytics import YOLO model = YOLO("yolov8n-seg.pt") - image = cv2.imread() + image = cv2.imread() results = model(image)[0] detections = sv.Detections.from_ultralytics(results) diff --git a/docs/how_to/detect_small_objects.md b/docs/how_to/detect_small_objects.md index 9f35a96c..e2d02328 100644 --- a/docs/how_to/detect_small_objects.md +++ b/docs/how_to/detect_small_objects.md @@ -28,7 +28,7 @@ size relative to the image resolution. from inference import get_model model = get_model(model_id="yolov8x-640") - image = cv2.imread() + image = cv2.imread() results = model.infer(image)[0] detections = sv.Detections.from_inference(results) @@ -49,7 +49,7 @@ size relative to the image resolution. from ultralytics import YOLO model = YOLO("yolov8x.pt") - image = cv2.imread() + image = cv2.imread() results = model(image)[0] detections = sv.Detections.from_ultralytics(results) @@ -73,7 +73,7 @@ size relative to the image resolution. processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") - image = Image.open() + image = Image.open() inputs = processor(images=image, return_tensors="pt") with torch.no_grad(): @@ -116,7 +116,7 @@ is less effective for ultra-high-resolution images (4K and above). from inference import get_model model = get_model(model_id="yolov8x-1280") - image = cv2.imread() + image = cv2.imread() results = model.infer(image)[0] detections = sv.Detections.from_inference(results) @@ -137,7 +137,7 @@ is less effective for ultra-high-resolution images (4K and above). from ultralytics import YOLO model = YOLO("yolov8x.pt") - image = cv2.imread() + image = cv2.imread() results = model(image, imgsz=1280)[0] detections = sv.Detections.from_ultralytics(results) @@ -171,7 +171,7 @@ objects within each, and aggregating the results. from inference import get_model model = get_model(model_id="yolov8x-640") - image = cv2.imread() + image = cv2.imread() def callback(image_slice: np.ndarray) -> sv.Detections: results = model.infer(image_slice)[0] @@ -198,7 +198,7 @@ objects within each, and aggregating the results. from ultralytics import YOLO model = YOLO("yolov8x.pt") - image = cv2.imread() + image = cv2.imread() def callback(image_slice: np.ndarray) -> sv.Detections: result = model(image_slice)[0] @@ -229,7 +229,7 @@ objects within each, and aggregating the results. processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") - image = cv2.imread() + image = cv2.imread() def callback(image_slice: np.ndarray) -> sv.Detections: image_slice = cv2.cvtColor(image_slice, cv2.COLOR_BGR2RGB) diff --git a/docs/keypoint/annotate.md b/docs/keypoint/annotate.md deleted file mode 100644 index 4f9cd861..00000000 --- a/docs/keypoint/annotate.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -comments: true -status: new ---- - -# Annotate - -:::supervision.keypoint.annotate.VertexAnnotator - -:::supervision.keypoint.annotate.EdgeAnnotator diff --git a/docs/keypoint/annotators.md b/docs/keypoint/annotators.md new file mode 100644 index 00000000..3efc5b03 --- /dev/null +++ b/docs/keypoint/annotators.md @@ -0,0 +1,10 @@ +--- +comments: true +status: new +--- + +# Annotators + +:::supervision.keypoint.annotators.VertexAnnotator + +:::supervision.keypoint.annotators.EdgeAnnotator diff --git a/docs/metrics/detection.md b/docs/metrics/detection.md deleted file mode 100644 index a953ba9b..00000000 --- a/docs/metrics/detection.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -comments: true ---- - -#  Detection Metrics - -!!! 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`. - - - -:::supervision.metrics.detection.ConfusionMatrix - - - -:::supervision.metrics.detection.MeanAveragePrecision diff --git a/docs/draw/utils.md b/docs/utils/draw.md similarity index 83% rename from docs/draw/utils.md rename to docs/utils/draw.md index 7975952c..7b47a6a7 100644 --- a/docs/draw/utils.md +++ b/docs/utils/draw.md @@ -51,3 +51,16 @@ comments: true :::supervision.draw.utils.calculate_optimal_line_thickness + +
+

Color

+
+ +:::supervision.draw.color.Color + +
+

ColorPalette

+
+ +:::supervision.draw.color.ColorPalette + diff --git a/docs/geometry/core.md b/docs/utils/geometry.md similarity index 100% rename from docs/geometry/core.md rename to docs/utils/geometry.md diff --git a/mkdocs.yml b/mkdocs.yml index 256c6bef..cf206a82 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,15 +44,16 @@ nav: - Track Objects: how_to/track_objects.md - API: - - Annotators: annotators.md - - Classifications: - - Core: classification/core.md - - Detections: + - Detection and Segmentation: - Core: detection/core.md + - Annotators: detection/annotators.md + - Metrics: detection/metrics.md - Utils: detection/utils.md - - KeyPoints: + - Keypoint Detection: - Core: keypoint/core.md - - Annotators: keypoint/annotate.md + - Annotators: keypoint/annotators.md + - Classification: + - Core: classification/core.md - Tools: - Line Zone: detection/tools/line_zone.md - Polygon Zone: detection/tools/polygon_zone.md @@ -61,19 +62,14 @@ nav: - Save Detections: detection/tools/save_detections.md - Trackers: trackers.md - Datasets: datasets.md - - Metrics: - - Object Detection: metrics/detection.md - - Draw: - - Color: draw/color.md - - Utils: draw/utils.md - - Geometry: - - Position: geometry/core.md - Utils: - Video: utils/video.md - Image: utils/image.md - Iterables: utils/iterables.md - Notebook: utils/notebook.md - File: utils/file.md + - Draw: utils/draw.md + - Geometry: utils/geometry.md - Assets: assets.md - Cookbooks: cookbooks.md - Contribute: diff --git a/supervision/__init__.py b/supervision/__init__.py index 053e3718..bb526514 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -69,7 +69,7 @@ from supervision.draw.utils import ( ) from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center -from supervision.keypoint.annotate import EdgeAnnotator, VertexAnnotator +from supervision.keypoint.annotators import EdgeAnnotator, VertexAnnotator from supervision.keypoint.core import KeyPoints from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision from supervision.tracker.byte_tracker.core import ByteTrack diff --git a/supervision/assets/list.py b/supervision/assets/list.py index 77b1e461..0866431a 100644 --- a/supervision/assets/list.py +++ b/supervision/assets/list.py @@ -29,6 +29,7 @@ class VideoAssets(Enum): MARKET_SQUARE = "market-square.mp4" PEOPLE_WALKING = "people-walking.mp4" BEACH = "beach-1.mp4" + BASKETBALL = "basketball-1.mp4" @classmethod def list(cls): @@ -68,4 +69,8 @@ VIDEO_ASSETS: Dict[str, Tuple[str, str]] = { f"{BASE_VIDEO_URL}{VideoAssets.BEACH.value}", "4175d42fec4d450ed081523fd39e0cf8", ), + VideoAssets.BASKETBALL.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.BASKETBALL.value}", + "60d94a3c7c47d16f09d342b088012ecc", + ), } diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 53286375..3e3d0114 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -27,34 +27,68 @@ from supervision.validators import validate_detections_fields @dataclass class Detections: """ - The `sv.Detections` allows you to convert results from a variety of object detection - and segmentation models into a single, unified format. The `sv.Detections` class - enables easy data manipulation and filtering, and provides a consistent API for - Supervision's tools like trackers, annotators, and zones. + The `sv.Detections` class in the Supervision library standardizes results from + various object detection and segmentation models into a consistent format. This + class simplifies data manipulation and filtering, providing a uniform API for + integration with Supervision [trackers](/trackers/), [annotators](/detection/annotators/), and [tools](/detection/tools/line_zone/). - ```python - import cv2 - import supervision as sv - from ultralytics import YOLO + === "Inference" - image = cv2.imread() - model = YOLO('yolov8s.pt') - annotator = sv.BoundingBoxAnnotator() + Use [`sv.Detections.from_inference`](/detection/core/#supervision.detection.core.Detections.from_inference) method, which accepts model results from both detection and segmentation models. - result = model(image)[0] - detections = sv.Detections.from_ultralytics(result) + ```python + import cv2 + import supervision as sv + from inference import get_model - annotated_image = annotator.annotate(image, detections) - ``` + model = get_model(model_id="yolov8n-640") + image = cv2.imread() + results = model.infer(image)[0] + detections = sv.Detections.from_inference(results) + ``` - !!! tip + === "Ultralytics" - In `sv.Detections`, detection data is categorized into two main field types: - fixed and custom. The fixed fields include `xyxy`, `mask`, `confidence`, - `class_id`, and `tracker_id`. For any additional data requirements, custom - fields come into play, stored in the data field. These custom fields are easily - accessible using the `detections[]` syntax, providing flexibility - for diverse data handling needs. + Use [`sv.Detections.from_ultralytics`](/detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models. + + ```python + import cv2 + import supervision as sv + from ultralytics import YOLO + + model = YOLO("yolov8n.pt") + image = cv2.imread() + results = model(image)[0] + detections = sv.Detections.from_ultralytics(results) + ``` + + === "Transformers" + + Use [`sv.Detections.from_transformers`](/detection/core/#supervision.detection.core.Detections.from_transformers) method, which accepts model results from both detection and segmentation models. + + ```python + import torch + import supervision as sv + from PIL import Image + from transformers import DetrImageProcessor, DetrForObjectDetection + + processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50") + model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50") + + image = Image.open() + inputs = processor(images=image, return_tensors="pt") + + with torch.no_grad(): + outputs = model(**inputs) + + width, height = image.size + target_size = torch.tensor([[height, width]]) + results = processor.post_process_object_detection( + outputs=outputs, target_sizes=target_size)[0] + detections = sv.Detections.from_transformers( + transformers_results=results, + id2label=model.config.id2label) + ``` Attributes: xyxy (np.ndarray): An array of shape `(n, 4)` containing @@ -70,14 +104,6 @@ class Detections: data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional data where each key is a string representing the data type, and the value is either a NumPy array or a list of corresponding data. - - !!! warning - - The `data` field in the `sv.Detections` class is currently in an experimental - phase. Please be aware that its API and functionality are subject to change in - future updates as we continue to refine and improve its capabilities. - We encourage users to experiment with this feature and provide feedback, but - also to be prepared for potential modifications in upcoming releases. """ xyxy: np.ndarray @@ -177,8 +203,8 @@ class Detections: @classmethod def from_ultralytics(cls, ultralytics_results) -> Detections: """ - Creates a Detections instance from a - [YOLOv8](https://github.com/ultralytics/ultralytics) inference result. + Creates a `sv.Detections` instance from a + [YOLOv8](https://github.com/ultralytics/ultralytics) inference result. !!! Note @@ -202,10 +228,13 @@ class Detections: image = cv2.imread() model = YOLO('yolov8s.pt') - - result = model(image)[0] - detections = sv.Detections.from_ultralytics(result) + results = model(image)[0] + detections = sv.Detections.from_ultralytics(results) ``` + + !!! tip + + Class names values can be accessed using `detections["class_name"]`. """ # noqa: E501 // docs if "obb" in ultralytics_results and ultralytics_results.obb is not None: @@ -396,11 +425,6 @@ class Detections: Creates a Detections instance from object detection or segmentation [Transformer](https://github.com/huggingface/transformers) inference result. - !!! note - - Class names can be accessed using the key `class_name` in the returned - object's data attribute. - Args: transformers_results (dict): The output of Transformers model inference. A dictionary containing the `scores`, `labels`, `boxes` and `masks` keys. @@ -437,6 +461,10 @@ class Detections: id2label=model.config.id2label ) ``` + + !!! tip + + Class names values can be accessed using `detections["class_name"]`. """ # noqa: E501 // docs class_ids = transformers_results["labels"].cpu().detach().numpy().astype(int) @@ -510,17 +538,12 @@ class Detections: @classmethod def from_inference(cls, roboflow_result: Union[dict, Any]) -> Detections: """ - Create a Detections object from the [Roboflow](https://roboflow.com/) + Create a `sv.Detections` object from the [Roboflow](https://roboflow.com/) API inference result or the [Inference](https://inference.roboflow.com/) package results. This method extracts bounding boxes, class IDs, confidences, and class names from the Roboflow API result and encapsulates them into a Detections object. - !!! note - - Class names can be accessed using the key `class_name` in the returned - object's data attribute. - Args: roboflow_result (dict, any): The result from the Roboflow API or Inference package containing predictions. @@ -533,14 +556,18 @@ class Detections: ```python import cv2 import supervision as sv - from inference.models.utils import get_roboflow_model + from inference import get_model image = cv2.imread() - model = get_roboflow_model(model_id="yolov8s-640") + model = get_model(model_id="yolov8s-640") result = model.infer(image)[0] detections = sv.Detections.from_inference(result) ``` + + !!! tip + + Class names values can be accessed using `detections["class_name"]`. """ with suppress(AttributeError): roboflow_result = roboflow_result.dict(exclude_none=True, by_alias=True) @@ -590,10 +617,10 @@ class Detections: ```python import cv2 import supervision as sv - from inference.models.utils import get_roboflow_model + from inference import get_model image = cv2.imread() - model = get_roboflow_model(model_id="yolov8s-640") + model = get_model(model_id="yolov8s-640") result = model.infer(image)[0] detections = sv.Detections.from_roboflow(result) diff --git a/supervision/keypoint/annotate.py b/supervision/keypoint/annotate.py deleted file mode 100644 index b1f3530f..00000000 --- a/supervision/keypoint/annotate.py +++ /dev/null @@ -1,104 +0,0 @@ -from abc import ABC, abstractmethod -from logging import warn -from typing import List, Optional, Tuple - -import cv2 -import numpy as np - -from supervision.annotators.base import ImageType -from supervision.draw.color import Color -from supervision.keypoint.core import KeyPoints -from supervision.keypoint.skeletons import SKELETONS_BY_VERTEX_COUNT -from supervision.utils.conversion import convert_for_annotation_method - - -class BaseKeyPointAnnotator(ABC): - @abstractmethod - def annotate(self, scene: ImageType, detections: KeyPoints) -> ImageType: - pass - - -class VertexAnnotator(BaseKeyPointAnnotator): - def __init__( - self, - color: Color = Color.ROBOFLOW, - radius: int = 4, - ) -> None: - """ - Most basic keypoint annotator. - - Args: - color (Color, optional): The color of the keypoint. - radius (int, optional): The radius of the keypoint. - """ - self.color = color - self.radius = radius - - @convert_for_annotation_method - def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: - if len(keypoints) == 0: - return scene - - for xy in keypoints.xy: - for x, y in xy: - cv2.circle( - img=scene, - center=(int(x), int(y)), - radius=self.radius, - color=self.color.as_bgr(), - thickness=-1, - ) - - return scene - - -class EdgeAnnotator(BaseKeyPointAnnotator): - def __init__( - self, - color: Color = Color.ROBOFLOW, - thickness: int = 2, - edges: Optional[List[Tuple[int, int]]] = None, - ) -> None: - """ - Draw the lines between points of the image. - - Args: - color (Color, optional): The color of the lines. - thickness (int, optional): The thickness of the lines. - edge (Optional[List[Tuple[int, int]]]): The edges to draw. - If set to `None`, will attempt to select automatically. - """ - self.color = color - self.thickness = thickness - self.edges = edges - - @convert_for_annotation_method - def annotate(self, scene: ImageType, keypoints: KeyPoints) -> ImageType: - if len(keypoints) == 0: - return scene - - for xy in keypoints.xy: - edges = self.edges - if not edges: - edges = SKELETONS_BY_VERTEX_COUNT.get(len(xy)) - if not edges: - warn(f"No skeleton found with {len(xy)} vertices") - return scene - - for class_a, class_b in edges: - xy_a = xy[class_a - 1] - xy_b = xy[class_b - 1] - missing_a = np.allclose(xy_a, 0) - missing_b = np.allclose(xy_b, 0) - if missing_a or missing_b: - continue - - cv2.line( - img=scene, - pt1=(int(xy_a[0]), int(xy_a[1])), - pt2=(int(xy_b[0]), int(xy_b[1])), - color=self.color.as_bgr(), - thickness=self.thickness, - ) - - return scene diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py new file mode 100644 index 00000000..5cadc377 --- /dev/null +++ b/supervision/keypoint/annotators.py @@ -0,0 +1,170 @@ +from abc import ABC, abstractmethod +from logging import warn +from typing import List, Optional, Tuple + +import cv2 +import numpy as np + +from supervision.annotators.base import ImageType +from supervision.draw.color import Color +from supervision.keypoint.core import KeyPoints +from supervision.keypoint.skeletons import SKELETONS_BY_VERTEX_COUNT +from supervision.utils.conversion import convert_for_annotation_method + + +class BaseKeyPointAnnotator(ABC): + @abstractmethod + def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType: + pass + + +class VertexAnnotator(BaseKeyPointAnnotator): + """ + A class that specializes in drawing skeleton vertices on images. It uses + specified key points to determine the locations where the vertices should be + drawn. + """ + + def __init__( + self, + color: Color = Color.ROBOFLOW, + radius: int = 4, + ) -> None: + """ + Args: + color (Color, optional): The color to use for annotating key points. + radius (int, optional): The radius of the circles used to represent the key + points. + """ + self.color = color + self.radius = radius + + @convert_for_annotation_method + def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType: + """ + Annotates the given scene with skeleton vertices based on the provided key + points. It draws circles at each key point location. + + Args: + scene (ImageType): The image where bounding boxes will be drawn. `ImageType` + is a flexible type, accepting either `numpy.ndarray` or + `PIL.Image.Image`. + key_points (KeyPoints): A collection of key points where each key point + consists of x and y coordinates. + + Returns: + The annotated image, matching the type of `scene` (`numpy.ndarray` + or `PIL.Image.Image`) + + Example: + ```python + import supervision as sv + + image = ... + key_points = sv.KeyPoints(...) + + vertex_annotator = sv.VertexAnnotator() + annotated_frame = vertex_annotator.annotate( + scene=image.copy(), + key_points=key_points + ) + ``` + """ + if len(key_points) == 0: + return scene + + for xy in key_points.xy: + for x, y in xy: + cv2.circle( + img=scene, + center=(int(x), int(y)), + radius=self.radius, + color=self.color.as_bgr(), + thickness=-1, + ) + + return scene + + +class EdgeAnnotator(BaseKeyPointAnnotator): + """ + A class that specializes in drawing skeleton edges on images using specified key + points. It connects key points with lines to form the skeleton structure. + """ + def __init__( + self, + color: Color = Color.ROBOFLOW, + thickness: int = 2, + edges: Optional[List[Tuple[int, int]]] = None, + ) -> None: + """ + Args: + color (Color, optional): The color to use for the edges. + thickness (int, optional): The thickness of the edges. + edges (Optional[List[Tuple[int, int]]]): The edges to draw. + If set to `None`, will attempt to select automatically. + """ + self.color = color + self.thickness = thickness + self.edges = edges + + @convert_for_annotation_method + def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType: + """ + Annotates the given scene by drawing lines between specified key points to form + edges. + + Args: + scene (ImageType): The image where bounding boxes will be drawn. `ImageType` + is a flexible type, accepting either `numpy.ndarray` or + `PIL.Image.Image`. + key_points (KeyPoints): A collection of key points where each key point + consists of x and y coordinates. + + Returns: + Returns: + The annotated image, matching the type of `scene` (`numpy.ndarray` + or `PIL.Image.Image`) + + Example: + ```python + import supervision as sv + + image = ... + key_points = sv.KeyPoints(...) + + edge_annotator = sv.EdgeAnnotator() + annotated_frame = edge_annotator.annotate( + scene=image.copy(), + key_points=key_points + ) + ``` + """ + if len(key_points) == 0: + return scene + + for xy in key_points.xy: + edges = self.edges + if not edges: + edges = SKELETONS_BY_VERTEX_COUNT.get(len(xy)) + if not edges: + warn(f"No skeleton found with {len(xy)} vertices") + return scene + + for class_a, class_b in edges: + xy_a = xy[class_a - 1] + xy_b = xy[class_b - 1] + missing_a = np.allclose(xy_a, 0) + missing_b = np.allclose(xy_b, 0) + if missing_a or missing_b: + continue + + cv2.line( + img=scene, + pt1=(int(xy_a[0]), int(xy_a[1])), + pt2=(int(xy_b[0]), int(xy_b[1])), + color=self.color.as_bgr(), + thickness=self.thickness, + ) + + return scene diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 6749f845..b44d3b7c 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -14,28 +14,25 @@ from supervision.validators import validate_keypoints_fields @dataclass class KeyPoints: """ - The `sv.KeyPoints` allows you to convert results from a variety of keypoint - keypoints models into a single, unified format. + The `sv.KeyPoints` class in the Supervision library standardizes results from + various keypoint detection and pose estimation models into a consistent format. This + class simplifies data manipulation and filtering, providing a uniform API for + integration with Supervision annotators. - ```python - import cv2 - import supervision as sv - from ultralytics import YOLO + === "Ultralytics" - image = cv2.imread() - model = YOLO('yolov8s-pose.pt') - result = model(image)[0] - keypoints = sv.KeyPoints.from_ultralytics(result) - ``` + Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics) method, which accepts model results. - !!! tip + ```python + import cv2 + import supervision as sv + from ultralytics import YOLO - In `sv.KeyPoints`, detection data is categorized into two main field types: - fixed and custom. The fixed fields include `xy`, `confidence`, - `class_id`. For any additional data requirements, custom - fields come into play, stored in the data field. These custom fields are easily - accessible using the `keypoints[]` syntax, providing flexibility - for diverse data handling needs. + image = cv2.imread() + model = YOLO('yolov8s-pose.pt') + result = model(image)[0] + key_points = sv.KeyPoints.from_ultralytics(result) + ``` Attributes: xy (np.ndarray): An array of shape `(n, 2)` containing @@ -47,14 +44,6 @@ class KeyPoints: data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional data where each key is a string representing the data type, and the value is either a NumPy array or a list of corresponding data. - - !!! warning - - The `data` field in the `sv.KeyPoints` class is currently in an experimental - phase. Please be aware that its API and functionality are subject to change in - future updates as we continue to refine and improve its capabilities. - We encourage users to experiment with this feature and provide feedback, but - also to be prepared for potential modifications in upcoming releases. """ xy: npt.NDArray[np.float32] @@ -232,7 +221,7 @@ class KeyPoints: Create an empty Keypoints object with no keypoints. Returns: - (Keypoints): An empty Keypoints object. + (KeyPoints): An empty Keypoints object. Example: ```python From 389453777ec94dab77e8d6e474907314ef5bbb55 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 16:18:21 +0200 Subject: [PATCH 084/136] vertex and edge annotators visualizations added --- docs/assets.md | 4 +-- docs/detection/metrics.md | 8 ++--- docs/detection/utils.md | 22 ++++++------- docs/keypoint/annotators.md | 50 ++++++++++++++++++++++++++++++ supervision/assets/list.py | 1 + supervision/keypoint/annotators.py | 10 ++++-- 6 files changed, 74 insertions(+), 21 deletions(-) diff --git a/docs/assets.md b/docs/assets.md index 7e0d7dff..3fd293ee 100644 --- a/docs/assets.md +++ b/docs/assets.md @@ -19,13 +19,13 @@ as an extra within the Supervision package. ```
-

download_assets

+

download_assets

:::supervision.assets.downloader.download_assets
-

VideoAssets

+

VideoAssets

:::supervision.assets.list.VideoAssets diff --git a/docs/detection/metrics.md b/docs/detection/metrics.md index 981f399c..a68f0f96 100644 --- a/docs/detection/metrics.md +++ b/docs/detection/metrics.md @@ -4,18 +4,14 @@ comments: true # Metrics -!!! 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`. - :::supervision.metrics.detection.ConfusionMatrix :::supervision.metrics.detection.MeanAveragePrecision diff --git a/docs/detection/utils.md b/docs/detection/utils.md index cc4eff5d..abacdc21 100644 --- a/docs/detection/utils.md +++ b/docs/detection/utils.md @@ -6,67 +6,67 @@ status: new # Detection Utils
-

box_iou_batch

+

box_iou_batch

:::supervision.detection.utils.box_iou_batch
-

mask_iou_batch

+

mask_iou_batch

:::supervision.detection.utils.mask_iou_batch
-

box_non_max_suppression

+

box_non_max_suppression

:::supervision.detection.utils.box_non_max_suppression
-

mask_non_max_suppression

+

mask_non_max_suppression

:::supervision.detection.utils.mask_non_max_suppression
-

polygon_to_mask

+

polygon_to_mask

:::supervision.detection.utils.polygon_to_mask
-

mask_to_xyxy

+

mask_to_xyxy

:::supervision.detection.utils.mask_to_xyxy
-

mask_to_polygons

+

mask_to_polygons

:::supervision.detection.utils.mask_to_polygons
-

polygon_to_xyxy

+

polygon_to_xyxy

:::supervision.detection.utils.polygon_to_xyxy
-

filter_polygons_by_area

+

filter_polygons_by_area

:::supervision.detection.utils.filter_polygons_by_area
-

move_boxes

+

move_boxes

:::supervision.detection.utils.move_boxes
-

scale_boxes

+

scale_boxes

:::supervision.detection.utils.scale_boxes diff --git a/docs/keypoint/annotators.md b/docs/keypoint/annotators.md index 3efc5b03..b5f998bc 100644 --- a/docs/keypoint/annotators.md +++ b/docs/keypoint/annotators.md @@ -5,6 +5,56 @@ status: new # Annotators +=== "VertexAnnotator" + + ```python + import supervision as sv + + image = ... + key_points = sv.KeyPoints(...) + + vertex_annotator = sv.VertexAnnotator(color=sv.Color.GREEN, radius=10) + annotated_frame = vertex_annotator.annotate( + scene=image.copy(), + key_points=key_points + ) + ``` + +
+ + ![vertex-annotator-example](https://media.roboflow.com/supervision-annotator-examples/vertex-annotator-example.png){ align=center width="800" } + +
+ +=== "EdgeAnnotator" + + ```python + import supervision as sv + + image = ... + key_points = sv.KeyPoints(...) + + edge_annotator = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5) + annotated_frame = edge_annotator.annotate( + scene=image.copy(), + key_points=key_points + ) + ``` + +
+ + ![edge-annotator-example](https://media.roboflow.com/supervision-annotator-examples/edge-annotator-example.png){ align=center width="800" } + +
+ + + :::supervision.keypoint.annotators.VertexAnnotator + + :::supervision.keypoint.annotators.EdgeAnnotator diff --git a/supervision/assets/list.py b/supervision/assets/list.py index 0866431a..8a01b758 100644 --- a/supervision/assets/list.py +++ b/supervision/assets/list.py @@ -19,6 +19,7 @@ class VideoAssets(Enum): | `MARKET_SQUARE` | `market-square.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/market-square.mp4) | | `PEOPLE_WALKING` | `people-walking.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/people-walking.mp4) | | `BEACH` | `beach-1.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/beach-1.mp4) | + | `BASKETBALL` | `basketball-1.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/basketball-1.mp4) | """ # noqa: E501 // docs VEHICLES = "vehicles.mp4" diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py index 5cadc377..4d6df72c 100644 --- a/supervision/keypoint/annotators.py +++ b/supervision/keypoint/annotators.py @@ -63,12 +63,15 @@ class VertexAnnotator(BaseKeyPointAnnotator): image = ... key_points = sv.KeyPoints(...) - vertex_annotator = sv.VertexAnnotator() + vertex_annotator = sv.VertexAnnotator(color=sv.Color.GREEN, radius=10) annotated_frame = vertex_annotator.annotate( scene=image.copy(), key_points=key_points ) ``` + + ![vertex-annotator-example](https://media.roboflow.com/ + supervision-annotator-examples/vertex-annotator-example.png) """ if len(key_points) == 0: return scene @@ -133,12 +136,15 @@ class EdgeAnnotator(BaseKeyPointAnnotator): image = ... key_points = sv.KeyPoints(...) - edge_annotator = sv.EdgeAnnotator() + edge_annotator = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5) annotated_frame = edge_annotator.annotate( scene=image.copy(), key_points=key_points ) ``` + + ![edge-annotator-example](https://media.roboflow.com/ + supervision-annotator-examples/edge-annotator-example.png) """ if len(key_points) == 0: return scene From 908f3d14c6d0dd622211434c23bfd63a3e2fa720 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 16:28:48 +0200 Subject: [PATCH 085/136] improved docs linking --- docs/utils/image.md | 12 ++++++------ docs/utils/iterables.md | 4 ++-- docs/utils/notebook.md | 5 ++--- docs/utils/video.md | 10 +++++----- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/docs/utils/image.md b/docs/utils/image.md index 87fd1943..8f170d35 100644 --- a/docs/utils/image.md +++ b/docs/utils/image.md @@ -6,37 +6,37 @@ status: new # Image Utils
-

crop_image

+

crop_image

:::supervision.utils.image.crop_image
-

scale_image

+

crop_image

:::supervision.utils.image.scale_image
-

resize_image

+

resize_image

:::supervision.utils.image.resize_image
-

letterbox_image

+

letterbox_image

:::supervision.utils.image.letterbox_image
-

overlay_image

+

overlay_image

:::supervision.utils.image.overlay_image
-

ImageSink

+

ImageSink

:::supervision.utils.image.ImageSink diff --git a/docs/utils/iterables.md b/docs/utils/iterables.md index 3ccf98e5..b65cd954 100644 --- a/docs/utils/iterables.md +++ b/docs/utils/iterables.md @@ -6,13 +6,13 @@ status: new # Iterables Utils
-

create_batches

+

create_batches

:::supervision.utils.iterables.create_batches
-

fill

+

fill

:::supervision.utils.iterables.fill diff --git a/docs/utils/notebook.md b/docs/utils/notebook.md index d09b9bbd..3eab046a 100644 --- a/docs/utils/notebook.md +++ b/docs/utils/notebook.md @@ -5,14 +5,13 @@ comments: true # Notebooks Utils
-

plot_image

+

plot_image

:::supervision.utils.notebook.plot_image
-

plot_images_grid -

+

plot_images_grid

:::supervision.utils.notebook.plot_images_grid diff --git a/docs/utils/video.md b/docs/utils/video.md index 1f58450f..f9a5821d 100644 --- a/docs/utils/video.md +++ b/docs/utils/video.md @@ -5,31 +5,31 @@ comments: true # Video Utils
-

VideoInfo

+

VideoInfo

:::supervision.utils.video.VideoInfo
-

VideoSink

+

VideoSink

:::supervision.utils.video.VideoSink
-

FPSMonitor

+

FPSMonitor

:::supervision.utils.video.FPSMonitor
-

get_video_frames_generator

+

get_video_frames_generator

:::supervision.utils.video.get_video_frames_generator
-

process_video

+

process_video

:::supervision.utils.video.process_video From 1c42838ca1c529532af7583d7979d647e04a1045 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 16:35:34 +0200 Subject: [PATCH 086/136] improved docs linking --- docs/utils/draw.md | 21 ++++++++++----------- docs/utils/file.md | 2 +- docs/utils/geometry.md | 4 +++- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/utils/draw.md b/docs/utils/draw.md index 7b47a6a7..84758e06 100644 --- a/docs/utils/draw.md +++ b/docs/utils/draw.md @@ -5,62 +5,61 @@ comments: true # Draw Utils
-

draw_line

+

draw_line

:::supervision.draw.utils.draw_line
-

draw_rectangle

+

draw_rectangle

:::supervision.draw.utils.draw_rectangle
-

draw_filled_rectangle

+

draw_filled_rectangle

:::supervision.draw.utils.draw_filled_rectangle
-

draw_polygon

+

draw_polygon

:::supervision.draw.utils.draw_polygon
-

draw_text

+

draw_text

:::supervision.draw.utils.draw_text
-

draw_image

+

draw_image

:::supervision.draw.utils.draw_image
-

calculate_optimal_font_scale

+

calculate_optimal_font_scale

:::supervision.draw.utils.calculate_optimal_text_scale
-

calculate_optimal_line_thickness

+

calculate_optimal_line_thickness

:::supervision.draw.utils.calculate_optimal_line_thickness
-

Color

+

Color

:::supervision.draw.color.Color
-

ColorPalette

+

ColorPalette

:::supervision.draw.color.ColorPalette - diff --git a/docs/utils/file.md b/docs/utils/file.md index 2ba5a015..a2a08185 100644 --- a/docs/utils/file.md +++ b/docs/utils/file.md @@ -5,7 +5,7 @@ comments: true # File Utils
-

list_files_with_extensions

+

list_files_with_extensions

:::supervision.utils.file.list_files_with_extensions diff --git a/docs/utils/geometry.md b/docs/utils/geometry.md index ca99b626..8c92f562 100644 --- a/docs/utils/geometry.md +++ b/docs/utils/geometry.md @@ -2,6 +2,8 @@ comments: true --- -# Position + :::supervision.geometry.core.Position From 263420758e6b509c069f29005fd7d1b75e2d8817 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:36:15 +0000 Subject: [PATCH 087/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 4 ++-- supervision/keypoint/annotators.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 3e3d0114..8df11eca 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -231,7 +231,7 @@ class Detections: results = model(image)[0] detections = sv.Detections.from_ultralytics(results) ``` - + !!! tip Class names values can be accessed using `detections["class_name"]`. @@ -461,7 +461,7 @@ class Detections: id2label=model.config.id2label ) ``` - + !!! tip Class names values can be accessed using `detections["class_name"]`. diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py index 4d6df72c..4b43765c 100644 --- a/supervision/keypoint/annotators.py +++ b/supervision/keypoint/annotators.py @@ -91,9 +91,10 @@ class VertexAnnotator(BaseKeyPointAnnotator): class EdgeAnnotator(BaseKeyPointAnnotator): """ - A class that specializes in drawing skeleton edges on images using specified key - points. It connects key points with lines to form the skeleton structure. + A class that specializes in drawing skeleton edges on images using specified key + points. It connects key points with lines to form the skeleton structure. """ + def __init__( self, color: Color = Color.ROBOFLOW, From 895562d444959e140bf8f82a3e8dd4b2a40c4d5e Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 16:37:41 +0200 Subject: [PATCH 088/136] make pre-commit happy --- supervision/detection/core.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 3e3d0114..b1ded4d6 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -34,7 +34,8 @@ class Detections: === "Inference" - Use [`sv.Detections.from_inference`](/detection/core/#supervision.detection.core.Detections.from_inference) method, which accepts model results from both detection and segmentation models. + Use [`sv.Detections.from_inference`](/detection/core/#supervision.detection.core.Detections.from_inference) + method, which accepts model results from both detection and segmentation models. ```python import cv2 @@ -49,7 +50,8 @@ class Detections: === "Ultralytics" - Use [`sv.Detections.from_ultralytics`](/detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models. + Use [`sv.Detections.from_ultralytics`](/detection/core/#supervision.detection.core.Detections.from_ultralytics) + method, which accepts model results from both detection and segmentation models. ```python import cv2 @@ -64,7 +66,8 @@ class Detections: === "Transformers" - Use [`sv.Detections.from_transformers`](/detection/core/#supervision.detection.core.Detections.from_transformers) method, which accepts model results from both detection and segmentation models. + Use [`sv.Detections.from_transformers`](/detection/core/#supervision.detection.core.Detections.from_transformers) + method, which accepts model results from both detection and segmentation models. ```python import torch @@ -104,7 +107,7 @@ class Detections: data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional data where each key is a string representing the data type, and the value is either a NumPy array or a list of corresponding data. - """ + """ # noqa: E501 // docs xyxy: np.ndarray mask: Optional[np.ndarray] = None From 383c1a1d8d0fae16fac712667551b01ed4695d5f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:39:01 +0000 Subject: [PATCH 089/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index f305385d..1900954d 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -34,7 +34,7 @@ class Detections: === "Inference" - Use [`sv.Detections.from_inference`](/detection/core/#supervision.detection.core.Detections.from_inference) + Use [`sv.Detections.from_inference`](/detection/core/#supervision.detection.core.Detections.from_inference) method, which accepts model results from both detection and segmentation models. ```python @@ -50,7 +50,7 @@ class Detections: === "Ultralytics" - Use [`sv.Detections.from_ultralytics`](/detection/core/#supervision.detection.core.Detections.from_ultralytics) + Use [`sv.Detections.from_ultralytics`](/detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models. ```python @@ -66,7 +66,7 @@ class Detections: === "Transformers" - Use [`sv.Detections.from_transformers`](/detection/core/#supervision.detection.core.Detections.from_transformers) + Use [`sv.Detections.from_transformers`](/detection/core/#supervision.detection.core.Detections.from_transformers) method, which accepts model results from both detection and segmentation models. ```python From d7da00c55b8dc47034f5f4e49d0493fa1a98c1b9 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 16:43:40 +0200 Subject: [PATCH 090/136] make pre-commit happy --- supervision/keypoint/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index b44d3b7c..ed5d698e 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -21,7 +21,8 @@ class KeyPoints: === "Ultralytics" - Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics) method, which accepts model results. + Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics) + method, which accepts model results. ```python import cv2 @@ -44,7 +45,7 @@ class KeyPoints: data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional data where each key is a string representing the data type, and the value is either a NumPy array or a list of corresponding data. - """ + """ # noqa: E501 // docs xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None From f4fca3339ded4c33379a6609b0f8c441ac4e6808 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:45:18 +0000 Subject: [PATCH 091/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index ed5d698e..8a97b51c 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -21,7 +21,7 @@ class KeyPoints: === "Ultralytics" - Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics) + Use [`sv.KeyPoints.from_ultralytics`](/keypoint/core/#supervision.keypoint.core.KeyPoints.from_ultralytics) method, which accepts model results. ```python From 7a90eefd5b63f7aafba3adf90a839178e3a8684b Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 19:22:59 +0200 Subject: [PATCH 092/136] changelog updated version bump from `0.20.0rc2` to `0.20.0` --- docs/changelog.md | 65 +++++++++++++++++++++++++++++++ docs/deprecated.md | 1 + docs/utils/geometry.md | 6 +++ pyproject.toml | 2 +- supervision/detection/annotate.py | 8 ++-- supervision/geometry/utils.py | 15 +++---- 6 files changed, 82 insertions(+), 15 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 2c5d7ae1..f8d301cd 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,68 @@ +### 0.20.0 April 24, 2024 + +- Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.KeyPoints`](/0.20.0/keypoint/core/#supervision.keypoint.core.KeyPoints) to provide initial support for pose estimation and broader keypoint detection models. + +- Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.EdgeAnnotator`](/0.20.0/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator) and [`sv.VertexAnnotator`](/0.20.0/keypoint/annotators/#supervision.keypoint.annotators.VertexAnnotator) to enable rendering of results from keypoint detection models. + +```python +import cv2 +import supervision as sv +from ultralytics import YOLO + +image = cv2.imread() +model = YOLO('yolov8l-pose') + +result = model(image, verbose=False)[0] +keypoints = sv.KeyPoints.from_ultralytics(result) + +edge_annotators = sv.EdgeAnnotator(color=sv.Color.GREEN, thickness=5) +annotated_image = edge_annotators.annotate(image.copy(), keypoints) +``` + +- Changed [#1037](https://github.com/roboflow/supervision/pull/1037): [`sv.LabelAnnotator`](/0.20.0/annotators/#supervision.annotators.core.LabelAnnotator) by adding an additional `corner_radius` argument that allows for rounding the corners of the bounding box. + +- Changed [#1109](https://github.com/roboflow/supervision/pull/1109): [`sv.PolygonZone`](/0.20.0/detection/tools/polygon_zone/#supervision.detection.tools.polygon_zone.PolygonZone) such that the `frame_resolution_wh` argument is no longer required to initialize `sv.PolygonZone`. + +!!! failure "Deprecated" + + The `frame_resolution_wh` parameter in `sv.PolygonZone` is deprecated and will be removed in `supervision-0.24.0`. + +- Changed [#1084](https://github.com/roboflow/supervision/pull/1084): [`sv.get_polygon_center`](/0.20.0/utils/geometry/#supervision.geometry.core.utils.get_polygon_center) to calculate a more accurate polygon centroid. + +- Changed [#1069](https://github.com/roboflow/supervision/pull/1069): [`sv.Detections.from_transformers`](/0.20.0/detection/core/#supervision.detection.core.Detections.from_transformers) by adding support for Transformers segmentation models and extract class names values. + +```python +import torch +import supervision as sv +from PIL import Image +from transformers import DetrImageProcessor, DetrForSegmentation + +processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50-panoptic") +model = DetrForSegmentation.from_pretrained("facebook/detr-resnet-50-panoptic") + +image = Image.open() +inputs = processor(images=image, return_tensors="pt") + +with torch.no_grad(): + outputs = model(**inputs) + +width, height = image.size +target_size = torch.tensor([[height, width]]) +results = processor.post_process_segmentation( + outputs=outputs, target_sizes=target_size)[0] +detections = sv.Detections.from_transformers(results, id2label=model.config.id2label) + +mask_annotator = sv.MaskAnnotator() +label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER) + +annotated_image = mask_annotator.annotate( + scene=image, detections=detections) +annotated_image = label_annotator.annotate( + scene=annotated_image, detections=detections) +``` + +- Fixed [#787](https://github.com/roboflow/supervision/pull/787): [`sv.ByteTrack.update_with_detections`](/0.20.0/trackers/#supervision.tracker.byte_tracker.core.ByteTrack.update_with_detections) which was removing segmentation masks while tracking. Now, `ByteTrack` can be used alongside segmentation models. + ### 0.19.0 March 15, 2024 - Added [#818](https://github.com/roboflow/supervision/pull/818): [`sv.CSVSink`](/0.19.0/detection/tools/save_detections/#supervision.detection.tools.csv_sink.CSVSink) allowing for the straightforward saving of image, video, or stream inference results in a `.csv` file. diff --git a/docs/deprecated.md b/docs/deprecated.md index cc1e9649..5834b05f 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -16,3 +16,4 @@ These features are phased out due to better alternatives or potential issues in - The method [`FPSMonitor.__call__`](utils/video.md/#supervision.utils.video.FPSMonitor.__call__) is deprecated and will be removed in `supervision-0.22.0`. Use the attribute [`FPSMonitor.fps`](utils/video.md/#supervision.utils.video.FPSMonitor.fps) instead. - The `track_buffer`, `track_thresh`, and `match_thresh` parameters in [`ByterTrack`](trackers.md/#supervision.tracker.byte_tracker.core.ByteTrack) are deprecated and will be removed in `supervision-0.23.0`. Use `lost_track_buffer,` `track_activation_threshold`, and `minimum_matching_threshold` instead. - The `triggering_position ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.23.0`. Use `triggering_anchors ` instead. +- The `frame_resolution_wh ` parameter in [`sv.PolygonZone`](detection/tools/polygon_zone.md/#supervision.detection.tools.polygon_zone.PolygonZone) is deprecated and will be removed in `supervision-0.24.0`. diff --git a/docs/utils/geometry.md b/docs/utils/geometry.md index 8c92f562..d79bcfeb 100644 --- a/docs/utils/geometry.md +++ b/docs/utils/geometry.md @@ -2,6 +2,12 @@ comments: true --- + + +:::supervision.geometry.utils.get_polygon_center + diff --git a/pyproject.toml b/pyproject.toml index 7be53b6c..de0df748 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "supervision" -version = "0.20.0rc2" +version = "0.20.0" description = "A set of easy-to-use utils that will come in handy in any Computer Vision project" authors = ["Piotr Skalski "] maintainers = ["Piotr Skalski "] diff --git a/supervision/detection/annotate.py b/supervision/detection/annotate.py index f32c7344..f496b248 100644 --- a/supervision/detection/annotate.py +++ b/supervision/detection/annotate.py @@ -9,10 +9,6 @@ from supervision.utils.conversion import convert_for_annotation_method from supervision.utils.internal import deprecated -@deprecated( - "`BoxAnnotator` is deprecated and will be removed in " - "`supervision-0.22.0`. Use `BoundingBoxAnnotator` and `LabelAnnotator` instead" -) class BoxAnnotator: """ A class for drawing bounding boxes on an image using detections provided. @@ -46,6 +42,10 @@ class BoxAnnotator: self.text_thickness: int = text_thickness self.text_padding: int = text_padding + @deprecated( + "`BoxAnnotator` is deprecated and will be removed in " + "`supervision-0.22.0`. Use `BoundingBoxAnnotator` and `LabelAnnotator` instead" + ) @convert_for_annotation_method def annotate( self, diff --git a/supervision/geometry/utils.py b/supervision/geometry/utils.py index 9cf717e4..8a0ca35c 100644 --- a/supervision/geometry/utils.py +++ b/supervision/geometry/utils.py @@ -5,12 +5,7 @@ from supervision.geometry.core import Point 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 center + Calculate the center of a polygon. The center is calculated as the center of the solid figure formed by the points of the polygon Parameters: @@ -23,12 +18,12 @@ def get_polygon_center(polygon: np.ndarray) -> Point: Examples: ```python - from supervision.geometry.utils import get_polygon_center import numpy as np + import supervision as sv - vertices = np.array([[0, 0], [0, 2], [2, 2], [2, 0]]) - get_polygon_center(vertices) - Point(x=1, y=1) + polygon = np.array([[0, 0], [0, 2], [2, 2], [2, 0]]) + sv.get_polygon_center(polygon=polygon) + # Point(x=1, y=1) ``` """ From 6ae99444b543d005c2b48104b90f826c37cfa02d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:24:16 +0000 Subject: [PATCH 093/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index f8d301cd..47d160aa 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,6 @@ ### 0.20.0 April 24, 2024 -- Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.KeyPoints`](/0.20.0/keypoint/core/#supervision.keypoint.core.KeyPoints) to provide initial support for pose estimation and broader keypoint detection models. +- Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.KeyPoints`](/0.20.0/keypoint/core/#supervision.keypoint.core.KeyPoints) to provide initial support for pose estimation and broader keypoint detection models. - Added [#1128](https://github.com/roboflow/supervision/pull/1128): [`sv.EdgeAnnotator`](/0.20.0/keypoint/annotators/#supervision.keypoint.annotators.EdgeAnnotator) and [`sv.VertexAnnotator`](/0.20.0/keypoint/annotators/#supervision.keypoint.annotators.VertexAnnotator) to enable rendering of results from keypoint detection models. From 5c4ed2650e0a6b1e2ef3d3de36b3c9ae0fafb5f6 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 19:25:45 +0200 Subject: [PATCH 094/136] drop accidentally committed `from_inference` --- supervision/keypoint/core.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 7019aa95..8a97b51c 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -100,37 +100,6 @@ class KeyPoints: ] ) - @classmethod - def from_inference(cls, inference_result: dict) -> KeyPoints: - if not inference_result.get("predictions"): - return cls.empty() - - xy = [] - confidence = [] - class_id = [] - class_names = [] - - for prediction in inference_result["predictions"]: - prediction_xy = [] - prediction_confidence = [] - for keypoint in prediction["keypoints"]: - prediction_xy.append([keypoint["x"], keypoint["y"]]) - prediction_confidence.append(keypoint["confidence"]) - xy.append(prediction_xy) - confidence.append(prediction_confidence) - - class_id.append(prediction["class_id"]) - class_names.append(prediction["class"]) - - data = {CLASS_NAME_DATA_FIELD: np.array(class_names)} - - return cls( - xy=np.array(xy, dtype=np.float32), - confidence=np.array(confidence, dtype=np.float32), - class_id=np.array(class_id, dtype=int), - data=data, - ) - @classmethod def from_ultralytics(cls, ultralytics_results) -> KeyPoints: """ From a45341ce63d98d982ce7e975ba6df94a5468cdb0 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 24 Apr 2024 22:41:59 +0200 Subject: [PATCH 095/136] final cleanup; setting `supervision>=0.20.0` in `requirements.txt` and refactoring stream processing scripts --- examples/count_people_in_zone/requirements.txt | 2 +- examples/speed_estimation/requirements.txt | 2 +- .../time_in_zone/inference_stream_example.py | 17 +++++++---------- examples/time_in_zone/requirements.txt | 2 +- .../time_in_zone/ultralytics_stream_example.py | 17 +++++++---------- examples/traffic_analysis/requirements.txt | 2 +- 6 files changed, 18 insertions(+), 24 deletions(-) diff --git a/examples/count_people_in_zone/requirements.txt b/examples/count_people_in_zone/requirements.txt index d9e27264..114447fc 100644 --- a/examples/count_people_in_zone/requirements.txt +++ b/examples/count_people_in_zone/requirements.txt @@ -1,5 +1,5 @@ gdown inference -supervision==0.19.0 +supervision>=0.20.0 tqdm ultralytics diff --git a/examples/speed_estimation/requirements.txt b/examples/speed_estimation/requirements.txt index 36de970d..af2e1c13 100644 --- a/examples/speed_estimation/requirements.txt +++ b/examples/speed_estimation/requirements.txt @@ -1,4 +1,4 @@ -supervision==0.19.0 +supervision>=0.20.0 tqdm==4.66.1 requests ultralytics==8.0.237 diff --git a/examples/time_in_zone/inference_stream_example.py b/examples/time_in_zone/inference_stream_example.py index 604aade4..0dfdf660 100644 --- a/examples/time_in_zone/inference_stream_example.py +++ b/examples/time_in_zone/inference_stream_example.py @@ -24,18 +24,15 @@ class CustomSink: self.fps_monitor = sv.FPSMonitor() self.polygons = load_zones_config(file_path=zone_configuration_path) self.timers = [ClockBasedTimer() for _ in self.polygons] - self.zones = None + self.zones = [ + sv.PolygonZone( + polygon=polygon, + triggering_anchors=(sv.Position.CENTER,), + ) + for polygon in self.polygons + ] def on_prediction(self, result: dict, frame: VideoFrame) -> None: - if self.zones is None: - self.zones = [ - sv.PolygonZone( - polygon=polygon, - triggering_anchors=(sv.Position.CENTER,), - ) - for polygon in self.polygons - ] - self.fps_monitor.tick() fps = self.fps_monitor.fps diff --git a/examples/time_in_zone/requirements.txt b/examples/time_in_zone/requirements.txt index fa17b986..752b4a8f 100644 --- a/examples/time_in_zone/requirements.txt +++ b/examples/time_in_zone/requirements.txt @@ -1,5 +1,5 @@ opencv-python -supervision +supervision>=0.20.0 ultralytics inference pytube diff --git a/examples/time_in_zone/ultralytics_stream_example.py b/examples/time_in_zone/ultralytics_stream_example.py index 6262d594..8b8ecd60 100644 --- a/examples/time_in_zone/ultralytics_stream_example.py +++ b/examples/time_in_zone/ultralytics_stream_example.py @@ -25,18 +25,15 @@ class CustomSink: self.fps_monitor = sv.FPSMonitor() self.polygons = load_zones_config(file_path=zone_configuration_path) self.timers = [ClockBasedTimer() for _ in self.polygons] - self.zones = None + self.zones = [ + sv.PolygonZone( + polygon=polygon, + triggering_anchors=(sv.Position.CENTER,), + ) + for polygon in self.polygons + ] def on_prediction(self, detections: sv.Detections, frame: VideoFrame) -> None: - if self.zones is None: - self.zones = [ - sv.PolygonZone( - polygon=polygon, - triggering_anchors=(sv.Position.CENTER,), - ) - for polygon in self.polygons - ] - self.fps_monitor.tick() fps = self.fps_monitor.fps diff --git a/examples/traffic_analysis/requirements.txt b/examples/traffic_analysis/requirements.txt index 6e72dd55..114447fc 100644 --- a/examples/traffic_analysis/requirements.txt +++ b/examples/traffic_analysis/requirements.txt @@ -1,5 +1,5 @@ gdown inference -supervision>=0.19.0 +supervision>=0.20.0 tqdm ultralytics From e253f86e8f2a68fbed76d273092d6529972076e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 00:46:24 +0000 Subject: [PATCH 096/136] :arrow_up: Bump mypy from 1.9.0 to 1.10.0 Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 56 ++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/poetry.lock b/poetry.lock index 95cbef01..e279ed9a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2277,38 +2277,38 @@ files = [ [[package]] name = "mypy" -version = "1.9.0" +version = "1.10.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, ] [package.dependencies] From cab06f2c2b424e547b7c4e80f142b0ba61aeb70d Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Thu, 25 Apr 2024 12:29:08 +0300 Subject: [PATCH 097/136] Implementation of from_yolo_nas for keypoints * Missing batch image support --- supervision/keypoint/core.py | 55 ++++++++++++++++++++++++++++--- supervision/keypoint/skeletons.py | 24 ++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 8a97b51c..587d459c 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,7 +50,8 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List] + ] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -103,7 +104,7 @@ class KeyPoints: @classmethod def from_ultralytics(cls, ultralytics_results) -> KeyPoints: """ - Creates a Keypoints instance from a + Creates a KeyPoints instance from a [YOLOv8](https://github.com/ultralytics/ultralytics) inference result. Args: @@ -111,7 +112,7 @@ class KeyPoints: The output Results instance from YOLOv8 Returns: - KeyPoints: A new Keypoints object. + KeyPoints: A new KeyPoints object. Example: ```python @@ -130,12 +131,56 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] for i in class_id]) + class_names = np.array([ultralytics_results.names[i] + for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} return cls(xy, class_id, confidence, data) + @classmethod + def from_yolo_nas(cls, yolo_nas_results) -> KeyPoints: + """ + Create a KeyPoints instance from a YOLO NAS results. + + Args: + yolo_nas_results (ImagePoseEstimationPrediction): The output object from YOLO NAS. + + Returns: + KeyPoints: A new KeyPoints object. + + Example: + ```python + import cv2 + import supervision as sv + import super_gradients + + image = cv2.imread() + + yolo_nas = super_gradients.training.models.get( + "yolo_nas_pose_s", pretrained_weights="coco_pose").to("cuda") + + results = yolo_nas.predict(image, conf=0.1) + keypoints = sv.KeyPoints.from_yolo_nas(results) + ``` + """ + if len(yolo_nas_results.prediction.poses) == 0: + return cls.empty() + + # TODO: multi-image input + + xy = yolo_nas_results.prediction.poses[:, :, :2] + confidence = yolo_nas_results.prediction.poses[:, :, 2] + class_id = [0] * len(xy) + data = {CLASS_NAME_DATA_FIELD: [f"person" for _ in xy]} + + return cls( + xy=xy, + confidence=confidence, + class_id=np.array(class_id), + data=data, + ) + def __getitem__( self, index: Union[int, slice, List[int], np.ndarray, str] ) -> Union["KeyPoints", List, np.ndarray, None]: @@ -216,7 +261,7 @@ class KeyPoints: self.data[key] = value - @classmethod + @ classmethod def empty(cls) -> KeyPoints: """ Create an empty Keypoints object with no keypoints. diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index 6c110854..01f768b1 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -25,6 +25,30 @@ class Skeleton(Enum): (17, 15), ] + # Hardcoding here, but it also comes within model results + # Note: the classes in the model are 0-indexed, while the skeleton is 1-indexed + YOLO_NAS = [ + (0, 1), + (0, 2), + (1, 2), + (1, 3), + (2, 4), + (3, 5), + (4, 6), + (5, 6), + (5, 7), + (5, 11), + (6, 8), + (6, 12), + (7, 9), + (8, 10), + (11, 12), + (11, 13), + (12, 14), + (13, 15), + (14, 16) + ] + SKELETONS_BY_EDGE_COUNT: Dict[int, Edges] = {} SKELETONS_BY_VERTEX_COUNT: Dict[int, Edges] = {} From 18b3c17ed423649ff933826f72ed68c44b15b89a Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Thu, 25 Apr 2024 12:42:33 +0300 Subject: [PATCH 098/136] Styling --- supervision/keypoint/core.py | 13 ++++++------- supervision/keypoint/skeletons.py | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 587d459c..2f2e9aa4 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,8 +50,7 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List] - ] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -131,8 +130,7 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] - for i in class_id]) + class_names = np.array([ultralytics_results.names[i] for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} @@ -144,7 +142,8 @@ class KeyPoints: Create a KeyPoints instance from a YOLO NAS results. Args: - yolo_nas_results (ImagePoseEstimationPrediction): The output object from YOLO NAS. + yolo_nas_results (ImagePoseEstimationPrediction): + The output object from YOLO NAS. Returns: KeyPoints: A new KeyPoints object. @@ -172,7 +171,7 @@ class KeyPoints: xy = yolo_nas_results.prediction.poses[:, :, :2] confidence = yolo_nas_results.prediction.poses[:, :, 2] class_id = [0] * len(xy) - data = {CLASS_NAME_DATA_FIELD: [f"person" for _ in xy]} + data = {CLASS_NAME_DATA_FIELD: ["person" for _ in xy]} return cls( xy=xy, @@ -261,7 +260,7 @@ class KeyPoints: self.data[key] = value - @ classmethod + @classmethod def empty(cls) -> KeyPoints: """ Create an empty Keypoints object with no keypoints. diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index 01f768b1..beb11281 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -46,7 +46,7 @@ class Skeleton(Enum): (11, 13), (12, 14), (13, 15), - (14, 16) + (14, 16), ] From c8c2ff5c454f36d5caf4823e9c680aa1bc8c8ca5 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Thu, 25 Apr 2024 12:53:09 +0300 Subject: [PATCH 099/136] Minor styling --- supervision/keypoint/core.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 2f2e9aa4..968ee3d4 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -166,8 +166,6 @@ class KeyPoints: if len(yolo_nas_results.prediction.poses) == 0: return cls.empty() - # TODO: multi-image input - xy = yolo_nas_results.prediction.poses[:, :, :2] confidence = yolo_nas_results.prediction.poses[:, :, 2] class_id = [0] * len(xy) From 399342513ab321c8b800aa41b566643ec5bd6b6d Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Thu, 25 Apr 2024 15:00:01 +0300 Subject: [PATCH 100/136] Keypoints: rm NAS skeleton, improve docs examples --- supervision/keypoint/core.py | 12 ++++++++---- supervision/keypoint/skeletons.py | 24 ------------------------ 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 968ee3d4..cc02f84b 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,7 +50,8 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List] + ] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -130,7 +131,8 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] for i in class_id]) + class_names = np.array([ultralytics_results.names[i] + for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} @@ -151,13 +153,15 @@ class KeyPoints: Example: ```python import cv2 + import torch import supervision as sv import super_gradients image = cv2.imread() + device = "cuda" if torch.cuda.is_available() else "cpu" yolo_nas = super_gradients.training.models.get( - "yolo_nas_pose_s", pretrained_weights="coco_pose").to("cuda") + "yolo_nas_pose_s", pretrained_weights="coco_pose").to(device) results = yolo_nas.predict(image, conf=0.1) keypoints = sv.KeyPoints.from_yolo_nas(results) @@ -180,7 +184,7 @@ class KeyPoints: def __getitem__( self, index: Union[int, slice, List[int], np.ndarray, str] - ) -> Union["KeyPoints", List, np.ndarray, None]: + ) -> Union[KeyPoints, List, np.ndarray, None]: """ Get a subset of the KeyPoints object or access an item from its data field. diff --git a/supervision/keypoint/skeletons.py b/supervision/keypoint/skeletons.py index beb11281..6c110854 100644 --- a/supervision/keypoint/skeletons.py +++ b/supervision/keypoint/skeletons.py @@ -25,30 +25,6 @@ class Skeleton(Enum): (17, 15), ] - # Hardcoding here, but it also comes within model results - # Note: the classes in the model are 0-indexed, while the skeleton is 1-indexed - YOLO_NAS = [ - (0, 1), - (0, 2), - (1, 2), - (1, 3), - (2, 4), - (3, 5), - (4, 6), - (5, 6), - (5, 7), - (5, 11), - (6, 8), - (6, 12), - (7, 9), - (8, 10), - (11, 12), - (11, 13), - (12, 14), - (13, 15), - (14, 16), - ] - SKELETONS_BY_EDGE_COUNT: Dict[int, Edges] = {} SKELETONS_BY_VERTEX_COUNT: Dict[int, Edges] = {} From 4bd593fe3c0376bd57aa974be3e8da919268f33c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:02:20 +0000 Subject: [PATCH 101/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index cc02f84b..4790acd7 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,8 +50,7 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List] - ] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -131,8 +130,7 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] - for i in class_id]) + class_names = np.array([ultralytics_results.names[i] for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} From 059583e2ba1d8cab60308a5801ce8a32ea8b5a47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 00:31:25 +0000 Subject: [PATCH 102/136] :arrow_up: Bump mkdocs-material from 9.5.18 to 9.5.19 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.18 to 9.5.19. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.18...9.5.19) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index e279ed9a..f9411209 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2083,34 +2083,34 @@ files = [ [[package]] name = "mkdocs" -version = "1.5.3" +version = "1.6.0" description = "Project documentation with Markdown." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, - {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, + {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, + {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" -markdown = ">=3.2.1" +markdown = ">=3.3.6" markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" packaging = ">=20.5" pathspec = ">=0.11.1" -platformdirs = ">=2.2.0" pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -2127,6 +2127,23 @@ files = [ Markdown = ">=3.3" mkdocs = ">=1.1" +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, + {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + [[package]] name = "mkdocs-git-committers-plugin-2" version = "2.3.0" @@ -2180,13 +2197,13 @@ pygments = ">2.12.0" [[package]] name = "mkdocs-material" -version = "9.5.18" +version = "9.5.19" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.18-py3-none-any.whl", hash = "sha256:1e0e27fc9fe239f9064318acf548771a4629d5fd5dfd45444fd80a953fe21eb4"}, - {file = "mkdocs_material-9.5.18.tar.gz", hash = "sha256:a43f470947053fa2405c33995f282d24992c752a50114f23f30da9d8d0c57e62"}, + {file = "mkdocs_material-9.5.19-py3-none-any.whl", hash = "sha256:ea96e150b6c95f5e4ffe47d78bb712c7bacdd91d2a0bec47f46b6fa0705a86ec"}, + {file = "mkdocs_material-9.5.19.tar.gz", hash = "sha256:7473e06e17e23af608a30ef583fdde8f36389dd3ef56b1d503eed54c89c9618c"}, ] [package.dependencies] @@ -2195,7 +2212,7 @@ cairosvg = {version = ">=2.6,<3.0", optional = true, markers = "extra == \"imagi colorama = ">=0.4,<1.0" jinja2 = ">=3.0,<4.0" markdown = ">=3.2,<4.0" -mkdocs = ">=1.5.3,<1.6.0" +mkdocs = ">=1.6,<2.0" mkdocs-material-extensions = ">=1.3,<2.0" paginate = ">=0.5,<1.0" pillow = {version = ">=10.2,<11.0", optional = true, markers = "extra == \"imaging\""} From 8436f52f8a1d02f7ff7c0251ee2d1b3962684981 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 00:33:10 +0000 Subject: [PATCH 103/136] :arrow_up: Bump ruff from 0.4.1 to 0.4.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.1 to 0.4.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.1...v0.4.2) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index e279ed9a..ebe49f10 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3643,28 +3643,28 @@ files = [ [[package]] name = "ruff" -version = "0.4.1" +version = "0.4.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2d9ef6231e3fbdc0b8c72404a1a0c46fd0dcea84efca83beb4681c318ea6a953"}, - {file = "ruff-0.4.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9485f54a7189e6f7433e0058cf8581bee45c31a25cd69009d2a040d1bd4bfaef"}, - {file = "ruff-0.4.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2921ac03ce1383e360e8a95442ffb0d757a6a7ddd9a5be68561a671e0e5807e"}, - {file = "ruff-0.4.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eec8d185fe193ad053eda3a6be23069e0c8ba8c5d20bc5ace6e3b9e37d246d3f"}, - {file = "ruff-0.4.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa27d9d72a94574d250f42b7640b3bd2edc4c58ac8ac2778a8c82374bb27984"}, - {file = "ruff-0.4.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f1ee41580bff1a651339eb3337c20c12f4037f6110a36ae4a2d864c52e5ef954"}, - {file = "ruff-0.4.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0926cefb57fc5fced629603fbd1a23d458b25418681d96823992ba975f050c2b"}, - {file = "ruff-0.4.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6e37f2e3cd74496a74af9a4fa67b547ab3ca137688c484749189bf3a686ceb"}, - {file = "ruff-0.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd703a5975ac1998c2cc5e9494e13b28f31e66c616b0a76e206de2562e0843c"}, - {file = "ruff-0.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b92f03b4aa9fa23e1799b40f15f8b95cdc418782a567d6c43def65e1bbb7f1cf"}, - {file = "ruff-0.4.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c859f294f8633889e7d77de228b203eb0e9a03071b72b5989d89a0cf98ee262"}, - {file = "ruff-0.4.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b34510141e393519a47f2d7b8216fec747ea1f2c81e85f076e9f2910588d4b64"}, - {file = "ruff-0.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6e68d248ed688b9d69fd4d18737edcbb79c98b251bba5a2b031ce2470224bdf9"}, - {file = "ruff-0.4.1-py3-none-win32.whl", hash = "sha256:b90506f3d6d1f41f43f9b7b5ff845aeefabed6d2494307bc7b178360a8805252"}, - {file = "ruff-0.4.1-py3-none-win_amd64.whl", hash = "sha256:c7d391e5936af5c9e252743d767c564670dc3889aff460d35c518ee76e4b26d7"}, - {file = "ruff-0.4.1-py3-none-win_arm64.whl", hash = "sha256:a1eaf03d87e6a7cd5e661d36d8c6e874693cb9bc3049d110bc9a97b350680c43"}, - {file = "ruff-0.4.1.tar.gz", hash = "sha256:d592116cdbb65f8b1b7e2a2b48297eb865f6bdc20641879aa9d7b9c11d86db79"}, + {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d14dc8953f8af7e003a485ef560bbefa5f8cc1ad994eebb5b12136049bbccc5"}, + {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:24016ed18db3dc9786af103ff49c03bdf408ea253f3cb9e3638f39ac9cf2d483"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2e06459042ac841ed510196c350ba35a9b24a643e23db60d79b2db92af0c2b"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3afabaf7ba8e9c485a14ad8f4122feff6b2b93cc53cd4dad2fd24ae35112d5c5"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799eb468ea6bc54b95527143a4ceaf970d5aa3613050c6cff54c85fda3fde480"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec4ba9436a51527fb6931a8839af4c36a5481f8c19e8f5e42c2f7ad3a49f5069"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a2243f8f434e487c2a010c7252150b1fdf019035130f41b77626f5655c9ca22"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8772130a063f3eebdf7095da00c0b9898bd1774c43b336272c3e98667d4fb8fa"}, + {file = "ruff-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab165ef5d72392b4ebb85a8b0fbd321f69832a632e07a74794c0e598e7a8376"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f32cadf44c2020e75e0c56c3408ed1d32c024766bd41aedef92aa3ca28eef68"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:22e306bf15e09af45ca812bc42fa59b628646fa7c26072555f278994890bc7ac"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82986bb77ad83a1719c90b9528a9dd663c9206f7c0ab69282af8223566a0c34e"}, + {file = "ruff-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:652e4ba553e421a6dc2a6d4868bc3b3881311702633eb3672f9f244ded8908cd"}, + {file = "ruff-0.4.2-py3-none-win32.whl", hash = "sha256:7891ee376770ac094da3ad40c116258a381b86c7352552788377c6eb16d784fe"}, + {file = "ruff-0.4.2-py3-none-win_amd64.whl", hash = "sha256:5ec481661fb2fd88a5d6cf1f83403d388ec90f9daaa36e40e2c003de66751798"}, + {file = "ruff-0.4.2-py3-none-win_arm64.whl", hash = "sha256:cbd1e87c71bca14792948c4ccb51ee61c3296e164019d2d484f3eaa2d360dfaf"}, + {file = "ruff-0.4.2.tar.gz", hash = "sha256:33bcc160aee2520664bc0859cfeaebc84bb7323becff3f303b8f1f2d81cb4edc"}, ] [[package]] From 25bdb560eba3081386028bfcfd32a15d604ec24b Mon Sep 17 00:00:00 2001 From: LinasKo Date: Fri, 26 Apr 2024 17:53:19 +0300 Subject: [PATCH 104/136] from_inference, support direct inference results --- supervision/keypoint/core.py | 48 ++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 7019aa95..b3704929 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -1,5 +1,6 @@ from __future__ import annotations +from contextlib import suppress from dataclasses import dataclass, field from typing import Any, Dict, Iterator, List, Optional, Tuple, Union @@ -101,7 +102,44 @@ class KeyPoints: ) @classmethod - def from_inference(cls, inference_result: dict) -> KeyPoints: + def from_inference(cls, inference_result: Union[dict, Any]) -> KeyPoints: + """ + Create a `sv.KeyPoints` object from the [Roboflow](https://roboflow.com/) + API inference result or the [Inference](https://inference.roboflow.com/) + package results. When a keypoint detection model is used, this method + extracts the keypoint coordinates, class IDs, confidences, and class names. + + Args: + inference_result (dict, any): The result from the + Roboflow API or Inference package containing predictions with keypoints. + + Returns: + (KeyPoints): A KeyPoints object containing the keypoint coordinates, + class IDs, and confidences of each keypoint. + + Example: + ```python + import cv2 + import supervision as sv + from inference import get_model + + image = cv2.imread() + model = get_model(model_id="yolov8s-640") + + result = model.infer(image)[0] + keypoints = sv.KeyPoints.from_inference(result) + ``` + + """ + if isinstance(inference_result, list): + raise ValueError("from_inference() operates on a single result at a time." + "You can retrieve it like so: inference_result = model.infer(image)[0]") + + # Unpack the result if received from inference.get_model, + # rather than inference_sdk.InferenceHTTPClient + with suppress(AttributeError): + inference_result = inference_result.dict(exclude_none=True, by_alias=True) + if not inference_result.get("predictions"): return cls.empty() @@ -120,7 +158,13 @@ class KeyPoints: confidence.append(prediction_confidence) class_id.append(prediction["class_id"]) - class_names.append(prediction["class"]) + + if "class" in prediction: + class_names.append(prediction["class"]) + elif "class_name" in prediction: + class_names.append(prediction["class_name"]) + else: + raise KeyError("Neither 'class' nor 'class_name' found in prediction.") data = {CLASS_NAME_DATA_FIELD: np.array(class_names)} From 619da9575a7d56c5ff13a16e75f956a8f4d6ae32 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Fri, 26 Apr 2024 17:55:17 +0300 Subject: [PATCH 105/136] pre-commit tidy-up --- supervision/keypoint/core.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index b3704929..e206b320 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -129,12 +129,14 @@ class KeyPoints: result = model.infer(image)[0] keypoints = sv.KeyPoints.from_inference(result) ``` - + """ if isinstance(inference_result, list): - raise ValueError("from_inference() operates on a single result at a time." - "You can retrieve it like so: inference_result = model.infer(image)[0]") - + raise ValueError( + "from_inference() operates on a single result at a time." + "You can retrieve it like so: inference_result = model.infer(image)[0]" + ) + # Unpack the result if received from inference.get_model, # rather than inference_sdk.InferenceHTTPClient with suppress(AttributeError): From ba2d30d110b5e8a74281397feb91b75e851bfb17 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 26 Apr 2024 17:36:50 +0200 Subject: [PATCH 106/136] `calculate_dynamic_line_thickness` -> `calculate_optimal_line_thickness` `calculate_dynamic_text_scale` -> `calculate_optimal_text_scale` --- examples/count_people_in_zone/inference_example.py | 4 ++-- examples/count_people_in_zone/ultralytics_example.py | 4 ++-- examples/speed_estimation/inference_example.py | 4 ++-- examples/speed_estimation/ultralytics_example.py | 4 ++-- examples/speed_estimation/yolo_nas_example.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/count_people_in_zone/inference_example.py b/examples/count_people_in_zone/inference_example.py index 163f1771..c5086806 100644 --- a/examples/count_people_in_zone/inference_example.py +++ b/examples/count_people_in_zone/inference_example.py @@ -38,8 +38,8 @@ def initiate_annotators( ) -> Tuple[ List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator] ]: - line_thickness = sv.calculate_dynamic_line_thickness(resolution_wh=resolution_wh) - text_scale = sv.calculate_dynamic_text_scale(resolution_wh=resolution_wh) + line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh) + text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh) zones = [] zone_annotators = [] diff --git a/examples/count_people_in_zone/ultralytics_example.py b/examples/count_people_in_zone/ultralytics_example.py index 861db951..c17e4d7b 100644 --- a/examples/count_people_in_zone/ultralytics_example.py +++ b/examples/count_people_in_zone/ultralytics_example.py @@ -36,8 +36,8 @@ def initiate_annotators( ) -> Tuple[ List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator] ]: - line_thickness = sv.calculate_dynamic_line_thickness(resolution_wh=resolution_wh) - text_scale = sv.calculate_dynamic_text_scale(resolution_wh=resolution_wh) + line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh) + text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh) zones = [] zone_annotators = [] diff --git a/examples/speed_estimation/inference_example.py b/examples/speed_estimation/inference_example.py index a435261c..e715d88e 100644 --- a/examples/speed_estimation/inference_example.py +++ b/examples/speed_estimation/inference_example.py @@ -98,10 +98,10 @@ if __name__ == "__main__": frame_rate=video_info.fps, track_thresh=args.confidence_threshold ) - thickness = sv.calculate_dynamic_line_thickness( + thickness = sv.calculate_optimal_line_thickness( resolution_wh=video_info.resolution_wh ) - text_scale = sv.calculate_dynamic_text_scale(resolution_wh=video_info.resolution_wh) + text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh) bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness) label_annotator = sv.LabelAnnotator( text_scale=text_scale, diff --git a/examples/speed_estimation/ultralytics_example.py b/examples/speed_estimation/ultralytics_example.py index 3218d268..4440d4d9 100644 --- a/examples/speed_estimation/ultralytics_example.py +++ b/examples/speed_estimation/ultralytics_example.py @@ -76,10 +76,10 @@ if __name__ == "__main__": frame_rate=video_info.fps, track_thresh=args.confidence_threshold ) - thickness = sv.calculate_dynamic_line_thickness( + thickness = sv.calculate_optimal_line_thickness( resolution_wh=video_info.resolution_wh ) - text_scale = sv.calculate_dynamic_text_scale(resolution_wh=video_info.resolution_wh) + text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh) bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness) label_annotator = sv.LabelAnnotator( text_scale=text_scale, diff --git a/examples/speed_estimation/yolo_nas_example.py b/examples/speed_estimation/yolo_nas_example.py index 18798166..4bb1d960 100644 --- a/examples/speed_estimation/yolo_nas_example.py +++ b/examples/speed_estimation/yolo_nas_example.py @@ -77,10 +77,10 @@ if __name__ == "__main__": frame_rate=video_info.fps, track_thresh=args.confidence_threshold ) - thickness = sv.calculate_dynamic_line_thickness( + thickness = sv.calculate_optimal_line_thickness( resolution_wh=video_info.resolution_wh ) - text_scale = sv.calculate_dynamic_text_scale(resolution_wh=video_info.resolution_wh) + text_scale = sv.calculate_optimal_text_scale(resolution_wh=video_info.resolution_wh) bounding_box_annotator = sv.BoundingBoxAnnotator(thickness=thickness) label_annotator = sv.LabelAnnotator( text_scale=text_scale, From a5326706119ddf9b29017fcc2d372b39cbceadb4 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 26 Apr 2024 17:55:34 +0200 Subject: [PATCH 107/136] bump version from 0.20.0 to 0.21.0rc1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index de0df748..5960768a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "supervision" -version = "0.20.0" +version = "0.21.0rc1" description = "A set of easy-to-use utils that will come in handy in any Computer Vision project" authors = ["Piotr Skalski "] maintainers = ["Piotr Skalski "] From c5a2f87bb91c8c27d27b50ba6ba84681ab20f432 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 01:20:56 +0000 Subject: [PATCH 108/136] :arrow_up: Bump pytest from 8.1.1 to 8.2.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.1 to 8.2.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.1.1...8.2.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index bc59c72e..bc65995e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2832,13 +2832,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest- [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -3037,13 +3037,13 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "pytest" -version = "8.1.1" +version = "8.2.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, - {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, + {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, + {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, ] [package.dependencies] @@ -3051,11 +3051,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.4,<2.0" +pluggy = ">=1.5,<2.0" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "python-dateutil" From 31844c165bc43397a9f6253e13bb01994f537ed6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 01:24:07 +0000 Subject: [PATCH 109/136] :arrow_up: Bump mkdocstrings from 0.24.3 to 0.25.0 Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.24.3 to 0.25.0. - [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases) - [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.24.3...0.25.0) --- updated-dependencies: - dependency-name: mkdocstrings dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index bc59c72e..303c9195 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2239,13 +2239,13 @@ files = [ [[package]] name = "mkdocstrings" -version = "0.24.3" +version = "0.25.0" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocstrings-0.24.3-py3-none-any.whl", hash = "sha256:5c9cf2a32958cd161d5428699b79c8b0988856b0d4a8c5baf8395fc1bf4087c3"}, - {file = "mkdocstrings-0.24.3.tar.gz", hash = "sha256:f327b234eb8d2551a306735436e157d0a22d45f79963c60a8b585d5f7a94c1d2"}, + {file = "mkdocstrings-0.25.0-py3-none-any.whl", hash = "sha256:df1b63f26675fcde8c1b77e7ea996cd2f93220b148e06455428f676f5dc838f1"}, + {file = "mkdocstrings-0.25.0.tar.gz", hash = "sha256:066986b3fb5b9ef2d37c4417255a808f7e63b40ff8f67f6cab8054d903fbc91d"}, ] [package.dependencies] @@ -4267,4 +4267,4 @@ desktop = ["opencv-python"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "56ddae6824a9f28c9954badd4c642c57f687b099c6169f97fa0372e294500c17" +content-hash = "a2019dd5d779d7fe9ae684d9b133bceb519c87123fd9917b42a86984bc2a7d3b" diff --git a/pyproject.toml b/pyproject.toml index 5960768a..f8de1a9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ nbconvert = "^7.14.2" [tool.poetry.group.docs.dependencies] mkdocs-material = {extras = ["imaging"], version = "^9.5.5"} -mkdocstrings = {extras = ["python"], version = ">=0.20,<0.25"} +mkdocstrings = {extras = ["python"], version = ">=0.20,<0.26"} mike = "^2.0.0" # For Documentation Development use Python 3.10 or above # Use Latest mkdocs-jupyter min 0.24.6 for Jupyter Notebook Theme support From df0b2df2f68ffa72c4a58b069bd6dfdf09034ce6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 01:25:37 +0000 Subject: [PATCH 110/136] :arrow_up: Bump tox from 4.14.2 to 4.15.0 Bumps [tox](https://github.com/tox-dev/tox) from 4.14.2 to 4.15.0. - [Release notes](https://github.com/tox-dev/tox/releases) - [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.14.2...4.15.0) --- updated-dependencies: - dependency-name: tox dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index bc59c72e..d8ed39d2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3957,13 +3957,13 @@ files = [ [[package]] name = "tox" -version = "4.14.2" +version = "4.15.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.8" files = [ - {file = "tox-4.14.2-py3-none-any.whl", hash = "sha256:2900c4eb7b716af4a928a7fdc2ed248ad6575294ed7cfae2ea41203937422847"}, - {file = "tox-4.14.2.tar.gz", hash = "sha256:0defb44f6dafd911b61788325741cc6b2e12ea71f987ac025ad4d649f1f1a104"}, + {file = "tox-4.15.0-py3-none-any.whl", hash = "sha256:300055f335d855b2ab1b12c5802de7f62a36d4fd53f30bd2835f6a201dda46ea"}, + {file = "tox-4.15.0.tar.gz", hash = "sha256:7a0beeef166fbe566f54f795b4906c31b428eddafc0102ac00d20998dd1933f6"}, ] [package.dependencies] From d2d69f986eedfa3bb1d064ec2659dc283ea51f9d Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Mon, 29 Apr 2024 09:46:12 +0300 Subject: [PATCH 111/136] from_infernece: Example suggests users find their own model * Example incorrectly suggested yolov8s-640 * Replaced with "" until we have a COCO human pose model on prod. I don't think "horse-pose/3" is helpful - the use case is uncommon and the training quality is poor. --- supervision/keypoint/core.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index e206b320..d1021b94 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -51,7 +51,8 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List] + ] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -124,7 +125,7 @@ class KeyPoints: from inference import get_model image = cv2.imread() - model = get_model(model_id="yolov8s-640") + model = get_model(model_id="") result = model.infer(image)[0] keypoints = sv.KeyPoints.from_inference(result) @@ -140,7 +141,8 @@ class KeyPoints: # Unpack the result if received from inference.get_model, # rather than inference_sdk.InferenceHTTPClient with suppress(AttributeError): - inference_result = inference_result.dict(exclude_none=True, by_alias=True) + inference_result = inference_result.dict( + exclude_none=True, by_alias=True) if not inference_result.get("predictions"): return cls.empty() @@ -166,7 +168,8 @@ class KeyPoints: elif "class_name" in prediction: class_names.append(prediction["class_name"]) else: - raise KeyError("Neither 'class' nor 'class_name' found in prediction.") + raise KeyError( + "Neither 'class' nor 'class_name' found in prediction.") data = {CLASS_NAME_DATA_FIELD: np.array(class_names)} @@ -207,7 +210,8 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] for i in class_id]) + class_names = np.array([ultralytics_results.names[i] + for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} From 0dd8d7dca843f7e93336469c4157b3de79b65d58 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 06:53:33 +0000 Subject: [PATCH 112/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/core.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index d1021b94..09c307f5 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -51,8 +51,7 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List] - ] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -141,8 +140,7 @@ class KeyPoints: # Unpack the result if received from inference.get_model, # rather than inference_sdk.InferenceHTTPClient with suppress(AttributeError): - inference_result = inference_result.dict( - exclude_none=True, by_alias=True) + inference_result = inference_result.dict(exclude_none=True, by_alias=True) if not inference_result.get("predictions"): return cls.empty() @@ -168,8 +166,7 @@ class KeyPoints: elif "class_name" in prediction: class_names.append(prediction["class_name"]) else: - raise KeyError( - "Neither 'class' nor 'class_name' found in prediction.") + raise KeyError("Neither 'class' nor 'class_name' found in prediction.") data = {CLASS_NAME_DATA_FIELD: np.array(class_names)} @@ -210,8 +207,7 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] - for i in class_id]) + class_names = np.array([ultralytics_results.names[i] for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} From 7331577e6905b87506a7a8f761af528e9faec82e Mon Sep 17 00:00:00 2001 From: LinasKo Date: Mon, 29 Apr 2024 13:37:37 +0300 Subject: [PATCH 113/136] keypoints, from_inference: removed class_name option --- supervision/keypoint/core.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 09c307f5..8456eb6f 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -160,13 +160,7 @@ class KeyPoints: confidence.append(prediction_confidence) class_id.append(prediction["class_id"]) - - if "class" in prediction: - class_names.append(prediction["class"]) - elif "class_name" in prediction: - class_names.append(prediction["class_name"]) - else: - raise KeyError("Neither 'class' nor 'class_name' found in prediction.") + class_names.append(prediction["class"]) data = {CLASS_NAME_DATA_FIELD: np.array(class_names)} From 67160c96b04278bcd43ceba272553e3292717528 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:52:51 +0000 Subject: [PATCH 114/136] =?UTF-8?q?chore(pre=5Fcommit):=20=E2=AC=86=20pre?= =?UTF-8?q?=5Fcommit=20autoupdate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.1...v0.4.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef3620b1..c0903f3a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.1 + rev: v0.4.2 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From a70588284a1f37989bde166987e498f15982bcff Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 29 Apr 2024 20:36:05 +0200 Subject: [PATCH 115/136] add Roboflow API example --- supervision/keypoint/core.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 8456eb6f..bbabca66 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -124,12 +124,26 @@ class KeyPoints: from inference import get_model image = cv2.imread() - model = get_model(model_id="") + model = get_model(model_id=) result = model.infer(image)[0] - keypoints = sv.KeyPoints.from_inference(result) + key_points = sv.KeyPoints.from_inference(result) ``` + ```python + import cv2 + import supervision as sv + from inference_sdk import InferenceHTTPClient + + image = cv2.imread() + client = InferenceHTTPClient( + api_url="https://detect.roboflow.com", + api_key= + ) + + result = client.infer(image, model_id=) + key_points = sv.KeyPoints.from_inference(result) + ``` """ if isinstance(inference_result, list): raise ValueError( From 66f6a86bbfb0ef7da6fd9a6ab7f1ff474691b350 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 29 Apr 2024 20:37:56 +0200 Subject: [PATCH 116/136] add Roboflow API KEY to example --- supervision/keypoint/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index bbabca66..45d2d9b4 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -124,7 +124,7 @@ class KeyPoints: from inference import get_model image = cv2.imread() - model = get_model(model_id=) + model = get_model(model_id=, api_key=) result = model.infer(image)[0] key_points = sv.KeyPoints.from_inference(result) From d4b1375db7bbb9716921e2076138593b901257db Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 29 Apr 2024 21:46:07 +0200 Subject: [PATCH 117/136] pin inference version --- examples/speed_estimation/requirements.txt | 2 +- examples/time_in_zone/requirements.txt | 2 +- examples/traffic_analysis/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/speed_estimation/requirements.txt b/examples/speed_estimation/requirements.txt index af2e1c13..b3aa2456 100644 --- a/examples/speed_estimation/requirements.txt +++ b/examples/speed_estimation/requirements.txt @@ -3,4 +3,4 @@ tqdm==4.66.1 requests ultralytics==8.0.237 super-gradients==3.5.0 -inference==0.9.8 +inference<=0.9.21 diff --git a/examples/time_in_zone/requirements.txt b/examples/time_in_zone/requirements.txt index 752b4a8f..df94e4bd 100644 --- a/examples/time_in_zone/requirements.txt +++ b/examples/time_in_zone/requirements.txt @@ -1,5 +1,5 @@ opencv-python supervision>=0.20.0 ultralytics -inference +inference<=0.9.21 pytube diff --git a/examples/traffic_analysis/requirements.txt b/examples/traffic_analysis/requirements.txt index 114447fc..536ac653 100644 --- a/examples/traffic_analysis/requirements.txt +++ b/examples/traffic_analysis/requirements.txt @@ -1,5 +1,5 @@ gdown -inference +inference<=0.9.21 supervision>=0.20.0 tqdm ultralytics From 4732b6f1d0142192a9faea635670e64696cae004 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 29 Apr 2024 22:58:13 +0200 Subject: [PATCH 118/136] bump version from 0.21.0rc1 to 0.21.0rc2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f8de1a9e..426ffb7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "supervision" -version = "0.21.0rc1" +version = "0.21.0rc2" description = "A set of easy-to-use utils that will come in handy in any Computer Vision project" authors = ["Piotr Skalski "] maintainers = ["Piotr Skalski "] From 71919f1d6fcbbd9a5debb4f102ae15e5118e63c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 00:33:04 +0000 Subject: [PATCH 119/136] :arrow_up: Bump nbconvert from 7.16.3 to 7.16.4 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.16.3 to 7.16.4. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md) - [Commits](https://github.com/jupyter/nbconvert/compare/v7.16.3...v7.16.4) --- updated-dependencies: - dependency-name: nbconvert dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 754f37f2..be9666ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2374,13 +2374,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.16.3" +version = "7.16.4" description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." optional = false python-versions = ">=3.8" files = [ - {file = "nbconvert-7.16.3-py3-none-any.whl", hash = "sha256:ddeff14beeeedf3dd0bc506623e41e4507e551736de59df69a91f86700292b3b"}, - {file = "nbconvert-7.16.3.tar.gz", hash = "sha256:a6733b78ce3d47c3f85e504998495b07e6ea9cf9bf6ec1c98dda63ec6ad19142"}, + {file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"}, + {file = "nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"}, ] [package.dependencies] @@ -2402,9 +2402,9 @@ tinycss2 = "*" traitlets = ">=5.1" [package.extras] -all = ["nbconvert[docs,qtpdf,serve,test,webpdf]"] +all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"] docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] -qtpdf = ["nbconvert[qtpng]"] +qtpdf = ["pyqtwebengine (>=5.15)"] qtpng = ["pyqtwebengine (>=5.15)"] serve = ["tornado (>=6.1)"] test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"] From 71fd3178ccdcd38e095b638f69d4b0e8d9174c85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 00:35:39 +0000 Subject: [PATCH 120/136] :arrow_up: Bump mkdocs-material from 9.5.19 to 9.5.20 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.19 to 9.5.20. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.19...9.5.20) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 754f37f2..367af17b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2197,13 +2197,13 @@ pygments = ">2.12.0" [[package]] name = "mkdocs-material" -version = "9.5.19" +version = "9.5.20" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.19-py3-none-any.whl", hash = "sha256:ea96e150b6c95f5e4ffe47d78bb712c7bacdd91d2a0bec47f46b6fa0705a86ec"}, - {file = "mkdocs_material-9.5.19.tar.gz", hash = "sha256:7473e06e17e23af608a30ef583fdde8f36389dd3ef56b1d503eed54c89c9618c"}, + {file = "mkdocs_material-9.5.20-py3-none-any.whl", hash = "sha256:ad0094a7597bcb5d0cc3e8e543a10927c2581f7f647b9bb4861600f583180f9b"}, + {file = "mkdocs_material-9.5.20.tar.gz", hash = "sha256:986eef0250d22f70fb06ce0f4eac64cc92bd797a589ec3892ce31fad976fe3da"}, ] [package.dependencies] From 7fd5d80a3bd7b104f87e614c5b28f689de26d82e Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 30 Apr 2024 12:30:44 +0200 Subject: [PATCH 121/136] bump version from 0.21.0rc2 to 0.21.0rc3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 426ffb7c..1ea80ebf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "supervision" -version = "0.21.0rc2" +version = "0.21.0rc3" description = "A set of easy-to-use utils that will come in handy in any Computer Vision project" authors = ["Piotr Skalski "] maintainers = ["Piotr Skalski "] From ffda94b1e526aa1fa047df38db1336b700ee40fd Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 30 Apr 2024 16:06:02 +0200 Subject: [PATCH 122/136] pin inference version to 0.9.17 --- examples/count_people_in_zone/requirements.txt | 2 +- examples/speed_estimation/requirements.txt | 2 +- examples/time_in_zone/requirements.txt | 2 +- examples/tracking/requirements.txt | 2 +- examples/traffic_analysis/requirements.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/count_people_in_zone/requirements.txt b/examples/count_people_in_zone/requirements.txt index 114447fc..1cd58655 100644 --- a/examples/count_people_in_zone/requirements.txt +++ b/examples/count_people_in_zone/requirements.txt @@ -1,5 +1,5 @@ gdown -inference +inference==0.9.17 supervision>=0.20.0 tqdm ultralytics diff --git a/examples/speed_estimation/requirements.txt b/examples/speed_estimation/requirements.txt index b3aa2456..aa51418f 100644 --- a/examples/speed_estimation/requirements.txt +++ b/examples/speed_estimation/requirements.txt @@ -3,4 +3,4 @@ tqdm==4.66.1 requests ultralytics==8.0.237 super-gradients==3.5.0 -inference<=0.9.21 +inference==0.9.17 diff --git a/examples/time_in_zone/requirements.txt b/examples/time_in_zone/requirements.txt index df94e4bd..6154c3eb 100644 --- a/examples/time_in_zone/requirements.txt +++ b/examples/time_in_zone/requirements.txt @@ -1,5 +1,5 @@ opencv-python supervision>=0.20.0 ultralytics -inference<=0.9.21 +inference==0.9.17 pytube diff --git a/examples/tracking/requirements.txt b/examples/tracking/requirements.txt index 8d5a9233..a45d9291 100644 --- a/examples/tracking/requirements.txt +++ b/examples/tracking/requirements.txt @@ -1,4 +1,4 @@ -inference +inference==0.9.17 supervision==0.19.0 tqdm ultralytics diff --git a/examples/traffic_analysis/requirements.txt b/examples/traffic_analysis/requirements.txt index 536ac653..1cd58655 100644 --- a/examples/traffic_analysis/requirements.txt +++ b/examples/traffic_analysis/requirements.txt @@ -1,5 +1,5 @@ gdown -inference<=0.9.21 +inference==0.9.17 supervision>=0.20.0 tqdm ultralytics From 33e69b2da323e134e81b5a03eec908a584fd5f19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 00:35:55 +0000 Subject: [PATCH 123/136] :arrow_up: Bump mkdocs-git-revision-date-localized-plugin Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.2.4 to 1.2.5. - [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases) - [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.2.4...v1.2.5) --- updated-dependencies: - dependency-name: mkdocs-git-revision-date-localized-plugin dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index a980eb6a..89c2d4cd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2162,13 +2162,13 @@ requests = "*" [[package]] name = "mkdocs-git-revision-date-localized-plugin" -version = "1.2.4" +version = "1.2.5" description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs-git-revision-date-localized-plugin-1.2.4.tar.gz", hash = "sha256:08fd0c6f33c8da9e00daf40f7865943113b3879a1c621b2bbf0fa794ffe997d3"}, - {file = "mkdocs_git_revision_date_localized_plugin-1.2.4-py3-none-any.whl", hash = "sha256:1f94eb510862ef94e982a2910404fa17a1657ecf29f45a07b0f438c00767fc85"}, + {file = "mkdocs_git_revision_date_localized_plugin-1.2.5-py3-none-any.whl", hash = "sha256:d796a18b07cfcdb154c133e3ec099d2bb5f38389e4fd54d3eb516a8a736815b8"}, + {file = "mkdocs_git_revision_date_localized_plugin-1.2.5.tar.gz", hash = "sha256:0c439816d9d0dba48e027d9d074b2b9f1d7cd179f74ba46b51e4da7bb3dc4b9b"}, ] [package.dependencies] From ba30244adda2bea4f39acd03d1ac6fc583839ff6 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Wed, 1 May 2024 15:20:50 +0300 Subject: [PATCH 124/136] Remove hardcoding of class_names, class_id. * Still hardcoded ID to -1 and name to "" if not provided - this lets us stick to (n,) shape. * Found no way to add class_names to pose tracker, so used the response of YOLO NAS detection instead, to check what the response looks like. --- supervision/keypoint/core.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 4790acd7..10dfb7a0 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,7 +50,8 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List] + ] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -130,7 +131,8 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] for i in class_id]) + class_names = np.array([ultralytics_results.names[i] + for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} @@ -170,13 +172,27 @@ class KeyPoints: xy = yolo_nas_results.prediction.poses[:, :, :2] confidence = yolo_nas_results.prediction.poses[:, :, 2] - class_id = [0] * len(xy) - data = {CLASS_NAME_DATA_FIELD: ["person" for _ in xy]} + + # yolo_nas_results treats params differently. + # prediction.labels may not exist, whereas class_names might be None + if hasattr(yolo_nas_results.prediction, "labels"): + class_id = yolo_nas_results.prediction.labels # np.array[int] + else: + class_id = np.array([-1] * len(xy)) + + class_names = [""] * len(class_id) + if yolo_nas_results.class_names: + for i, c_id in enumerate(class_id): + if c_id != -1: + name = yolo_nas_results.class_names[c_id] # tuple[str] + class_names[i] = name + + data = {CLASS_NAME_DATA_FIELD: class_names} return cls( xy=xy, confidence=confidence, - class_id=np.array(class_id), + class_id=class_id, data=data, ) From aab3ed2ba9fecc0cb5ed13cf5f72aed817c2e540 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 12:23:51 +0000 Subject: [PATCH 125/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 10dfb7a0..89a56056 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,8 +50,7 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List] - ] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -131,8 +130,7 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] - for i in class_id]) + class_names = np.array([ultralytics_results.names[i] for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} From 079008cd176fd0ab207c8905b83ea0e753e09f7b Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Thu, 2 May 2024 16:30:01 +0300 Subject: [PATCH 126/136] from_yolo_nas - None class_id & names if not found --- supervision/keypoint/core.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 89a56056..d056819e 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -176,16 +176,15 @@ class KeyPoints: if hasattr(yolo_nas_results.prediction, "labels"): class_id = yolo_nas_results.prediction.labels # np.array[int] else: - class_id = np.array([-1] * len(xy)) + class_id = None - class_names = [""] * len(class_id) - if yolo_nas_results.class_names: - for i, c_id in enumerate(class_id): - if c_id != -1: - name = yolo_nas_results.class_names[c_id] # tuple[str] - class_names[i] = name - - data = {CLASS_NAME_DATA_FIELD: class_names} + data = {} + if class_id is not None and yolo_nas_results.class_names is not None: + class_names = [] + for c_id in class_id: + name = yolo_nas_results.class_names[c_id] # tuple[str] + class_names.append(name) + data[CLASS_NAME_DATA_FIELD] = class_names return cls( xy=xy, From 347b75935b114da82431b0a9f9ec973eb5e4d1e4 Mon Sep 17 00:00:00 2001 From: Linas Kondrackis Date: Thu, 2 May 2024 16:31:48 +0300 Subject: [PATCH 127/136] Ruff --- supervision/keypoint/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index d056819e..ae08e38a 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,7 +50,8 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List] + ] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -130,7 +131,8 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] for i in class_id]) + class_names = np.array([ultralytics_results.names[i] + for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} From 7e79fa3d07466f90e928d12644d84f31b051eb69 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 13:34:11 +0000 Subject: [PATCH 128/136] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20aut?= =?UTF-8?q?o=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/keypoint/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index ae08e38a..d056819e 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -50,8 +50,7 @@ class KeyPoints: xy: npt.NDArray[np.float32] class_id: Optional[npt.NDArray[np.int_]] = None confidence: Optional[npt.NDArray[np.float32]] = None - data: Dict[str, Union[npt.NDArray[Any], List] - ] = field(default_factory=dict) + data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict) def __post_init__(self): validate_keypoints_fields( @@ -131,8 +130,7 @@ class KeyPoints: xy = ultralytics_results.keypoints.xy.cpu().numpy() class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int) - class_names = np.array([ultralytics_results.names[i] - for i in class_id]) + class_names = np.array([ultralytics_results.names[i] for i in class_id]) confidence = ultralytics_results.keypoints.conf.cpu().numpy() data = {CLASS_NAME_DATA_FIELD: class_names} From 7d5a0bce45aeeb5a54d6e906e99509a33cd779ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 00:33:20 +0000 Subject: [PATCH 129/136] :arrow_up: Bump tqdm from 4.66.2 to 4.66.4 Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.2 to 4.66.4. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.66.2...v4.66.4) --- updated-dependencies: - dependency-name: tqdm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 89c2d4cd..b895934c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3984,13 +3984,13 @@ testing = ["build[virtualenv] (>=1.0.3)", "covdefaults (>=2.3)", "detect-test-po [[package]] name = "tqdm" -version = "4.66.2" +version = "4.66.4" description = "Fast, Extensible Progress Meter" optional = true python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, - {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, + {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, + {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, ] [package.dependencies] @@ -4267,4 +4267,4 @@ desktop = ["opencv-python"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "a2019dd5d779d7fe9ae684d9b133bceb519c87123fd9917b42a86984bc2a7d3b" +content-hash = "29af5aa06f97e77a2dba94c5a6d77d7d1903448724df07416026a378d3c6a64d" diff --git a/pyproject.toml b/pyproject.toml index 1ea80ebf..509c05b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ defusedxml = "^0.7.1" opencv-python = { version = ">=4.5.5.64", optional = true } opencv-python-headless = ">=4.5.5.64" requests = { version = ">=2.26.0,<=2.31.0", optional = true } -tqdm = { version = ">=4.62.3,<=4.66.2", optional = true } +tqdm = { version = ">=4.62.3,<=4.66.4", optional = true } pillow = ">=9.4" [tool.poetry.extras] From 29b0c514cf1be01b4c7954b10cad7c0ea1de8204 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 00:36:24 +0000 Subject: [PATCH 130/136] :arrow_up: Bump mike from 2.0.0 to 2.1.0 Bumps [mike](https://github.com/jimporter/mike) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/jimporter/mike/releases) - [Changelog](https://github.com/jimporter/mike/blob/master/CHANGES.md) - [Commits](https://github.com/jimporter/mike/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: mike dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 89c2d4cd..76605a88 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2048,13 +2048,13 @@ files = [ [[package]] name = "mike" -version = "2.0.0" +version = "2.1.0" description = "Manage multiple versions of your MkDocs-powered documentation" optional = false python-versions = "*" files = [ - {file = "mike-2.0.0-py3-none-any.whl", hash = "sha256:87f496a65900f93ba92d72940242b65c86f3f2f82871bc60ebdcffc91fad1d9e"}, - {file = "mike-2.0.0.tar.gz", hash = "sha256:566f1cab1a58cc50b106fb79ea2f1f56e7bfc8b25a051e95e6eaee9fba0922de"}, + {file = "mike-2.1.0-py3-none-any.whl", hash = "sha256:b3885f9b9e31fc4b0d61de473750d38ac170a6b291585076effb51a806245608"}, + {file = "mike-2.1.0.tar.gz", hash = "sha256:f0b8e51cbfae1273d648ffb602a4ab3061e57972ca1cd6836df1c51c01a36eb5"}, ] [package.dependencies] From f72d06b05e7283946f2600be237d1cf020d13c01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 21:51:32 +0000 Subject: [PATCH 131/136] Bump tqdm from 4.66.1 to 4.66.3 in /examples/speed_estimation Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.1 to 4.66.3. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.3) --- updated-dependencies: - dependency-name: tqdm dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- examples/speed_estimation/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/speed_estimation/requirements.txt b/examples/speed_estimation/requirements.txt index aa51418f..343a687d 100644 --- a/examples/speed_estimation/requirements.txt +++ b/examples/speed_estimation/requirements.txt @@ -1,5 +1,5 @@ supervision>=0.20.0 -tqdm==4.66.1 +tqdm==4.66.3 requests ultralytics==8.0.237 super-gradients==3.5.0 From 92d56eda150941d69bfe71a48b21e4edd024ad5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 01:06:46 +0000 Subject: [PATCH 132/136] :arrow_up: Bump mike from 2.1.0 to 2.1.1 Bumps [mike](https://github.com/jimporter/mike) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/jimporter/mike/releases) - [Changelog](https://github.com/jimporter/mike/blob/master/CHANGES.md) - [Commits](https://github.com/jimporter/mike/compare/v2.1.0...v2.1.1) --- updated-dependencies: - dependency-name: mike dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2497d802..e4ec3872 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2048,13 +2048,13 @@ files = [ [[package]] name = "mike" -version = "2.1.0" +version = "2.1.1" description = "Manage multiple versions of your MkDocs-powered documentation" optional = false python-versions = "*" files = [ - {file = "mike-2.1.0-py3-none-any.whl", hash = "sha256:b3885f9b9e31fc4b0d61de473750d38ac170a6b291585076effb51a806245608"}, - {file = "mike-2.1.0.tar.gz", hash = "sha256:f0b8e51cbfae1273d648ffb602a4ab3061e57972ca1cd6836df1c51c01a36eb5"}, + {file = "mike-2.1.1-py3-none-any.whl", hash = "sha256:0b1d01a397a423284593eeb1b5f3194e37169488f929b860c9bfe95c0d5efb79"}, + {file = "mike-2.1.1.tar.gz", hash = "sha256:f39ed39f3737da83ad0adc33e9f885092ed27f8c9e7ff0523add0480352a2c22"}, ] [package.dependencies] @@ -2064,6 +2064,7 @@ jinja2 = ">=2.7" mkdocs = ">=1.0" pyparsing = ">=3.0" pyyaml = ">=5.1" +pyyaml-env-tag = "*" verspec = "*" [package.extras] From f7d21acd3a40bfaab5729ba107915248edfdcbd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 01:09:12 +0000 Subject: [PATCH 133/136] :arrow_up: Bump mkdocs-material from 9.5.20 to 9.5.21 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.20 to 9.5.21. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.20...9.5.21) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2497d802..4608f00f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2197,13 +2197,13 @@ pygments = ">2.12.0" [[package]] name = "mkdocs-material" -version = "9.5.20" +version = "9.5.21" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.20-py3-none-any.whl", hash = "sha256:ad0094a7597bcb5d0cc3e8e543a10927c2581f7f647b9bb4861600f583180f9b"}, - {file = "mkdocs_material-9.5.20.tar.gz", hash = "sha256:986eef0250d22f70fb06ce0f4eac64cc92bd797a589ec3892ce31fad976fe3da"}, + {file = "mkdocs_material-9.5.21-py3-none-any.whl", hash = "sha256:210e1f179682cd4be17d5c641b2f4559574b9dea2f589c3f0e7c17c5bd1959bc"}, + {file = "mkdocs_material-9.5.21.tar.gz", hash = "sha256:049f82770f40559d3c2aa2259c562ea7257dbb4aaa9624323b5ef27b2d95a450"}, ] [package.dependencies] From ff52352b006ca0ffcee80792289aeb165ece9017 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 01:12:45 +0000 Subject: [PATCH 134/136] :arrow_up: Bump jupytext from 1.16.1 to 1.16.2 Bumps [jupytext](https://github.com/mwouts/jupytext) from 1.16.1 to 1.16.2. - [Release notes](https://github.com/mwouts/jupytext/releases) - [Changelog](https://github.com/mwouts/jupytext/blob/main/CHANGELOG.md) - [Commits](https://github.com/mwouts/jupytext/compare/v1.16.1...v1.16.2) --- updated-dependencies: - dependency-name: jupytext dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2497d802..87dc0781 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1648,13 +1648,13 @@ files = [ [[package]] name = "jupytext" -version = "1.16.1" +version = "1.16.2" description = "Jupyter notebooks as Markdown documents, Julia, Python or R scripts" optional = false python-versions = ">=3.8" files = [ - {file = "jupytext-1.16.1-py3-none-any.whl", hash = "sha256:796ec4f68ada663569e5d38d4ef03738a01284bfe21c943c485bc36433898bd0"}, - {file = "jupytext-1.16.1.tar.gz", hash = "sha256:68c7b68685e870e80e60fda8286fbd6269e9c74dc1df4316df6fe46eabc94c99"}, + {file = "jupytext-1.16.2-py3-none-any.whl", hash = "sha256:197a43fef31dca612b68b311e01b8abd54441c7e637810b16b6cb8f2ab66065e"}, + {file = "jupytext-1.16.2.tar.gz", hash = "sha256:8627dd9becbbebd79cc4a4ed4727d89d78e606b4b464eab72357b3b029023a14"}, ] [package.dependencies] @@ -1663,16 +1663,16 @@ mdit-py-plugins = "*" nbformat = "*" packaging = "*" pyyaml = "*" -toml = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} [package.extras] -dev = ["jupytext[test-cov,test-external]"] +dev = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"] docs = ["myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] test = ["pytest", "pytest-randomly", "pytest-xdist"] -test-cov = ["jupytext[test-integration]", "pytest-cov (>=2.6.1)"] -test-external = ["autopep8", "black", "flake8", "gitpython", "isort", "jupyter-fs (<0.4.0)", "jupytext[test-integration]", "pre-commit", "sphinx-gallery (<0.8)"] -test-functional = ["jupytext[test]"] -test-integration = ["ipykernel", "jupyter-server (!=2.11)", "jupytext[test-functional]", "nbconvert"] +test-cov = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist"] +test-external = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs (<0.4.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-randomly", "pytest-xdist", "sphinx-gallery (<0.8)"] +test-functional = ["pytest", "pytest-randomly", "pytest-xdist"] +test-integration = ["ipykernel", "jupyter-server (!=2.11)", "nbconvert", "pytest", "pytest-randomly", "pytest-xdist"] test-ui = ["calysto-bash"] [[package]] @@ -3913,17 +3913,6 @@ webencodings = ">=0.4" doc = ["sphinx", "sphinx_rtd_theme"] test = ["flake8", "isort", "pytest"] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" From aebbd01830b807def90a6a1d36636ccaa2ba94df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 01:15:08 +0000 Subject: [PATCH 135/136] :arrow_up: Bump mkdocstrings from 0.25.0 to 0.25.1 Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.25.0 to 0.25.1. - [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases) - [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md) - [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.25.0...0.25.1) --- updated-dependencies: - dependency-name: mkdocstrings dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2497d802..f209f594 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2239,13 +2239,13 @@ files = [ [[package]] name = "mkdocstrings" -version = "0.25.0" +version = "0.25.1" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocstrings-0.25.0-py3-none-any.whl", hash = "sha256:df1b63f26675fcde8c1b77e7ea996cd2f93220b148e06455428f676f5dc838f1"}, - {file = "mkdocstrings-0.25.0.tar.gz", hash = "sha256:066986b3fb5b9ef2d37c4417255a808f7e63b40ff8f67f6cab8054d903fbc91d"}, + {file = "mkdocstrings-0.25.1-py3-none-any.whl", hash = "sha256:da01fcc2670ad61888e8fe5b60afe9fee5781017d67431996832d63e887c2e51"}, + {file = "mkdocstrings-0.25.1.tar.gz", hash = "sha256:c3a2515f31577f311a9ee58d089e4c51fc6046dbd9e9b4c3de4c3194667fe9bf"}, ] [package.dependencies] From edd944fff887bfee404470a8efb408a84d10dfbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 01:18:43 +0000 Subject: [PATCH 136/136] :arrow_up: Bump ruff from 0.4.2 to 0.4.3 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.2 to 0.4.3. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.2...v0.4.3) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2497d802..d01eeae7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3660,28 +3660,28 @@ files = [ [[package]] name = "ruff" -version = "0.4.2" +version = "0.4.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8d14dc8953f8af7e003a485ef560bbefa5f8cc1ad994eebb5b12136049bbccc5"}, - {file = "ruff-0.4.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:24016ed18db3dc9786af103ff49c03bdf408ea253f3cb9e3638f39ac9cf2d483"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2e06459042ac841ed510196c350ba35a9b24a643e23db60d79b2db92af0c2b"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3afabaf7ba8e9c485a14ad8f4122feff6b2b93cc53cd4dad2fd24ae35112d5c5"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799eb468ea6bc54b95527143a4ceaf970d5aa3613050c6cff54c85fda3fde480"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ec4ba9436a51527fb6931a8839af4c36a5481f8c19e8f5e42c2f7ad3a49f5069"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a2243f8f434e487c2a010c7252150b1fdf019035130f41b77626f5655c9ca22"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8772130a063f3eebdf7095da00c0b9898bd1774c43b336272c3e98667d4fb8fa"}, - {file = "ruff-0.4.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab165ef5d72392b4ebb85a8b0fbd321f69832a632e07a74794c0e598e7a8376"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f32cadf44c2020e75e0c56c3408ed1d32c024766bd41aedef92aa3ca28eef68"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:22e306bf15e09af45ca812bc42fa59b628646fa7c26072555f278994890bc7ac"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:82986bb77ad83a1719c90b9528a9dd663c9206f7c0ab69282af8223566a0c34e"}, - {file = "ruff-0.4.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:652e4ba553e421a6dc2a6d4868bc3b3881311702633eb3672f9f244ded8908cd"}, - {file = "ruff-0.4.2-py3-none-win32.whl", hash = "sha256:7891ee376770ac094da3ad40c116258a381b86c7352552788377c6eb16d784fe"}, - {file = "ruff-0.4.2-py3-none-win_amd64.whl", hash = "sha256:5ec481661fb2fd88a5d6cf1f83403d388ec90f9daaa36e40e2c003de66751798"}, - {file = "ruff-0.4.2-py3-none-win_arm64.whl", hash = "sha256:cbd1e87c71bca14792948c4ccb51ee61c3296e164019d2d484f3eaa2d360dfaf"}, - {file = "ruff-0.4.2.tar.gz", hash = "sha256:33bcc160aee2520664bc0859cfeaebc84bb7323becff3f303b8f1f2d81cb4edc"}, + {file = "ruff-0.4.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b70800c290f14ae6fcbb41bbe201cf62dfca024d124a1f373e76371a007454ce"}, + {file = "ruff-0.4.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08a0d6a22918ab2552ace96adeaca308833873a4d7d1d587bb1d37bae8728eb3"}, + {file = "ruff-0.4.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba1f14df3c758dd7de5b55fbae7e1c8af238597961e5fb628f3de446c3c40c5"}, + {file = "ruff-0.4.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:819fb06d535cc76dfddbfe8d3068ff602ddeb40e3eacbc90e0d1272bb8d97113"}, + {file = "ruff-0.4.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfc9e955e6dc6359eb6f82ea150c4f4e82b660e5b58d9a20a0e42ec3bb6342b"}, + {file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:510a67d232d2ebe983fddea324dbf9d69b71c4d2dfeb8a862f4a127536dd4cfb"}, + {file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9ff11cd9a092ee7680a56d21f302bdda14327772cd870d806610a3503d001f"}, + {file = "ruff-0.4.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29efff25bf9ee685c2c8390563a5b5c006a3fee5230d28ea39f4f75f9d0b6f2f"}, + {file = "ruff-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b00e0bcccf0fc8d7186ed21e311dffd19761cb632241a6e4fe4477cc80ef6e"}, + {file = "ruff-0.4.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262f5635e2c74d80b7507fbc2fac28fe0d4fef26373bbc62039526f7722bca1b"}, + {file = "ruff-0.4.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7363691198719c26459e08cc17c6a3dac6f592e9ea3d2fa772f4e561b5fe82a3"}, + {file = "ruff-0.4.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eeb039f8428fcb6725bb63cbae92ad67b0559e68b5d80f840f11914afd8ddf7f"}, + {file = "ruff-0.4.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:927b11c1e4d0727ce1a729eace61cee88a334623ec424c0b1c8fe3e5f9d3c865"}, + {file = "ruff-0.4.3-py3-none-win32.whl", hash = "sha256:25cacda2155778beb0d064e0ec5a3944dcca9c12715f7c4634fd9d93ac33fd30"}, + {file = "ruff-0.4.3-py3-none-win_amd64.whl", hash = "sha256:7a1c3a450bc6539ef00da6c819fb1b76b6b065dec585f91456e7c0d6a0bbc725"}, + {file = "ruff-0.4.3-py3-none-win_arm64.whl", hash = "sha256:71ca5f8ccf1121b95a59649482470c5601c60a416bf189d553955b0338e34614"}, + {file = "ruff-0.4.3.tar.gz", hash = "sha256:ff0a3ef2e3c4b6d133fbedcf9586abfbe38d076041f2dc18ffb2c7e0485d5a07"}, ] [[package]]