From c3641de724cc911ddd9d55fc9dd9937ee7be1161 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Wed, 14 Aug 2024 12:14:26 +0300 Subject: [PATCH 01/11] np.array -> np.ndarray --- supervision/keypoint/annotators.py | 4 ++-- test/dataset/formats/test_yolo.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py index 38dea23c..e50043e1 100644 --- a/supervision/keypoint/annotators.py +++ b/supervision/keypoint/annotators.py @@ -400,7 +400,7 @@ class VertexLabelAnnotator: @staticmethod def preprocess_and_validate_labels( labels: Optional[List[str]], points_count: int, skeletons_count: int - ) -> np.array: + ) -> np.ndarray: if labels and len(labels) != points_count: raise ValueError( f"Number of labels ({len(labels)}) must match number of key points " @@ -416,7 +416,7 @@ class VertexLabelAnnotator: colors: Optional[Union[Color, List[Color]]], points_count: int, skeletons_count: int, - ) -> np.array: + ) -> np.ndarray: if isinstance(colors, list) and len(colors) != points_count: raise ValueError( f"Number of colors ({len(colors)}) must match number of key points " diff --git a/test/dataset/formats/test_yolo.py b/test/dataset/formats/test_yolo.py index f84e275b..70f0e362 100644 --- a/test/dataset/formats/test_yolo.py +++ b/test/dataset/formats/test_yolo.py @@ -13,7 +13,7 @@ from supervision.dataset.formats.yolo import ( from supervision.detection.core import Detections -def _mock_simple_mask(resolution_wh: Tuple[int, int], box: List[int]) -> np.array: +def _mock_simple_mask(resolution_wh: Tuple[int, int], box: List[int]) -> np.ndarray: x_min, y_min, x_max, y_max = box mask = np.full(resolution_wh, False, dtype=bool) mask[y_min:y_max, x_min:x_max] = True From 5b3ce80a340c0589880fbc59e31a01727ee57671 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Wed, 14 Aug 2024 12:16:15 +0300 Subject: [PATCH 02/11] Change classproperty so sv.Color.RED retain type signature * Previously sv.Color.RED would be seen as having type 'classproperty' * Setting the type info during construvtion passes it to the return type * Previously unhandled case where the class was missing is now explicitly checked for * Broke down the inheritance from 'property', as it was marking the result as 'classproperty' instead of e.g. Color, but only until type was provided (but without mypy confusion). --- supervision/utils/internal.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/supervision/utils/internal.py b/supervision/utils/internal.py index 4a09e0f7..cc8d4c79 100644 --- a/supervision/utils/internal.py +++ b/supervision/utils/internal.py @@ -2,7 +2,7 @@ import functools import inspect import os import warnings -from typing import Any, Callable, Set +from typing import Any, Callable, Generic, Optional, Set, TypeVar class SupervisionWarnings(Warning): @@ -121,8 +121,9 @@ def deprecated(reason: str): return decorator +T = TypeVar('T') -class classproperty(property): +class classproperty(Generic[T]): """ A decorator that combines @classmethod and @property. It allows a method to be accessed as a property of the class, @@ -133,18 +134,27 @@ class classproperty(property): def my_method(cls): ... """ + def __init__(self, fget: Callable[..., T]): + """ + Args: + The function that is called when the property is accessed. + """ + self.fget = fget - def __get__(self, owner_self: object, owner_cls: type) -> object: + def __get__(self, owner_self: Any, owner_cls: Optional[type] = None) -> T: """ Override the __get__ method to return the result of the function call. Args: - owner_self: The instance through which the attribute was accessed, or None. - owner_cls: The class through which the attribute was accessed. + owner_self: The instance through which the attribute was accessed, or None. + Irrelevant for class properties. + owner_cls: The class through which the attribute was accessed. Returns: - The result of calling the function stored in 'fget' with 'owner_cls'. + The result of calling the function stored in 'fget' with 'owner_cls'. """ + if self.fget is None: + raise AttributeError("unreadable attribute") return self.fget(owner_cls) From 2012ec7bcb3a78f12471f442a10c749097a0a157 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Wed, 14 Aug 2024 12:46:16 +0300 Subject: [PATCH 03/11] Unify how optional values are marked in docstings * No longer means 'has default value'. Removed where it meant that. * `Optional[datatype]` is now used instead of `datatype, optional` * Fixed a handful of incorrect type annotations --- .../count_people_in_zone/inference_example.py | 2 +- .../ultralytics_example.py | 2 +- examples/time_in_zone/utils/timers.py | 2 +- supervision/annotators/core.py | 2 +- supervision/dataset/core.py | 20 +++++++------- supervision/dataset/formats/pascal_voc.py | 2 +- supervision/dataset/formats/yolo.py | 4 +-- supervision/detection/core.py | 8 +++--- supervision/detection/overlap_filter.py | 10 +++---- supervision/detection/tools/polygon_zone.py | 2 +- supervision/detection/utils.py | 2 +- supervision/draw/utils.py | 14 +++++----- supervision/keypoint/annotators.py | 27 ++++++++++--------- supervision/metrics/detection.py | 2 +- supervision/tracker/byte_tracker/core.py | 10 +++---- supervision/utils/image.py | 8 +++--- supervision/utils/internal.py | 9 ++++--- supervision/utils/video.py | 2 +- 18 files changed, 67 insertions(+), 61 deletions(-) diff --git a/examples/count_people_in_zone/inference_example.py b/examples/count_people_in_zone/inference_example.py index 96e3e83c..8f42ff43 100644 --- a/examples/count_people_in_zone/inference_example.py +++ b/examples/count_people_in_zone/inference_example.py @@ -75,7 +75,7 @@ def detect( frame (np.ndarray): The frame to process, expected to be a NumPy array. model (RoboflowInferenceModel): The Inference model used for processing the frame. - confidence_threshold (float, optional): The confidence threshold for filtering + confidence_threshold (float): The confidence threshold for filtering detections. Default is 0.5. Returns: diff --git a/examples/count_people_in_zone/ultralytics_example.py b/examples/count_people_in_zone/ultralytics_example.py index 87badadb..2fd07782 100644 --- a/examples/count_people_in_zone/ultralytics_example.py +++ b/examples/count_people_in_zone/ultralytics_example.py @@ -72,7 +72,7 @@ def detect( Args: frame (np.ndarray): The frame to process, expected to be a NumPy array. model (YOLO): The YOLO model used for processing the frame. - confidence_threshold (float, optional): The confidence threshold for filtering + confidence_threshold (float): The confidence threshold for filtering detections. Default is 0.5. Returns: diff --git a/examples/time_in_zone/utils/timers.py b/examples/time_in_zone/utils/timers.py index cb5b471f..7afa9af3 100644 --- a/examples/time_in_zone/utils/timers.py +++ b/examples/time_in_zone/utils/timers.py @@ -22,7 +22,7 @@ class FPSBasedTimer: """Initializes the FPSBasedTimer with the specified frames per second rate. Args: - fps (int, optional): The frame rate of the video stream. Defaults to 30. + fps (int): The frame rate of the video stream. Defaults to 30. """ self.fps = fps self.frame_id = 0 diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index ca2a71f8..b9a307da 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -1975,7 +1975,7 @@ class PercentageBarAnnotator(BaseAnnotator): border_color: Color = Color.BLACK, position: Position = Position.TOP_CENTER, color_lookup: ColorLookup = ColorLookup.CLASS, - border_thickness: int = None, + border_thickness: Optional[int] = None, ): """ Args: diff --git a/supervision/dataset/core.py b/supervision/dataset/core.py index 7d320bbc..37ba530b 100644 --- a/supervision/dataset/core.py +++ b/supervision/dataset/core.py @@ -181,11 +181,11 @@ class DetectionDataset(BaseDataset): using the provided split_ratio. Args: - split_ratio (float, optional): The ratio of the training + split_ratio (float): The ratio of the training set to the entire dataset. - random_state (int, optional): The seed for the random number generator. + random_state (Optional[int]): The seed for the random number generator. This is used for reproducibility. - shuffle (bool, optional): Whether to shuffle the data before splitting. + shuffle (bool): Whether to shuffle the data before splitting. Returns: Tuple[DetectionDataset, DetectionDataset]: A tuple containing @@ -396,7 +396,7 @@ class DetectionDataset(BaseDataset): images_directory_path (str): Path to the directory containing the images. annotations_directory_path (str): Path to the directory containing the PASCAL VOC XML annotations. - force_masks (bool, optional): If True, forces masks to + force_masks (bool): If True, forces masks to be loaded for all annotations, regardless of whether they are present. Returns: @@ -455,10 +455,10 @@ class DetectionDataset(BaseDataset): containing the YOLO annotation files. data_yaml_path (str): The path to the data YAML file containing class information. - force_masks (bool, optional): If True, forces + force_masks (bool): If True, forces masks to be loaded for all annotations, regardless of whether they are present. - is_obb (bool, optional): If True, loads the annotations in OBB format. + is_obb (bool): If True, loads the annotations in OBB format. OBB annotations are defined as `[class_id, x, y, x, y, x, y, x, y]`, where pairs of [x, y] are box corners. @@ -565,7 +565,7 @@ class DetectionDataset(BaseDataset): images_directory_path (str): The path to the directory containing the images. annotations_path (str): The path to the json annotation files. - force_masks (bool, optional): If True, + force_masks (bool): If True, forces masks to be loaded for all annotations, regardless of whether they are present. @@ -784,11 +784,11 @@ class ClassificationDataset(BaseDataset): using the provided split_ratio. Args: - split_ratio (float, optional): The ratio of the training + split_ratio (float): The ratio of the training set to the entire dataset. - random_state (int, optional): The seed for the + random_state (Optional[int]): The seed for the random number generator. This is used for reproducibility. - shuffle (bool, optional): Whether to shuffle the data before splitting. + shuffle (bool): Whether to shuffle the data before splitting. Returns: Tuple[ClassificationDataset, ClassificationDataset]: A tuple containing diff --git a/supervision/dataset/formats/pascal_voc.py b/supervision/dataset/formats/pascal_voc.py index b9724564..49c59a39 100644 --- a/supervision/dataset/formats/pascal_voc.py +++ b/supervision/dataset/formats/pascal_voc.py @@ -147,7 +147,7 @@ def load_pascal_voc_annotations( images_directory_path (str): The path to the directory containing the images. annotations_directory_path (str): The path to the directory containing the PASCAL VOC annotation files. - force_masks (bool, optional): If True, forces masks to be loaded for all + force_masks (bool): If True, forces masks to be loaded for all annotations, regardless of whether they are present. Returns: diff --git a/supervision/dataset/formats/yolo.py b/supervision/dataset/formats/yolo.py index 0ecbac4b..1f9033fc 100644 --- a/supervision/dataset/formats/yolo.py +++ b/supervision/dataset/formats/yolo.py @@ -138,9 +138,9 @@ def load_yolo_annotations( containing the YOLO annotation files. data_yaml_path (str): The path to the data YAML file containing class information. - force_masks (bool, optional): If True, forces masks to be loaded + force_masks (bool): If True, forces masks to be loaded for all annotations, regardless of whether they are present. - is_obb (bool, optional): If True, loads the annotations in OBB format. + is_obb (bool): If True, loads the annotations in OBB format. OBB annotations are defined as `[class_id, x, y, x, y, x, y, x, y]`, where pairs of [x, y] are box corners. diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 73daadda..87142ef6 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1149,10 +1149,10 @@ class Detections: from a segmentation model, the IoU mask is applied. Otherwise, box IoU is used. Args: - threshold (float, optional): The intersection-over-union threshold + threshold (float): The intersection-over-union threshold to use for non-maximum suppression. I'm the lower the value the more restrictive the NMS becomes. Defaults to 0.5. - class_agnostic (bool, optional): Whether to perform class-agnostic + class_agnostic (bool): Whether to perform class-agnostic non-maximum suppression. If True, the class_id of each detection will be ignored. Defaults to False. @@ -1204,9 +1204,9 @@ class Detections: Perform non-maximum merging on the current set of object detections. Args: - threshold (float, optional): The intersection-over-union threshold + threshold (float): The intersection-over-union threshold to use for non-maximum merging. Defaults to 0.5. - class_agnostic (bool, optional): Whether to perform class-agnostic + class_agnostic (bool): Whether to perform class-agnostic non-maximum merging. If True, the class_id of each detection will be ignored. Defaults to False. diff --git a/supervision/detection/overlap_filter.py b/supervision/detection/overlap_filter.py index f51f1dce..461e413c 100644 --- a/supervision/detection/overlap_filter.py +++ b/supervision/detection/overlap_filter.py @@ -55,9 +55,9 @@ def mask_non_max_suppression( masks (np.ndarray): A 3D array of binary masks corresponding to the predictions. Shape: `(N, H, W)`, where N is the number of predictions, and H, W are the dimensions of each mask. - iou_threshold (float, optional): The intersection-over-union threshold + iou_threshold (float): The intersection-over-union threshold to use for non-maximum suppression. - mask_dimension (int, optional): The dimension to which the masks should be + mask_dimension (int): The dimension to which the masks should be resized before computing IOU values. Defaults to 640. Returns: @@ -103,7 +103,7 @@ def box_non_max_suppression( predictions (np.ndarray): An array of object detection predictions in the format of `(x_min, y_min, x_max, y_max, score)` or `(x_min, y_min, x_max, y_max, score, class)`. - iou_threshold (float, optional): The intersection-over-union threshold + iou_threshold (float): The intersection-over-union threshold to use for non-maximum suppression. Returns: @@ -158,7 +158,7 @@ def group_overlapping_boxes( predictions (npt.NDArray[np.float64]): An array of shape `(n, 5)` containing the bounding boxes coordinates in format `[x1, y1, x2, y2]` and the confidence scores. - iou_threshold (float, optional): The intersection-over-union threshold + iou_threshold (float): The intersection-over-union threshold to use for non-maximum suppression. Defaults to 0.5. Returns: @@ -202,7 +202,7 @@ def box_non_max_merge( containing the bounding boxes coordinates in format `[x1, y1, x2, y2]`, the confidence scores and class_ids. Omit class_id column to allow detections of different classes to be merged. - iou_threshold (float, optional): The intersection-over-union threshold + iou_threshold (float): The intersection-over-union threshold to use for non-maximum suppression. Defaults to 0.5. Returns: diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index f1c48f94..c1e030f9 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -147,7 +147,7 @@ class PolygonZoneAnnotator: Parameters: scene (np.ndarray): The image on which the polygon zone will be annotated - label (Optional[str]): An optional label for the count of detected objects + label (Optional[str]): A label for the count of detected objects within the polygon zone (default: None) Returns: diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index a02a6a1f..1a336ca7 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -106,7 +106,7 @@ def mask_iou_batch( Args: masks_true (np.ndarray): 3D `np.ndarray` representing ground-truth masks. masks_detection (np.ndarray): 3D `np.ndarray` representing detection masks. - memory_limit (int, optional): memory limit in MB, default is 1024 * 5 MB (5GB). + memory_limit (int): memory limit in MB, default is 1024 * 5 MB (5GB). Returns: np.ndarray: Pairwise IoU of masks from `masks_true` and `masks_detection`. diff --git a/supervision/draw/utils.py b/supervision/draw/utils.py index 884f6d18..36651811 100644 --- a/supervision/draw/utils.py +++ b/supervision/draw/utils.py @@ -142,7 +142,7 @@ def draw_polygon( scene (np.ndarray): The scene to draw the polygon on. polygon (np.ndarray): The polygon to be drawn, given as a list of vertices. color (Color): The color of the polygon. - thickness (int, optional): The thickness of the polygon lines, by default 2. + thickness (int): The thickness of the polygon lines, by default 2. Returns: np.ndarray: The scene with the polygon drawn on it. @@ -172,14 +172,14 @@ def draw_text( text (str): The text to be drawn. text_anchor (Point): The anchor point for the text, represented as a Point object with x and y attributes. - text_color (Color, optional): The color of the text. Defaults to black. - text_scale (float, optional): The scale of the text. Defaults to 0.5. - text_thickness (int, optional): The thickness of the text. Defaults to 1. - text_padding (int, optional): The amount of padding to add around the text + text_color (Color): The color of the text. Defaults to black. + text_scale (float): The scale of the text. Defaults to 0.5. + text_thickness (int): The thickness of the text. Defaults to 1. + text_padding (int): The amount of padding to add around the text when drawing a rectangle in the background. Defaults to 10. - text_font (int, optional): The font to use for the text. + text_font (int): The font to use for the text. Defaults to cv2.FONT_HERSHEY_SIMPLEX. - background_color (Color, optional): The color of the background rectangle, + background_color (Optional[Color]): The color of the background rectangle, if one is to be drawn. Defaults to None. Returns: diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py index e50043e1..6258ac12 100644 --- a/supervision/keypoint/annotators.py +++ b/supervision/keypoint/annotators.py @@ -34,8 +34,8 @@ class VertexAnnotator(BaseKeyPointAnnotator): ) -> 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 + color (Color): The color to use for annotating key points. + radius (int): The radius of the circles used to represent the key points. """ self.color = color @@ -108,8 +108,8 @@ class EdgeAnnotator(BaseKeyPointAnnotator): ) -> None: """ Args: - color (Color, optional): The color to use for the edges. - thickness (int, optional): The thickness of the edges. + color (Color): The color to use for the edges. + thickness (int): The thickness of the edges. edges (Optional[List[Tuple[int, int]]]): The edges to draw. If set to `None`, will attempt to select automatically. """ @@ -202,16 +202,16 @@ class VertexLabelAnnotator: ): """ Args: - color (Union[Color, List[Color]], optional): The color to use for each + color (Union[Color, List[Color]]): The color to use for each keypoint label. If a list is provided, the colors will be used in order for each keypoint. - text_color (Union[Color, List[Color]], optional): The color to use + text_color (Union[Color, List[Color]]): The color to use for the labels. If a list is provided, the colors will be used in order for each keypoint. - text_scale (float, optional): The scale of the text. - text_thickness (int, optional): The thickness of the text. - text_padding (int, optional): The padding around the text. - border_radius (int, optional): The radius of the rounded corners of the + text_scale (float): The scale of the text. + text_thickness (int): The thickness of the text. + text_padding (int): The padding around the text. + border_radius (int): The radius of the rounded corners of the boxes. Set to a high value to produce circles. """ self.border_radius: int = border_radius @@ -222,7 +222,10 @@ class VertexLabelAnnotator: self.text_padding: int = text_padding def annotate( - self, scene: ImageType, key_points: KeyPoints, labels: List[str] = None + self, + scene: ImageType, + key_points: KeyPoints, + labels: Optional[List[str]] = None, ) -> ImageType: """ A class that draws labels of skeleton vertices on images. It uses specified key @@ -234,7 +237,7 @@ class VertexLabelAnnotator: `PIL.Image.Image`. key_points (KeyPoints): A collection of key points where each key point consists of x and y coordinates. - labels (List[str], optional): A list of labels to be displayed on the + labels (Optional[List[str]]): A list of labels to be displayed on the annotated image. If not provided, keypoint indices will be used. Returns: diff --git a/supervision/metrics/detection.py b/supervision/metrics/detection.py index bbbfa711..5b9e6ca4 100644 --- a/supervision/metrics/detection.py +++ b/supervision/metrics/detection.py @@ -806,7 +806,7 @@ class MeanAveragePrecision: prediction_confidence (np.ndarray): Objectness value from 0-1. prediction_class_ids (np.ndarray): Predicted object classes. true_class_ids (np.ndarray): True object classes. - eps (float, optional): Small value to prevent division by zero. + eps (float): Small value to prevent division by zero. Returns: np.ndarray: Average precision for different IoU levels. diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index bd9bae50..4be3b184 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -197,19 +197,19 @@ class ByteTrack: Parameters: - track_activation_threshold (float, optional): Detection confidence threshold + track_activation_threshold (float): Detection confidence threshold for track activation. Increasing track_activation_threshold improves accuracy and stability but might miss true detections. Decreasing it increases completeness but risks introducing noise and instability. - lost_track_buffer (int, optional): Number of frames to buffer when a track is lost. + lost_track_buffer (int): Number of frames to buffer when a track is lost. Increasing lost_track_buffer enhances occlusion handling, significantly reducing the likelihood of track fragmentation or disappearance caused by brief detection gaps. - minimum_matching_threshold (float, optional): Threshold for matching tracks with detections. + minimum_matching_threshold (float): Threshold for matching tracks with detections. Increasing minimum_matching_threshold improves accuracy but risks fragmentation. Decreasing it improves completeness but risks false positives and drift. - frame_rate (int, optional): The frame rate of the video. - minimum_consecutive_frames (int, optional): Number of consecutive frames that an object must + frame_rate (int): The frame rate of the video. + minimum_consecutive_frames (int): 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. diff --git a/supervision/utils/image.py b/supervision/utils/image.py index d6972189..edd34c8e 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -158,7 +158,7 @@ def resize_image( 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 + keep_aspect_ratio (bool): Flag to maintain the image's original aspect ratio. Defaults to `False`. Returns: @@ -360,9 +360,9 @@ class ImageSink: Args: target_dir_path (str): The target directory where images will be saved. - overwrite (bool, optional): Whether to overwrite the existing directory. + overwrite (bool): Whether to overwrite the existing directory. Defaults to False. - image_name_pattern (str, optional): The image file name pattern. + image_name_pattern (str): The image file name pattern. Defaults to "image_{:05d}.png". Examples: @@ -399,7 +399,7 @@ class ImageSink: Args: image (np.ndarray): The image to be saved. The image must be in BGR color format. - image_name (str, optional): The name to use for the saved image. + image_name (Optional[str]): The name to use for the saved image. If not provided, a name will be generated using the `image_name_pattern`. """ diff --git a/supervision/utils/internal.py b/supervision/utils/internal.py index cc8d4c79..10f4a450 100644 --- a/supervision/utils/internal.py +++ b/supervision/utils/internal.py @@ -56,9 +56,9 @@ def deprecated_parameter( Parameters: old_parameter (str): The name of the deprecated parameter. new_parameter (str): The name of the parameter that should be used instead. - map_function (Callable, optional): A function used to map the value of the old + map_function (Callable): A function used to map the value of the old parameter to the new parameter. Defaults to the identity function. - warning_message (str, optional): The warning message to be displayed when the + warning_message (str): The warning message to be displayed when the deprecated parameter is used. Defaults to a generic warning message with placeholders for the old parameter, new parameter, and function name. **message_kwargs: Additional keyword arguments that can be used to customize @@ -121,7 +121,9 @@ def deprecated(reason: str): return decorator -T = TypeVar('T') + +T = TypeVar("T") + class classproperty(Generic[T]): """ @@ -134,6 +136,7 @@ class classproperty(Generic[T]): def my_method(cls): ... """ + def __init__(self, fget: Callable[..., T]): """ Args: diff --git a/supervision/utils/video.py b/supervision/utils/video.py index 7e9836de..26405ec4 100644 --- a/supervision/utils/video.py +++ b/supervision/utils/video.py @@ -19,7 +19,7 @@ class VideoInfo: width (int): width of the video in pixels height (int): height of the video in pixels fps (int): frames per second of the video - total_frames (int, optional): total number of frames in the video, + total_frames (Optional[int]): total number of frames in the video, default is None Examples: From 29de4d245ed13b6295b08de4c6f3b577d3c31f9b Mon Sep 17 00:00:00 2001 From: LinasKo Date: Wed, 14 Aug 2024 13:05:01 +0300 Subject: [PATCH 04/11] Add annotator assertions to clarify ImageType * Because we're generating docs from type annotations of function signatures, I can't find any other way to tell mypy that scene is np.ndarray --- supervision/annotators/core.py | 27 +++++++++++++++++++++++---- supervision/keypoint/annotators.py | 3 +++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index b9a307da..a9cecab8 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -3,7 +3,7 @@ from typing import List, Optional, Tuple, Union import cv2 import numpy as np -from PIL import ImageDraw, ImageFont +from PIL import Image, ImageDraw, ImageFont from supervision.annotators.base import BaseAnnotator, ImageType from supervision.annotators.utils import ( @@ -87,6 +87,7 @@ class BoxAnnotator(BaseAnnotator): ![bounding-box-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/bounding-box-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -172,6 +173,7 @@ class BoundingBoxAnnotator(BaseAnnotator): ![bounding-box-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/bounding-box-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -256,7 +258,7 @@ class OrientedBoxAnnotator(BaseAnnotator): ) ``` """ # noqa E501 // docs - + assert isinstance(scene, np.ndarray), "MyPy type hint" if detections.data is None or ORIENTED_BOX_COORDINATES not in detections.data: return scene @@ -342,6 +344,7 @@ class MaskAnnotator(BaseAnnotator): ![mask-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/mask-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" if detections.mask is None: return scene @@ -431,6 +434,7 @@ class PolygonAnnotator(BaseAnnotator): ![polygon-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/polygon-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" if detections.mask is None: return scene @@ -517,6 +521,7 @@ class ColorAnnotator(BaseAnnotator): ![box-mask-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/box-mask-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" scene_with_boxes = scene.copy() for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) @@ -612,6 +617,7 @@ class HaloAnnotator(BaseAnnotator): ![halo-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/halo-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" if detections.mask is None: return scene colored_mask = np.zeros_like(scene, dtype=np.uint8) @@ -711,6 +717,7 @@ class EllipseAnnotator(BaseAnnotator): ![ellipse-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/ellipse-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -802,6 +809,7 @@ class BoxCornerAnnotator(BaseAnnotator): ![box-corner-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/box-corner-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -891,6 +899,7 @@ class CircleAnnotator(BaseAnnotator): ![circle-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/circle-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) center = ((x1 + x2) // 2, (y1 + y2) // 2) @@ -983,6 +992,7 @@ class DotAnnotator(BaseAnnotator): ![dot-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/dot-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" xy = detections.get_anchors_coordinates(anchor=self.position) for detection_idx in range(len(detections)): color = resolve_color( @@ -1092,6 +1102,7 @@ class LabelAnnotator(BaseAnnotator): ![label-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/label-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" font = cv2.FONT_HERSHEY_SIMPLEX anchors_coordinates = detections.get_anchors_coordinates( anchor=self.text_anchor @@ -1308,6 +1319,7 @@ class RichLabelAnnotator(BaseAnnotator): ``` """ + assert isinstance(scene, Image.Image), "MyPy type hint" draw = ImageDraw.Draw(scene) anchors_coordinates = detections.get_anchors_coordinates( anchor=self.text_anchor @@ -1440,6 +1452,7 @@ class BlurAnnotator(BaseAnnotator): ![blur-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/blur-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" image_height, image_width = scene.shape[:2] clipped_xyxy = clip_boxes( xyxy=detections.xyxy, resolution_wh=(image_width, image_height) @@ -1538,6 +1551,7 @@ class TraceAnnotator(BaseAnnotator): ![trace-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/trace-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" self.trace.put(detections) for detection_idx in range(len(detections)): @@ -1636,7 +1650,7 @@ class HeatMapAnnotator(BaseAnnotator): ![heatmap-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/heat-map-annotator-example-purple.png) """ - + assert isinstance(scene, np.ndarray), "MyPy type hint" if self.heat_mask is None: self.heat_mask = np.zeros(scene.shape[:2]) mask = np.zeros(scene.shape[:2]) @@ -1709,6 +1723,7 @@ class PixelateAnnotator(BaseAnnotator): ![pixelate-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/pixelate-annotator-example-10.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" image_height, image_width = scene.shape[:2] clipped_xyxy = clip_boxes( xyxy=detections.xyxy, resolution_wh=(image_width, image_height) @@ -1802,6 +1817,7 @@ class TriangleAnnotator(BaseAnnotator): ![triangle-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/triangle-annotator-example.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" xy = detections.get_anchors_coordinates(anchor=self.position) for detection_idx in range(len(detections)): color = resolve_color( @@ -1902,7 +1918,7 @@ class RoundBoxAnnotator(BaseAnnotator): ![round-box-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/round-box-annotator-example-purple.png) """ - + assert isinstance(scene, np.ndarray), "MyPy type hint" for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -2046,6 +2062,7 @@ class PercentageBarAnnotator(BaseAnnotator): ![percentage-bar-example](https://media.roboflow.com/ supervision-annotator-examples/percentage-bar-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" self.validate_custom_values( custom_values=custom_values, detections_count=len(detections) ) @@ -2211,6 +2228,7 @@ class CropAnnotator(BaseAnnotator): ) ``` """ + assert isinstance(scene, np.ndarray), "MyPy type hint" crops = [ crop_image(image=scene, xyxy=xyxy) for xyxy in detections.xyxy.astype(int) ] @@ -2349,6 +2367,7 @@ class BackgroundOverlayAnnotator(BaseAnnotator): ![background-overlay-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/background-color-annotator-example-purple.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" colored_mask = np.full_like(scene, self.color.as_bgr(), dtype=np.uint8) cv2.addWeighted( diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py index 6258ac12..be52133a 100644 --- a/supervision/keypoint/annotators.py +++ b/supervision/keypoint/annotators.py @@ -78,6 +78,7 @@ class VertexAnnotator(BaseKeyPointAnnotator): ![vertex-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/vertex-annotator-example.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" if len(key_points) == 0: return scene @@ -155,6 +156,7 @@ class EdgeAnnotator(BaseKeyPointAnnotator): ![edge-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/edge-annotator-example.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" if len(key_points) == 0: return scene @@ -308,6 +310,7 @@ class VertexLabelAnnotator: ![vertex-label-annotator-custom-example](https://media.roboflow.com/ supervision-annotator-examples/vertex-label-annotator-custom-example.png) """ + assert isinstance(scene, np.ndarray), "MyPy type hint" font = cv2.FONT_HERSHEY_SIMPLEX skeletons_count, points_count, _ = key_points.xy.shape From 0477499b773975d1addc2c34029375e29e33960b Mon Sep 17 00:00:00 2001 From: LinasKo Date: Wed, 14 Aug 2024 14:07:10 +0300 Subject: [PATCH 05/11] Add error handling, clean up mypy confusion --- supervision/annotators/core.py | 61 +++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index a9cecab8..fc6e198c 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -3,6 +3,7 @@ from typing import List, Optional, Tuple, Union import cv2 import numpy as np +import numpy.typing as npt from PIL import Image, ImageDraw, ImageFont from supervision.annotators.base import BaseAnnotator, ImageType @@ -263,7 +264,7 @@ class OrientedBoxAnnotator(BaseAnnotator): return scene for detection_idx in range(len(detections)): - bbox = np.intp(detections.data.get(ORIENTED_BOX_COORDINATES)[detection_idx]) + bbox = detections.xyxy[detection_idx].astype(int) color = resolve_color( color=self.color, detections=detections, @@ -1139,8 +1140,8 @@ class LabelAnnotator(BaseAnnotator): if labels is not None: text = labels[detection_idx] - elif detections[CLASS_NAME_DATA_FIELD] is not None: - text = detections[CLASS_NAME_DATA_FIELD][detection_idx] + elif CLASS_NAME_DATA_FIELD in detections.data: + text = detections.data[CLASS_NAME_DATA_FIELD][detection_idx] elif detections.class_id is not None: text = str(detections.class_id[detection_idx]) else: @@ -1355,8 +1356,8 @@ class RichLabelAnnotator(BaseAnnotator): if labels is not None: text = labels[detection_idx] - elif detections[CLASS_NAME_DATA_FIELD] is not None: - text = detections[CLASS_NAME_DATA_FIELD][detection_idx] + elif CLASS_NAME_DATA_FIELD in detections.data: + text = detections.data[CLASS_NAME_DATA_FIELD][detection_idx] elif detections.class_id is not None: text = str(detections.class_id[detection_idx]) else: @@ -1552,8 +1553,13 @@ class TraceAnnotator(BaseAnnotator): supervision-annotator-examples/trace-annotator-example-purple.png) """ assert isinstance(scene, np.ndarray), "MyPy type hint" - self.trace.put(detections) + if detections.tracker_id is None: + raise ValueError( + "The `tracker_id` field is missing in the provided detections." + " See more: https://supervision.roboflow.com/latest/how_to/track_objects" + ) + self.trace.put(detections) for detection_idx in range(len(detections)): tracker_id = int(detections.tracker_id[detection_idx]) color = resolve_color( @@ -1606,9 +1612,9 @@ class HeatMapAnnotator(BaseAnnotator): self.opacity = opacity self.radius = radius self.kernel_size = kernel_size - self.heat_mask = None self.top_hue = top_hue self.low_hue = low_hue + self.heat_mask: Optional[npt.NDArray[np.float32]] = None @ensure_cv2_image_for_annotation def annotate(self, scene: ImageType, detections: Detections) -> ImageType: @@ -1652,10 +1658,18 @@ class HeatMapAnnotator(BaseAnnotator): """ assert isinstance(scene, np.ndarray), "MyPy type hint" if self.heat_mask is None: - self.heat_mask = np.zeros(scene.shape[:2]) + self.heat_mask = np.zeros(scene.shape[:2], dtype=np.float32) + mask = np.zeros(scene.shape[:2]) for xy in detections.get_anchors_coordinates(self.position): - cv2.circle(mask, (int(xy[0]), int(xy[1])), self.radius, 1, -1) + x, y = int(xy[0]), int(xy[1]) + cv2.circle( + img=mask, + center=(x, y), + radius=self.radius, + color=(1,), + thickness=-1, # fill + ) self.heat_mask = mask + self.heat_mask temp = self.heat_mask.copy() temp = self.low_hue - temp / temp.max() * (self.low_hue - self.top_hue) @@ -2063,9 +2077,8 @@ class PercentageBarAnnotator(BaseAnnotator): supervision-annotator-examples/percentage-bar-annotator-example-purple.png) """ assert isinstance(scene, np.ndarray), "MyPy type hint" - self.validate_custom_values( - custom_values=custom_values, detections_count=len(detections) - ) + self.validate_custom_values(custom_values=custom_values, detections=detections) + anchors = detections.get_anchors_coordinates(anchor=self.position) for detection_idx in range(len(detections)): anchor = anchors[detection_idx] @@ -2076,11 +2089,11 @@ class PercentageBarAnnotator(BaseAnnotator): ) border_width = border_coordinates[1][0] - border_coordinates[0][0] - value = ( - custom_values[detection_idx] - if custom_values is not None - else detections.confidence[detection_idx] - ) + if custom_values is not None: + value = custom_values[detection_idx] + else: + assert detections.confidence is not None # MyPy type hint + value = detections.confidence[detection_idx] color = resolve_color( color=self.color, @@ -2140,15 +2153,23 @@ class PercentageBarAnnotator(BaseAnnotator): @staticmethod def validate_custom_values( - custom_values: Optional[Union[np.ndarray, List[float]]], detections_count: int + custom_values: Optional[Union[np.ndarray, List[float]]], detections: Detections ) -> None: - if custom_values is not None: + if custom_values is None: + if detections.confidence is None: + raise ValueError( + "The provided detections do not contain confidence values. " + "Please provide `custom_values` or ensure that the detections " + "contain confidence values (e.g. by using a different model)." + ) + + else: if not isinstance(custom_values, (np.ndarray, list)): raise TypeError( "custom_values must be either a numpy array or a list of floats." ) - if len(custom_values) != detections_count: + if len(custom_values) != len(detections): raise ValueError( "The length of custom_values must match the number of detections." ) From d4f95eca45c29e65077e8462e4474408267f3350 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Wed, 14 Aug 2024 14:29:13 +0300 Subject: [PATCH 06/11] Minor: Insert missing sv. call in examples --- docs/detection/annotators.md | 4 ++-- supervision/annotators/core.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/detection/annotators.md b/docs/detection/annotators.md index eec45b00..1fe189a3 100644 --- a/docs/detection/annotators.md +++ b/docs/detection/annotators.md @@ -384,7 +384,7 @@ status: new trace_annotator = sv.TraceAnnotator() video_info = sv.VideoInfo.from_video_path(video_path='...') - frames_generator = get_video_frames_generator(source_path='...') + frames_generator = sv.get_video_frames_generator(source_path='...') tracker = sv.ByteTrack() with sv.VideoSink(target_path='...', video_info=video_info) as sink: @@ -415,7 +415,7 @@ status: new heat_map_annotator = sv.HeatMapAnnotator() video_info = sv.VideoInfo.from_video_path(video_path='...') - frames_generator = get_video_frames_generator(source_path='...') + frames_generator = sv.get_video_frames_generator(source_path='...') with sv.VideoSink(target_path='...', video_info=video_info) as sink: for frame in frames_generator: diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index fc6e198c..565d6355 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -1641,7 +1641,7 @@ class HeatMapAnnotator(BaseAnnotator): heat_map_annotator = sv.HeatMapAnnotator() video_info = sv.VideoInfo.from_video_path(video_path='...') - frames_generator = get_video_frames_generator(source_path='...') + frames_generator = sv.get_video_frames_generator(source_path='...') with sv.VideoSink(target_path='...', video_info=video_info) as sink: for frame in frames_generator: From 731d580a83cb12091d02a8f8381f9636d6dc8462 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Wed, 14 Aug 2024 14:36:02 +0300 Subject: [PATCH 07/11] Correct sizes in keypoint docstrings --- supervision/keypoint/core.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index 6d2a69c6..d4f6ed91 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -88,15 +88,17 @@ class KeyPoints: ``` Attributes: - xy (np.ndarray): An array of shape `(n, 2)` containing - the bounding boxes coordinates in format `[x1, y1]` + xy (np.ndarray): An array of shape `(n, m, 2)` containing + `n` detected objects, each composed of0 `m` equally-sized + sets of keypoints, where each point is `[x, y]`. confidence (Optional[np.ndarray]): An array of shape - `(n,)` containing the confidence scores of the keypoint keypoints. + `(n, m)` containing the confidence scores of each keypoint. class_id (Optional[np.ndarray]): An array of shape - `(n,)` containing the class ids of the keypoint keypoints. + `(n,)` containing the class ids of the detected objects. 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. + is either a NumPy array or a list of corresponding data, of length `n` + (one entry per detected object). """ # noqa: E501 // docs xy: npt.NDArray[np.float32] @@ -132,7 +134,7 @@ class KeyPoints: ]: """ Iterates over the Keypoint object and yield a tuple of - `(xy, confidence, class_id, data)` for each keypoint detection. + `(xy, confidence, class_id, data)` for each object detection. """ for i in range(len(self.xy)): yield ( From bae1797e83def34e1baae73e7f4240296ec0d5c7 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Mon, 19 Aug 2024 00:20:04 +0300 Subject: [PATCH 08/11] Remove "mypy type hint" from annotator scene assertions --- supervision/annotators/core.py | 44 +++++++++++++++--------------- supervision/keypoint/annotators.py | 6 ++-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index 565d6355..dd628772 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -88,7 +88,7 @@ class BoxAnnotator(BaseAnnotator): ![bounding-box-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/bounding-box-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -174,7 +174,7 @@ class BoundingBoxAnnotator(BaseAnnotator): ![bounding-box-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/bounding-box-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -259,7 +259,7 @@ class OrientedBoxAnnotator(BaseAnnotator): ) ``` """ # noqa E501 // docs - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if detections.data is None or ORIENTED_BOX_COORDINATES not in detections.data: return scene @@ -345,7 +345,7 @@ class MaskAnnotator(BaseAnnotator): ![mask-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/mask-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if detections.mask is None: return scene @@ -435,7 +435,7 @@ class PolygonAnnotator(BaseAnnotator): ![polygon-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/polygon-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if detections.mask is None: return scene @@ -522,7 +522,7 @@ class ColorAnnotator(BaseAnnotator): ![box-mask-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/box-mask-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) scene_with_boxes = scene.copy() for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) @@ -618,7 +618,7 @@ class HaloAnnotator(BaseAnnotator): ![halo-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/halo-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if detections.mask is None: return scene colored_mask = np.zeros_like(scene, dtype=np.uint8) @@ -718,7 +718,7 @@ class EllipseAnnotator(BaseAnnotator): ![ellipse-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/ellipse-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -810,7 +810,7 @@ class BoxCornerAnnotator(BaseAnnotator): ![box-corner-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/box-corner-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -900,7 +900,7 @@ class CircleAnnotator(BaseAnnotator): ![circle-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/circle-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) center = ((x1 + x2) // 2, (y1 + y2) // 2) @@ -993,7 +993,7 @@ class DotAnnotator(BaseAnnotator): ![dot-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/dot-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) xy = detections.get_anchors_coordinates(anchor=self.position) for detection_idx in range(len(detections)): color = resolve_color( @@ -1103,7 +1103,7 @@ class LabelAnnotator(BaseAnnotator): ![label-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/label-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) font = cv2.FONT_HERSHEY_SIMPLEX anchors_coordinates = detections.get_anchors_coordinates( anchor=self.text_anchor @@ -1320,7 +1320,7 @@ class RichLabelAnnotator(BaseAnnotator): ``` """ - assert isinstance(scene, Image.Image), "MyPy type hint" + assert isinstance(scene, Image.Image) draw = ImageDraw.Draw(scene) anchors_coordinates = detections.get_anchors_coordinates( anchor=self.text_anchor @@ -1453,7 +1453,7 @@ class BlurAnnotator(BaseAnnotator): ![blur-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/blur-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) image_height, image_width = scene.shape[:2] clipped_xyxy = clip_boxes( xyxy=detections.xyxy, resolution_wh=(image_width, image_height) @@ -1552,7 +1552,7 @@ class TraceAnnotator(BaseAnnotator): ![trace-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/trace-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if detections.tracker_id is None: raise ValueError( "The `tracker_id` field is missing in the provided detections." @@ -1656,7 +1656,7 @@ class HeatMapAnnotator(BaseAnnotator): ![heatmap-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/heat-map-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if self.heat_mask is None: self.heat_mask = np.zeros(scene.shape[:2], dtype=np.float32) @@ -1737,7 +1737,7 @@ class PixelateAnnotator(BaseAnnotator): ![pixelate-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/pixelate-annotator-example-10.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) image_height, image_width = scene.shape[:2] clipped_xyxy = clip_boxes( xyxy=detections.xyxy, resolution_wh=(image_width, image_height) @@ -1831,7 +1831,7 @@ class TriangleAnnotator(BaseAnnotator): ![triangle-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/triangle-annotator-example.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) xy = detections.get_anchors_coordinates(anchor=self.position) for detection_idx in range(len(detections)): color = resolve_color( @@ -1932,7 +1932,7 @@ class RoundBoxAnnotator(BaseAnnotator): ![round-box-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/round-box-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) for detection_idx in range(len(detections)): x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) color = resolve_color( @@ -2076,7 +2076,7 @@ class PercentageBarAnnotator(BaseAnnotator): ![percentage-bar-example](https://media.roboflow.com/ supervision-annotator-examples/percentage-bar-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) self.validate_custom_values(custom_values=custom_values, detections=detections) anchors = detections.get_anchors_coordinates(anchor=self.position) @@ -2249,7 +2249,7 @@ class CropAnnotator(BaseAnnotator): ) ``` """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) crops = [ crop_image(image=scene, xyxy=xyxy) for xyxy in detections.xyxy.astype(int) ] @@ -2388,7 +2388,7 @@ class BackgroundOverlayAnnotator(BaseAnnotator): ![background-overlay-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/background-color-annotator-example-purple.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) colored_mask = np.full_like(scene, self.color.as_bgr(), dtype=np.uint8) cv2.addWeighted( diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py index be52133a..559bfa92 100644 --- a/supervision/keypoint/annotators.py +++ b/supervision/keypoint/annotators.py @@ -78,7 +78,7 @@ class VertexAnnotator(BaseKeyPointAnnotator): ![vertex-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/vertex-annotator-example.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if len(key_points) == 0: return scene @@ -156,7 +156,7 @@ class EdgeAnnotator(BaseKeyPointAnnotator): ![edge-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/edge-annotator-example.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) if len(key_points) == 0: return scene @@ -310,7 +310,7 @@ class VertexLabelAnnotator: ![vertex-label-annotator-custom-example](https://media.roboflow.com/ supervision-annotator-examples/vertex-label-annotator-custom-example.png) """ - assert isinstance(scene, np.ndarray), "MyPy type hint" + assert isinstance(scene, np.ndarray) font = cv2.FONT_HERSHEY_SIMPLEX skeletons_count, points_count, _ = key_points.xy.shape From cdc3b4d6b1fac98c43502af936066cfbdf360943 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Mon, 19 Aug 2024 00:33:16 +0300 Subject: [PATCH 09/11] Typo in keypoint docstring --- supervision/keypoint/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/keypoint/core.py b/supervision/keypoint/core.py index d4f6ed91..39b2f08c 100644 --- a/supervision/keypoint/core.py +++ b/supervision/keypoint/core.py @@ -89,7 +89,7 @@ class KeyPoints: Attributes: xy (np.ndarray): An array of shape `(n, m, 2)` containing - `n` detected objects, each composed of0 `m` equally-sized + `n` detected objects, each composed of `m` equally-sized sets of keypoints, where each point is `[x, y]`. confidence (Optional[np.ndarray]): An array of shape `(n, m)` containing the confidence scores of each keypoint. @@ -97,7 +97,7 @@ class KeyPoints: `(n,)` containing the class ids of the detected objects. 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, of length `n` + is either a NumPy array or a list of corresponding data of length `n` (one entry per detected object). """ # noqa: E501 // docs From 8f480ead6a244068ed9696cdbe7ec23b3585e64e Mon Sep 17 00:00:00 2001 From: LinasKo Date: Mon, 19 Aug 2024 00:52:58 +0300 Subject: [PATCH 10/11] Fix regression: OBB annotator --- supervision/annotators/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index dd628772..637dd9d8 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -262,9 +262,10 @@ class OrientedBoxAnnotator(BaseAnnotator): assert isinstance(scene, np.ndarray) if detections.data is None or ORIENTED_BOX_COORDINATES not in detections.data: return scene + obb_boxes = np.array(detections.data[ORIENTED_BOX_COORDINATES]).astype(int) for detection_idx in range(len(detections)): - bbox = detections.xyxy[detection_idx].astype(int) + obb = obb_boxes[detection_idx] color = resolve_color( color=self.color, detections=detections, @@ -274,7 +275,7 @@ class OrientedBoxAnnotator(BaseAnnotator): else custom_color_lookup, ) - cv2.drawContours(scene, [bbox], 0, color.as_bgr(), self.thickness) + cv2.drawContours(scene, [obb], 0, color.as_bgr(), self.thickness) return scene From a3262a8f9cdc6a35de4b04c934dd37e42f4f43c6 Mon Sep 17 00:00:00 2001 From: LinasKo Date: Mon, 26 Aug 2024 14:56:59 +0300 Subject: [PATCH 11/11] Add types to dataset split func, add Optional to annotator --- supervision/annotators/core.py | 2 +- supervision/dataset/core.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index 637dd9d8..5320f19e 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -2018,7 +2018,7 @@ class PercentageBarAnnotator(BaseAnnotator): position (Position): The anchor position of drawing the percentage bar. color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. - border_thickness (int): The thickness of the border lines. + border_thickness (Optional[int]): The thickness of the border lines. """ self.height: int = height self.width: int = width diff --git a/supervision/dataset/core.py b/supervision/dataset/core.py index 37ba530b..f2cf7bce 100644 --- a/supervision/dataset/core.py +++ b/supervision/dataset/core.py @@ -43,7 +43,10 @@ class BaseDataset(ABC): @abstractmethod def split( - self, split_ratio=0.8, random_state=None, shuffle: bool = True + self, + split_ratio: float = 0.8, + random_state: Optional[int] = None, + shuffle: bool = True, ) -> Tuple[BaseDataset, BaseDataset]: pass @@ -174,7 +177,10 @@ class DetectionDataset(BaseDataset): return True def split( - self, split_ratio=0.8, random_state=None, shuffle: bool = True + self, + split_ratio: float = 0.8, + random_state: Optional[int] = None, + shuffle: bool = True, ) -> Tuple[DetectionDataset, DetectionDataset]: """ Splits the dataset into two parts (training and testing) @@ -777,7 +783,10 @@ class ClassificationDataset(BaseDataset): return True def split( - self, split_ratio=0.8, random_state=None, shuffle: bool = True + self, + split_ratio: float = 0.8, + random_state: Optional[int] = None, + shuffle: bool = True, ) -> Tuple[ClassificationDataset, ClassificationDataset]: """ Splits the dataset into two parts (training and testing)