From 212e69b5a48637553a9512029cfd6734e720d43f Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Tue, 15 Jul 2025 16:36:01 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=A7=AA=20type=20hints=20to=20?= =?UTF-8?q?use=20the=20new=20union=20syntax=20for=20optional=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated type hints across multiple files to replace `Optional[X]` with `X | None` for better readability and consistency. - Added `from __future__ import annotations` to files to enable postponed evaluation of type annotations. - Adjusted function signatures and variable types in various modules including `polygon_zone.py`, `smoother.py`, `transformers.py`, `vlm.py`, and others. - Ensured compatibility with Python 3.10+ type hinting features. Signed-off-by: Onuralp SEZER --- .pre-commit-config.yaml | 2 +- .../scripts/download_from_youtube.py | 5 +- examples/time_in_zone/scripts/draw_zones.py | 8 +- .../traffic_analysis/inference_example.py | 5 +- .../traffic_analysis/ultralytics_example.py | 5 +- supervision/annotators/core.py | 151 +++++++++--------- supervision/annotators/utils.py | 17 +- supervision/assets/downloader.py | 5 +- supervision/dataset/formats/pascal_voc.py | 5 +- supervision/dataset/formats/yolo.py | 8 +- supervision/dataset/utils.py | 12 +- supervision/detection/line_zone.py | 12 +- .../detection/tools/inference_slicer.py | 20 +-- supervision/detection/tools/polygon_zone.py | 5 +- supervision/detection/tools/smoother.py | 5 +- supervision/detection/tools/transformers.py | 20 +-- supervision/detection/vlm.py | 36 ++--- supervision/draw/utils.py | 7 +- supervision/keypoint/annotators.py | 19 +-- supervision/metrics/detection.py | 2 +- supervision/utils/file.py | 17 +- supervision/utils/image.py | 47 +++--- supervision/utils/internal.py | 7 +- supervision/utils/notebook.py | 8 +- supervision/utils/video.py | 3 +- test/annotators/test_utils.py | 5 +- test/classification/test_core.py | 7 +- test/dataset/formats/test_coco.py | 5 +- test/dataset/formats/test_pascal_voc.py | 5 +- test/dataset/formats/test_yolo.py | 13 +- test/dataset/test_core.py | 5 +- test/dataset/test_utils.py | 10 +- test/detection/test_core.py | 11 +- test/detection/test_line_counter.py | 7 +- test/detection/test_overlap_filter.py | 7 +- test/detection/test_utils.py | 14 +- test/detection/test_vlm.py | 22 +-- test/detection/tools/test_inference_slicer.py | 11 +- test/draw/test_color.py | 7 +- test/metrics/test_detection.py | 15 +- test/test_utils.py | 20 +-- test/utils/test_file.py | 5 +- 42 files changed, 320 insertions(+), 280 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 85b49dfb..24e432cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -58,6 +58,6 @@ repos: rev: v3.20.0 hooks: - id: pyupgrade - args: ["--py39-plus"] + args: ["--py310-plus"] additional_dependencies: - tomli diff --git a/examples/time_in_zone/scripts/download_from_youtube.py b/examples/time_in_zone/scripts/download_from_youtube.py index ff7d94c3..d8673631 100644 --- a/examples/time_in_zone/scripts/download_from_youtube.py +++ b/examples/time_in_zone/scripts/download_from_youtube.py @@ -1,11 +1,12 @@ +from __future__ import annotations + import argparse import os -from typing import Optional from pytube import YouTube -def main(url: str, output_path: Optional[str], file_name: Optional[str]) -> None: +def main(url: str, output_path: str | None, file_name: str | None) -> None: yt = YouTube(url) stream = yt.streams.get_highest_resolution() diff --git a/examples/time_in_zone/scripts/draw_zones.py b/examples/time_in_zone/scripts/draw_zones.py index 0e09a865..74508ccd 100644 --- a/examples/time_in_zone/scripts/draw_zones.py +++ b/examples/time_in_zone/scripts/draw_zones.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import argparse import json import os -from typing import Any, Optional +from typing import Any import cv2 import numpy as np @@ -19,10 +21,10 @@ COLORS = sv.ColorPalette.DEFAULT WINDOW_NAME = "Draw Zones" POLYGONS = [[]] -current_mouse_position: Optional[tuple[int, int]] = None +current_mouse_position: tuple[int, int] | None = None -def resolve_source(source_path: str) -> Optional[np.ndarray]: +def resolve_source(source_path: str) -> np.ndarray | None: if not os.path.exists(source_path): return None diff --git a/examples/traffic_analysis/inference_example.py b/examples/traffic_analysis/inference_example.py index 6cd4e4bd..f7530ce7 100644 --- a/examples/traffic_analysis/inference_example.py +++ b/examples/traffic_analysis/inference_example.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import argparse import os from collections.abc import Iterable -from typing import Optional import cv2 import numpy as np @@ -78,7 +79,7 @@ class VideoProcessor: roboflow_api_key: str, model_id: str, source_video_path: str, - target_video_path: Optional[str] = None, + target_video_path: str | None = None, confidence_threshold: float = 0.3, iou_threshold: float = 0.7, ) -> None: diff --git a/examples/traffic_analysis/ultralytics_example.py b/examples/traffic_analysis/ultralytics_example.py index 4beb1826..4df89405 100644 --- a/examples/traffic_analysis/ultralytics_example.py +++ b/examples/traffic_analysis/ultralytics_example.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import argparse from collections.abc import Iterable -from typing import Optional import cv2 import numpy as np @@ -75,7 +76,7 @@ class VideoProcessor: self, source_weights_path: str, source_video_path: str, - target_video_path: Optional[str] = None, + target_video_path: str | None = None, confidence_threshold: float = 0.3, iou_threshold: float = 0.7, ) -> None: diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index e3607224..4462351a 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -1,6 +1,7 @@ +from __future__ import annotations + from functools import lru_cache from math import sqrt -from typing import Optional, Union import cv2 import numpy as np @@ -65,14 +66,14 @@ class _BaseLabelAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, - text_color: Union[Color, ColorPalette] = Color.WHITE, + color: Color | ColorPalette = ColorPalette.DEFAULT, + text_color: Color | ColorPalette = Color.WHITE, text_padding: int = 10, text_position: Position = Position.TOP_LEFT, color_lookup: ColorLookup = ColorLookup.CLASS, border_radius: int = 0, smart_position: bool = False, - max_line_length: Optional[int] = None, + max_line_length: int | None = None, ): """ Initializes the _BaseLabelAnnotator. @@ -95,14 +96,14 @@ class _BaseLabelAnnotator(BaseAnnotator): line before wrapping the text. None means no wrapping. """ - self.color: Union[Color, ColorPalette] = color - self.text_color: Union[Color, ColorPalette] = text_color + self.color: Color | ColorPalette = color + self.text_color: Color | ColorPalette = text_color self.text_padding: int = text_padding self.text_anchor: Position = text_position self.color_lookup: ColorLookup = color_lookup self.border_radius: int = border_radius self.smart_position = smart_position - self.max_line_length: Optional[int] = max_line_length + self.max_line_length: int | None = max_line_length def _adjust_labels_in_frame( self, @@ -155,7 +156,7 @@ class BoxAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, thickness: int = 2, color_lookup: ColorLookup = ColorLookup.CLASS, ): @@ -167,7 +168,7 @@ class BoxAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.thickness: int = thickness self.color_lookup: ColorLookup = color_lookup @@ -176,7 +177,7 @@ class BoxAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with bounding boxes based on the provided detections. @@ -238,7 +239,7 @@ class OrientedBoxAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, thickness: int = 2, color_lookup: ColorLookup = ColorLookup.CLASS, ): @@ -250,7 +251,7 @@ class OrientedBoxAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.thickness: int = thickness self.color_lookup: ColorLookup = color_lookup @@ -259,7 +260,7 @@ class OrientedBoxAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with oriented bounding boxes based on the provided detections. @@ -327,7 +328,7 @@ class MaskAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, opacity: float = 0.5, color_lookup: ColorLookup = ColorLookup.CLASS, ): @@ -339,7 +340,7 @@ class MaskAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.opacity = opacity self.color_lookup: ColorLookup = color_lookup @@ -348,7 +349,7 @@ class MaskAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with masks based on the provided detections. @@ -417,7 +418,7 @@ class PolygonAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, thickness: int = 2, color_lookup: ColorLookup = ColorLookup.CLASS, ): @@ -429,7 +430,7 @@ class PolygonAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.thickness: int = thickness self.color_lookup: ColorLookup = color_lookup @@ -438,7 +439,7 @@ class PolygonAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with polygons based on the provided detections. @@ -504,7 +505,7 @@ class ColorAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, opacity: float = 0.5, color_lookup: ColorLookup = ColorLookup.CLASS, ): @@ -516,7 +517,7 @@ class ColorAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.color_lookup: ColorLookup = color_lookup self.opacity = opacity @@ -525,7 +526,7 @@ class ColorAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with box masks based on the provided detections. @@ -596,7 +597,7 @@ class HaloAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, opacity: float = 0.8, kernel_size: int = 40, color_lookup: ColorLookup = ColorLookup.CLASS, @@ -611,7 +612,7 @@ class HaloAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.opacity = opacity self.color_lookup: ColorLookup = color_lookup self.kernel_size: int = kernel_size @@ -621,7 +622,7 @@ class HaloAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with halos based on the provided detections. @@ -694,7 +695,7 @@ class EllipseAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, thickness: int = 2, start_angle: int = -45, end_angle: int = 235, @@ -710,7 +711,7 @@ class EllipseAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.thickness: int = thickness self.start_angle: int = start_angle self.end_angle: int = end_angle @@ -721,7 +722,7 @@ class EllipseAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with ellipses based on the provided detections. @@ -789,7 +790,7 @@ class BoxCornerAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, thickness: int = 4, corner_length: int = 15, color_lookup: ColorLookup = ColorLookup.CLASS, @@ -803,7 +804,7 @@ class BoxCornerAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.thickness: int = thickness self.corner_length: int = corner_length self.color_lookup: ColorLookup = color_lookup @@ -813,7 +814,7 @@ class BoxCornerAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with box corners based on the provided detections. @@ -880,7 +881,7 @@ class CircleAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, thickness: int = 2, color_lookup: ColorLookup = ColorLookup.CLASS, ): @@ -893,7 +894,7 @@ class CircleAnnotator(BaseAnnotator): Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.thickness: int = thickness self.color_lookup: ColorLookup = color_lookup @@ -902,7 +903,7 @@ class CircleAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with circles based on the provided detections. @@ -969,12 +970,12 @@ class DotAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, radius: int = 4, position: Position = Position.CENTER, color_lookup: ColorLookup = ColorLookup.CLASS, outline_thickness: int = 0, - outline_color: Union[Color, ColorPalette] = Color.BLACK, + outline_color: Color | ColorPalette = Color.BLACK, ): """ Args: @@ -989,19 +990,19 @@ class DotAnnotator(BaseAnnotator): use for outline. It is activated by setting outline_thickness to a value greater than 0. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.radius: int = radius self.position: Position = position self.color_lookup: ColorLookup = color_lookup self.outline_thickness = outline_thickness - self.outline_color: Union[Color, ColorPalette] = outline_color + self.outline_color: Color | ColorPalette = outline_color @ensure_cv2_image_for_annotation def annotate( self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with dots based on the provided detections. @@ -1075,8 +1076,8 @@ class LabelAnnotator(_BaseLabelAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, - text_color: Union[Color, ColorPalette] = Color.WHITE, + color: Color | ColorPalette = ColorPalette.DEFAULT, + text_color: Color | ColorPalette = Color.WHITE, text_scale: float = 0.5, text_thickness: int = 1, text_padding: int = 10, @@ -1084,7 +1085,7 @@ class LabelAnnotator(_BaseLabelAnnotator): color_lookup: ColorLookup = ColorLookup.CLASS, border_radius: int = 0, smart_position: bool = False, - max_line_length: Optional[int] = None, + max_line_length: int | None = None, ): self.text_scale: float = text_scale self.text_thickness: int = text_thickness @@ -1104,8 +1105,8 @@ class LabelAnnotator(_BaseLabelAnnotator): self, scene: ImageType, # Ensure scene is initially a NumPy array here detections: Detections, - labels: Optional[list[str]] = None, - custom_color_lookup: Optional[np.ndarray] = None, + labels: list[str] | None = None, + custom_color_lookup: np.ndarray | None = None, ) -> np.ndarray: assert isinstance(scene, np.ndarray) validate_labels(labels, detections) @@ -1189,7 +1190,7 @@ class LabelAnnotator(_BaseLabelAnnotator): labels: list[str], label_properties: np.ndarray, detections: Detections, - custom_color_lookup: Optional[np.ndarray], + custom_color_lookup: np.ndarray | None, ) -> None: assert len(labels) == len(label_properties) == len(detections), ( f"Number of label properties ({len(label_properties)}), " @@ -1316,16 +1317,16 @@ class RichLabelAnnotator(_BaseLabelAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, - text_color: Union[Color, ColorPalette] = Color.WHITE, - font_path: Optional[str] = None, + color: Color | ColorPalette = ColorPalette.DEFAULT, + text_color: Color | ColorPalette = Color.WHITE, + font_path: str | None = None, font_size: int = 10, text_padding: int = 10, text_position: Position = Position.TOP_LEFT, color_lookup: ColorLookup = ColorLookup.CLASS, border_radius: int = 0, smart_position: bool = False, - max_line_length: Optional[int] = None, + max_line_length: int | None = None, ): self.font_path = font_path self.font_size = font_size @@ -1346,8 +1347,8 @@ class RichLabelAnnotator(_BaseLabelAnnotator): self, scene: ImageType, detections: Detections, - labels: Optional[list[str]] = None, - custom_color_lookup: Optional[np.ndarray] = None, + labels: list[str] | None = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: assert isinstance(scene, Image.Image) validate_labels(labels, detections) @@ -1427,7 +1428,7 @@ class RichLabelAnnotator(_BaseLabelAnnotator): labels: list[str], label_properties: np.ndarray, detections: Detections, - custom_color_lookup: Optional[np.ndarray], + custom_color_lookup: np.ndarray | None, ) -> None: assert len(labels) == len(label_properties) == len(detections), ( f"Number of label properties ({len(label_properties)}), " @@ -1485,7 +1486,7 @@ class RichLabelAnnotator(_BaseLabelAnnotator): y_position += line_height + self.text_padding @staticmethod - def _load_font(font_size: int, font_path: Optional[str]): + def _load_font(font_size: int, font_path: str | None): def load_default_font(size): try: return ImageFont.load_default(size) @@ -1527,7 +1528,7 @@ class IconAnnotator(BaseAnnotator): @ensure_cv2_image_for_annotation def annotate( - self, scene: ImageType, detections: Detections, icon_path: Union[str, list[str]] + self, scene: ImageType, detections: Detections, icon_path: str | list[str] ) -> ImageType: """ Annotates the given scene with given icons. @@ -1677,7 +1678,7 @@ class TraceAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, position: Position = Position.CENTER, trace_length: int = 30, thickness: int = 2, @@ -1695,7 +1696,7 @@ class TraceAnnotator(BaseAnnotator): color_lookup (ColorLookup): Strategy for mapping colors to annotations. Options are `INDEX`, `CLASS`, `TRACK`. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.trace = Trace(max_size=trace_length, anchor=position) self.thickness = thickness self.color_lookup: ColorLookup = color_lookup @@ -1705,7 +1706,7 @@ class TraceAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Draws trace paths on the frame based on the detection coordinates provided. @@ -1812,7 +1813,7 @@ class HeatMapAnnotator(BaseAnnotator): self.kernel_size = kernel_size self.top_hue = top_hue self.low_hue = low_hue - self.heat_mask: Optional[npt.NDArray[np.float32]] = None + self.heat_mask: npt.NDArray[np.float32] | None = None @ensure_cv2_image_for_annotation def annotate(self, scene: ImageType, detections: Detections) -> ImageType: @@ -1963,13 +1964,13 @@ class TriangleAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, base: int = 10, height: int = 10, position: Position = Position.TOP_CENTER, color_lookup: ColorLookup = ColorLookup.CLASS, outline_thickness: int = 0, - outline_color: Union[Color, ColorPalette] = Color.BLACK, + outline_color: Color | ColorPalette = Color.BLACK, ): """ Args: @@ -1985,20 +1986,20 @@ class TriangleAnnotator(BaseAnnotator): use for outline. It is activated by setting outline_thickness to a value greater than 0. """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.base: int = base self.height: int = height self.position: Position = position self.color_lookup: ColorLookup = color_lookup self.outline_thickness: int = outline_thickness - self.outline_color: Union[Color, ColorPalette] = outline_color + self.outline_color: Color | ColorPalette = outline_color @ensure_cv2_image_for_annotation def annotate( self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with triangles based on the provided detections. @@ -2081,7 +2082,7 @@ class RoundBoxAnnotator(BaseAnnotator): def __init__( self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, thickness: int = 2, color_lookup: ColorLookup = ColorLookup.CLASS, roundness: float = 0.6, @@ -2098,7 +2099,7 @@ class RoundBoxAnnotator(BaseAnnotator): By default roundness percent is calculated based on smaller side length (width or height). """ - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.thickness: int = thickness self.color_lookup: ColorLookup = color_lookup if not 0 < roundness <= 1.0: @@ -2110,7 +2111,7 @@ class RoundBoxAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with bounding boxes with rounded edges @@ -2214,11 +2215,11 @@ class PercentageBarAnnotator(BaseAnnotator): self, height: int = 16, width: int = 80, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + color: Color | ColorPalette = ColorPalette.DEFAULT, border_color: Color = Color.BLACK, position: Position = Position.TOP_CENTER, color_lookup: ColorLookup = ColorLookup.CLASS, - border_thickness: Optional[int] = None, + border_thickness: int | None = None, ): """ Args: @@ -2234,7 +2235,7 @@ class PercentageBarAnnotator(BaseAnnotator): """ self.height: int = height self.width: int = width - self.color: Union[Color, ColorPalette] = color + self.color: Color | ColorPalette = color self.border_color: Color = border_color self.position: Position = position self.color_lookup: ColorLookup = color_lookup @@ -2247,8 +2248,8 @@ class PercentageBarAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, - custom_values: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, + custom_values: np.ndarray | None = None, ) -> ImageType: """ Annotates the given scene with percentage bars based on the provided @@ -2366,7 +2367,7 @@ class PercentageBarAnnotator(BaseAnnotator): @staticmethod def validate_custom_values( - custom_values: Optional[Union[np.ndarray, list[float]]], detections: Detections + custom_values: np.ndarray | list[float] | None, detections: Detections ) -> None: if custom_values is None: if detections.confidence is None: @@ -2400,7 +2401,7 @@ class CropAnnotator(BaseAnnotator): self, position: Position = Position.TOP_CENTER, scale_factor: float = 2.0, - border_color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, + border_color: Color | ColorPalette = ColorPalette.DEFAULT, border_thickness: int = 2, border_color_lookup: ColorLookup = ColorLookup.CLASS, ): @@ -2419,7 +2420,7 @@ class CropAnnotator(BaseAnnotator): """ self.position: Position = position self.scale_factor: float = scale_factor - self.border_color: Union[Color, ColorPalette] = border_color + self.border_color: Color | ColorPalette = border_color self.border_thickness: int = border_thickness self.border_color_lookup: ColorLookup = border_color_lookup @@ -2428,7 +2429,7 @@ class CropAnnotator(BaseAnnotator): self, scene: ImageType, detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, + custom_color_lookup: np.ndarray | None = None, ) -> ImageType: """ Annotates the provided scene with scaled and cropped parts of the image based diff --git a/supervision/annotators/utils.py b/supervision/annotators/utils.py index f51dfb19..a84e2049 100644 --- a/supervision/annotators/utils.py +++ b/supervision/annotators/utils.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import textwrap from enum import Enum -from typing import Optional, Union import numpy as np @@ -35,7 +36,7 @@ class ColorLookup(Enum): def resolve_color_idx( detections: Detections, detection_idx: int, - color_lookup: Union[ColorLookup, np.ndarray] = ColorLookup.CLASS, + color_lookup: ColorLookup | np.ndarray = ColorLookup.CLASS, ) -> int: if detection_idx >= len(detections): raise ValueError( @@ -124,17 +125,17 @@ def resolve_text_background_xyxy( ) -def get_color_by_index(color: Union[Color, ColorPalette], idx: int) -> Color: +def get_color_by_index(color: Color | ColorPalette, idx: int) -> Color: if isinstance(color, ColorPalette): return color.by_idx(idx) return color def resolve_color( - color: Union[Color, ColorPalette], + color: Color | ColorPalette, detections: Detections, detection_idx: int, - color_lookup: Union[ColorLookup, np.ndarray] = ColorLookup.CLASS, + color_lookup: ColorLookup | np.ndarray = ColorLookup.CLASS, ) -> Color: idx = resolve_color_idx( detections=detections, @@ -189,7 +190,7 @@ def wrap_text(text: str, max_line_length=None) -> list[str]: return all_lines if all_lines else [""] -def validate_labels(labels: Optional[list[str]], detections: Detections): +def validate_labels(labels: list[str] | None, detections: Detections): """ Validates that the number of provided labels matches the number of detections. @@ -211,7 +212,7 @@ def validate_labels(labels: Optional[list[str]], detections: Detections): def get_labels_text( - detections: Detections, custom_labels: Optional[list[str]] + detections: Detections, custom_labels: list[str] | None ) -> list[str]: """ Retrieves the text labels for the detections. @@ -309,7 +310,7 @@ def snap_boxes(xyxy: np.ndarray, resolution_wh: tuple[int, int]) -> np.ndarray: class Trace: def __init__( self, - max_size: Optional[int] = None, + max_size: int | None = None, start_frame_id: int = 0, anchor: Position = Position.CENTER, ) -> None: diff --git a/supervision/assets/downloader.py b/supervision/assets/downloader.py index c8e3e56d..dbdd84ce 100644 --- a/supervision/assets/downloader.py +++ b/supervision/assets/downloader.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import os from hashlib import new as hash_new from pathlib import Path from shutil import copyfileobj -from typing import Union from requests import get from tqdm.auto import tqdm @@ -32,7 +33,7 @@ def is_md5_hash_matching(filename: str, original_md5_hash: str) -> bool: return computed_md5_hash.hexdigest() == original_md5_hash -def download_assets(asset_name: Union[VideoAssets, str]) -> str: +def download_assets(asset_name: VideoAssets | str) -> str: """ Download a specified asset if it doesn't already exist or is corrupted. diff --git a/supervision/dataset/formats/pascal_voc.py b/supervision/dataset/formats/pascal_voc.py index 8eddfb96..fb068ccc 100644 --- a/supervision/dataset/formats/pascal_voc.py +++ b/supervision/dataset/formats/pascal_voc.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import os from pathlib import Path -from typing import Optional from xml.etree.ElementTree import Element, SubElement import cv2 @@ -15,7 +16,7 @@ from supervision.utils.file import list_files_with_extensions def object_to_pascal_voc( - xyxy: np.ndarray, name: str, polygon: Optional[np.ndarray] = None + xyxy: np.ndarray, name: str, polygon: np.ndarray | None = None ) -> Element: root = Element("object") diff --git a/supervision/dataset/formats/yolo.py b/supervision/dataset/formats/yolo.py index 7903e598..f065aefd 100644 --- a/supervision/dataset/formats/yolo.py +++ b/supervision/dataset/formats/yolo.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import os from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from PIL import Image @@ -205,7 +207,7 @@ def object_to_yolo( xyxy: np.ndarray, class_id: int, image_shape: tuple[int, int, int], - polygon: Optional[np.ndarray] = None, + polygon: np.ndarray | None = None, ) -> str: h, w, _ = image_shape if polygon is None: @@ -260,7 +262,7 @@ def detections_to_yolo_annotations( def save_yolo_annotations( - dataset: "DetectionDataset", + dataset: DetectionDataset, annotations_directory_path: str, min_image_area_percentage: float = 0.0, max_image_area_percentage: float = 1.0, diff --git a/supervision/dataset/utils.py b/supervision/dataset/utils.py index 487ff1d9..9c02c415 100644 --- a/supervision/dataset/utils.py +++ b/supervision/dataset/utils.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import copy import os import random import shutil from pathlib import Path -from typing import TYPE_CHECKING, Optional, TypeVar, Union +from typing import TYPE_CHECKING, TypeVar import cv2 import numpy as np @@ -98,9 +100,7 @@ def map_detections_class_id( return detections_copy -def save_dataset_images( - dataset: "DetectionDataset", images_directory_path: str -) -> None: +def save_dataset_images(dataset: DetectionDataset, images_directory_path: str) -> None: Path(images_directory_path).mkdir(parents=True, exist_ok=True) for image_path in dataset.image_paths: final_path = os.path.join(images_directory_path, Path(image_path).name) @@ -114,7 +114,7 @@ def save_dataset_images( def train_test_split( data: list[T], train_ratio: float = 0.8, - random_state: Optional[int] = None, + random_state: int | None = None, shuffle: bool = True, ) -> tuple[list[T], list[T]]: """ @@ -140,7 +140,7 @@ def train_test_split( def rle_to_mask( - rle: Union[npt.NDArray[np.int_], list[int]], resolution_wh: tuple[int, int] + rle: npt.NDArray[np.int_] | list[int], resolution_wh: tuple[int, int] ) -> npt.NDArray[np.bool_]: """ Converts run-length encoding (RLE) to a binary mask. diff --git a/supervision/detection/line_zone.py b/supervision/detection/line_zone.py index b94fc531..bf7e42da 100644 --- a/supervision/detection/line_zone.py +++ b/supervision/detection/line_zone.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import math import warnings from collections import Counter, defaultdict, deque from collections.abc import Iterable from functools import lru_cache -from typing import Any, Literal, Optional +from typing import Any, Literal import cv2 import numpy as np @@ -161,7 +163,7 @@ class LineZone: self._compute_anchor_sides(detections) ) - class_ids: list[Optional[int]] = ( + class_ids: list[int | None] = ( list(detections.class_id) if detections.class_id is not None else [None] * len(detections) @@ -325,8 +327,8 @@ class LineZoneAnnotator: text_scale: float = 0.5, text_offset: float = 1.5, text_padding: int = 10, - custom_in_text: Optional[str] = None, - custom_out_text: Optional[str] = None, + custom_in_text: str | None = None, + custom_out_text: str | None = None, display_in_count: bool = True, display_out_count: bool = True, display_text_box: bool = True, @@ -758,7 +760,7 @@ class LineZoneAnnotatorMulticlass: self, frame: np.ndarray, line_zones: list[LineZone], - line_zone_labels: Optional[list[str]] = None, + line_zone_labels: list[str] | None = None, ) -> np.ndarray: """ Draws a table with the number of objects of each class that crossed each line. diff --git a/supervision/detection/tools/inference_slicer.py b/supervision/detection/tools/inference_slicer.py index dd01f6d1..e0ffb3bf 100644 --- a/supervision/detection/tools/inference_slicer.py +++ b/supervision/detection/tools/inference_slicer.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import warnings +from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor, as_completed -from typing import Callable, Optional, Union import numpy as np @@ -18,7 +20,7 @@ from supervision.utils.internal import ( def move_detections( detections: Detections, offset: np.ndarray, - resolution_wh: Optional[tuple[int, int]] = None, + resolution_wh: tuple[int, int] | None = None, ) -> Detections: """ Args: @@ -89,9 +91,9 @@ class InferenceSlicer: self, callback: Callable[[np.ndarray], Detections], slice_wh: tuple[int, int] = (320, 320), - overlap_ratio_wh: Optional[tuple[float, float]] = (0.2, 0.2), - overlap_wh: Optional[tuple[int, int]] = None, - overlap_filter: Union[OverlapFilter, str] = OverlapFilter.NON_MAX_SUPPRESSION, + overlap_ratio_wh: tuple[float, float] | None = (0.2, 0.2), + overlap_wh: tuple[int, int] | None = None, + overlap_filter: OverlapFilter | str = OverlapFilter.NON_MAX_SUPPRESSION, iou_threshold: float = 0.5, match_metric: str = "IOU", thread_workers: int = 1, @@ -208,8 +210,8 @@ class InferenceSlicer: def _generate_offset( resolution_wh: tuple[int, int], slice_wh: tuple[int, int], - overlap_ratio_wh: Optional[tuple[float, float]], - overlap_wh: Optional[tuple[int, int]], + overlap_ratio_wh: tuple[float, float] | None, + overlap_wh: tuple[int, int] | None, ) -> np.ndarray: """ Generate offset coordinates for slicing an image based on the given resolution, @@ -268,8 +270,8 @@ class InferenceSlicer: @staticmethod def _validate_overlap( - overlap_ratio_wh: Optional[tuple[float, float]], - overlap_wh: Optional[tuple[int, int]], + overlap_ratio_wh: tuple[float, float] | None, + overlap_wh: tuple[int, int] | None, ) -> None: if overlap_ratio_wh is not None and overlap_wh is not None: raise ValueError( diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index 0b1141ce..40c75337 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -1,6 +1,7 @@ +from __future__ import annotations + from collections.abc import Iterable from dataclasses import replace -from typing import Optional import cv2 import numpy as np @@ -154,7 +155,7 @@ class PolygonZoneAnnotator: self.display_in_zone_count = display_in_zone_count self.opacity = opacity - def annotate(self, scene: np.ndarray, label: Optional[str] = None) -> np.ndarray: + def annotate(self, scene: np.ndarray, label: str | None = None) -> np.ndarray: """ Annotates the polygon zone within a frame with a count of detected objects. diff --git a/supervision/detection/tools/smoother.py b/supervision/detection/tools/smoother.py index 933d4fa4..2c1137c0 100644 --- a/supervision/detection/tools/smoother.py +++ b/supervision/detection/tools/smoother.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import warnings from collections import defaultdict, deque from copy import deepcopy -from typing import Optional import numpy as np @@ -95,7 +96,7 @@ class DetectionsSmoother: return self.get_smoothed_detections() - def get_track(self, track_id: int) -> Optional[Detections]: + def get_track(self, track_id: int) -> Detections | None: track = self.tracks.get(track_id, None) if track is None: return None diff --git a/supervision/detection/tools/transformers.py b/supervision/detection/tools/transformers.py index 433aa48f..97eb03ce 100644 --- a/supervision/detection/tools/transformers.py +++ b/supervision/detection/tools/transformers.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import io -from typing import Any, Optional +from typing import Any import numpy as np from PIL import Image @@ -9,7 +11,7 @@ from supervision.detection.utils import mask_to_xyxy def process_transformers_detection_result( - detection_result: dict, id2label: Optional[dict[int, str]] + detection_result: dict, id2label: dict[int, str] | None ) -> dict: """ Process the result of Transformers object detection functions such as @@ -38,7 +40,7 @@ def process_transformers_detection_result( def process_transformers_v4_segmentation_result( - segmentation_result: dict, id2label: Optional[dict[int, str]] + segmentation_result: dict, id2label: dict[int, str] | None ) -> dict: """ Process the result of Transformers segmentation functions such as @@ -77,7 +79,7 @@ def process_transformers_v4_segmentation_result( def process_transformers_v5_segmentation_result( - segmentation_result: dict, id2label: Optional[dict[int, str]] + segmentation_result: dict, id2label: dict[int, str] | None ) -> dict: """ Process the result of Transformers segmentation functions such as @@ -107,7 +109,7 @@ def process_transformers_v5_segmentation_result( def process_transformers_v5_semantic_or_instance_segmentation_result( - segmentation_result: dict, id2label: Optional[dict[int, str]] + segmentation_result: dict, id2label: dict[int, str] | None ) -> dict: """ Process the result of Transformers segmentation functions such as @@ -143,7 +145,7 @@ def process_transformers_v5_semantic_or_instance_segmentation_result( def process_transformers_v4_panoptic_segmentation_result( - segmentation_result: dict, id2label: Optional[dict[int, str]] + segmentation_result: dict, id2label: dict[int, str] | None ) -> dict: """ Process the result of the Transformers function `post_process_panoptic` (v4). @@ -177,7 +179,7 @@ def process_transformers_v4_panoptic_segmentation_result( def process_transformers_v5_panoptic_segmentation_result( - segmentation_array: np.ndarray, id2label: Optional[dict[int, str]] + segmentation_array: np.ndarray, id2label: dict[int, str] | None ) -> dict: """ Process the result of the Transformers function @@ -220,8 +222,8 @@ def png_string_to_segmentation_array(png_string: bytes) -> np.ndarray: def append_class_names_to_data( class_ids: np.ndarray, - id2label: Optional[dict[int, str]], - data: Optional[dict[str, Any]] = None, + id2label: dict[int, str] | None, + data: dict[str, Any] | None = None, ) -> dict[str, Any]: """ Helper function to create or append to a data dictionary with class names if diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index d98405e3..3adec2bc 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import base64 import io import json import re from enum import Enum -from typing import Any, Optional, Union +from typing import Any import numpy as np from PIL import Image @@ -80,9 +82,7 @@ SUPPORTED_TASKS_FLORENCE_2 = [ ] -def validate_vlm_parameters( - vlm: Union[VLM, str], result: Any, kwargs: dict[str, Any] -) -> VLM: +def validate_vlm_parameters(vlm: VLM | str, result: Any, kwargs: dict[str, Any]) -> VLM: if isinstance(vlm, str): try: vlm = VLM(vlm.lower()) @@ -110,8 +110,8 @@ def validate_vlm_parameters( def from_paligemma( - result: str, resolution_wh: tuple[int, int], classes: Optional[list[str]] = None -) -> tuple[np.ndarray, Optional[np.ndarray], np.ndarray]: + result: str, resolution_wh: tuple[int, int], classes: list[str] | None = None +) -> tuple[np.ndarray, np.ndarray | None, np.ndarray]: """ Parse bounding boxes from paligemma-formatted text, scale them to the specified resolution, and optionally filter by classes. @@ -161,8 +161,8 @@ def from_qwen_2_5_vl( result: str, input_wh: tuple[int, int], resolution_wh: tuple[int, int], - classes: Optional[list[str]] = None, -) -> tuple[np.ndarray, Optional[np.ndarray], np.ndarray]: + classes: list[str] | None = None, +) -> tuple[np.ndarray, np.ndarray | None, np.ndarray]: """ Parse and scale bounding boxes from Qwen-2.5-VL style JSON output. @@ -239,9 +239,7 @@ def from_qwen_2_5_vl( def from_florence_2( result: dict, resolution_wh: tuple[int, int] -) -> tuple[ - np.ndarray, Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray] -]: +) -> tuple[np.ndarray, np.ndarray | None, np.ndarray | None, np.ndarray | None]: """ Parse results from the Florence 2 multi-model model. https://huggingface.co/microsoft/Florence-2-large @@ -335,8 +333,8 @@ def from_florence_2( def from_google_gemini_2_0( result: str, resolution_wh: tuple[int, int], - classes: Optional[list[str]] = None, -) -> tuple[np.ndarray, Optional[np.ndarray], np.ndarray]: + classes: list[str] | None = None, +) -> tuple[np.ndarray, np.ndarray | None, np.ndarray]: """ Parse and scale bounding boxes from Google Gemini style [JSON output](https://ai.google.dev/gemini-api/docs/vision?lang=python). @@ -424,13 +422,13 @@ def from_google_gemini_2_0( def from_google_gemini_2_5( result: str, resolution_wh: tuple[int, int], - classes: Optional[list[str]] = None, + classes: list[str] | None = None, ) -> tuple[ np.ndarray, - Optional[np.ndarray], + np.ndarray | None, np.ndarray, - Optional[np.ndarray], - Optional[np.ndarray], + np.ndarray | None, + np.ndarray | None, ]: """ Parse and scale bounding boxes and masks from Google Gemini 2.5 style @@ -490,8 +488,8 @@ def from_google_gemini_2_5( boxes_list: list = [] labels_list: list = [] - confidence_list: Optional[list] = [] - masks_list: Optional[list] = [] + confidence_list: list | None = [] + masks_list: list | None = [] for item in data: if "box_2d" not in item or "label" not in item: diff --git a/supervision/draw/utils.py b/supervision/draw/utils.py index 87a96a2a..ed4a9037 100644 --- a/supervision/draw/utils.py +++ b/supervision/draw/utils.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import os -from typing import Optional, Union import cv2 import numpy as np @@ -215,7 +216,7 @@ def draw_text( text_thickness: int = 1, text_padding: int = 10, text_font: int = cv2.FONT_HERSHEY_SIMPLEX, - background_color: Optional[Color] = None, + background_color: Color | None = None, ) -> np.ndarray: """ Draw text with background on a scene. @@ -282,7 +283,7 @@ def draw_text( def draw_image( - scene: np.ndarray, image: Union[str, np.ndarray], opacity: float, rect: Rect + scene: np.ndarray, image: str | np.ndarray, opacity: float, rect: Rect ) -> np.ndarray: """ Draws an image onto a given scene with specified opacity and dimensions. diff --git a/supervision/keypoint/annotators.py b/supervision/keypoint/annotators.py index 9a44c5eb..5d74ea11 100644 --- a/supervision/keypoint/annotators.py +++ b/supervision/keypoint/annotators.py @@ -1,6 +1,7 @@ +from __future__ import annotations + from abc import ABC, abstractmethod from logging import warn -from typing import Optional, Union import cv2 import numpy as np @@ -106,7 +107,7 @@ class EdgeAnnotator(BaseKeyPointAnnotator): self, color: Color = Color.ROBOFLOW, thickness: int = 2, - edges: Optional[list[tuple[int, int]]] = None, + edges: list[tuple[int, int]] | None = None, ) -> None: """ Args: @@ -196,8 +197,8 @@ class VertexLabelAnnotator: def __init__( self, - color: Union[Color, list[Color]] = Color.ROBOFLOW, - text_color: Union[Color, list[Color]] = Color.WHITE, + color: Color | list[Color] = Color.ROBOFLOW, + text_color: Color | list[Color] = Color.WHITE, text_scale: float = 0.5, text_thickness: int = 1, text_padding: int = 10, @@ -220,8 +221,8 @@ class VertexLabelAnnotator: smart_position (bool): Spread out the labels to avoid overlap. """ self.border_radius: int = border_radius - self.color: Union[Color, list[Color]] = color - self.text_color: Union[Color, list[Color]] = text_color + self.color: Color | list[Color] = color + self.text_color: Color | list[Color] = text_color self.text_scale: float = text_scale self.text_thickness: int = text_thickness self.text_padding: int = text_padding @@ -231,7 +232,7 @@ class VertexLabelAnnotator: self, scene: ImageType, key_points: KeyPoints, - labels: Optional[list[str]] = None, + labels: list[str] | None = None, ) -> ImageType: """ A class that draws labels of skeleton vertices on images. It uses specified key @@ -412,7 +413,7 @@ class VertexLabelAnnotator: @staticmethod def preprocess_and_validate_labels( - labels: Optional[list[str]], points_count: int, skeletons_count: int + labels: list[str] | None, points_count: int, skeletons_count: int ) -> np.ndarray: if labels and len(labels) != points_count: raise ValueError( @@ -426,7 +427,7 @@ class VertexLabelAnnotator: @staticmethod def preprocess_and_validate_colors( - colors: Optional[Union[Color, list[Color]]], + colors: Color | list[Color] | None, points_count: int, skeletons_count: int, ) -> np.ndarray: diff --git a/supervision/metrics/detection.py b/supervision/metrics/detection.py index 3f1f96d5..1a55de55 100644 --- a/supervision/metrics/detection.py +++ b/supervision/metrics/detection.py @@ -1,7 +1,7 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass -from typing import Callable import matplotlib import matplotlib.pyplot as plt diff --git a/supervision/utils/file.py b/supervision/utils/file.py index 82c32ef3..c3a04ff2 100644 --- a/supervision/utils/file.py +++ b/supervision/utils/file.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import json from pathlib import Path -from typing import Optional, Union import numpy as np import yaml @@ -18,7 +19,7 @@ class NumpyJsonEncoder(json.JSONEncoder): def list_files_with_extensions( - directory: Union[str, Path], extensions: Optional[list[str]] = None + directory: str | Path, extensions: list[str] | None = None ) -> list[Path]: """ List files in a directory with specified extensions or @@ -57,7 +58,7 @@ def list_files_with_extensions( return files_with_extensions -def read_txt_file(file_path: Union[str, Path], skip_empty: bool = False) -> list[str]: +def read_txt_file(file_path: str | Path, skip_empty: bool = False) -> list[str]: """ Read a text file and return a list of strings without newline characters. Optionally skip empty lines. @@ -79,7 +80,7 @@ def read_txt_file(file_path: Union[str, Path], skip_empty: bool = False) -> list return lines -def save_text_file(lines: list[str], file_path: Union[str, Path]) -> None: +def save_text_file(lines: list[str], file_path: str | Path) -> None: """ Write a list of strings to a text file, each string on a new line. @@ -92,7 +93,7 @@ def save_text_file(lines: list[str], file_path: Union[str, Path]) -> None: file.write(line + "\n") -def read_json_file(file_path: Union[str, Path]) -> dict: +def read_json_file(file_path: str | Path) -> dict: """ Read a json file and return a dict. @@ -107,7 +108,7 @@ def read_json_file(file_path: Union[str, Path]) -> dict: return data -def save_json_file(data: dict, file_path: Union[str, Path], indent: int = 3) -> None: +def save_json_file(data: dict, file_path: str | Path, indent: int = 3) -> None: """ Write a dict to a json file. @@ -120,7 +121,7 @@ def save_json_file(data: dict, file_path: Union[str, Path], indent: int = 3) -> json.dump(data, fp, cls=NumpyJsonEncoder, indent=indent) -def read_yaml_file(file_path: Union[str, Path]) -> dict: +def read_yaml_file(file_path: str | Path) -> dict: """ Read a yaml file and return a dict. @@ -135,7 +136,7 @@ def read_yaml_file(file_path: Union[str, Path]) -> dict: return data -def save_yaml_file(data: dict, file_path: Union[str, Path]) -> None: +def save_yaml_file(data: dict, file_path: str | Path) -> None: """ Save a dict to a json file. diff --git a/supervision/utils/image.py b/supervision/utils/image.py index b34872fc..69609867 100644 --- a/supervision/utils/image.py +++ b/supervision/utils/image.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import itertools import math import os import shutil +from collections.abc import Callable from functools import partial -from typing import Callable, Literal, Optional, Union +from typing import Literal import cv2 import numpy as np @@ -28,7 +31,7 @@ MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3 @ensure_cv2_image_for_processing def crop_image( image: ImageType, - xyxy: Union[npt.NDArray[int], list[int], tuple[int, int, int, int]], + xyxy: npt.NDArray[int] | list[int] | tuple[int, int, int, int], ) -> ImageType: """ Crops the given image based on the given bounding box. @@ -220,7 +223,7 @@ def resize_image( def letterbox_image( image: ImageType, resolution_wh: tuple[int, int], - color: Union[tuple[int, int, int], Color] = Color.BLACK, + color: tuple[int, int, int] | Color = Color.BLACK, ) -> ImageType: """ Resizes and pads an image to a specified resolution with a given color, maintaining @@ -412,7 +415,7 @@ class ImageSink: return self - def save_image(self, image: np.ndarray, image_name: Optional[str] = None): + def save_image(self, image: np.ndarray, image_name: str | None = None): """ Save a given image in the target directory. @@ -436,23 +439,21 @@ class ImageSink: def create_tiles( images: list[ImageType], - grid_size: Optional[tuple[Optional[int], Optional[int]]] = None, - single_tile_size: Optional[tuple[int, int]] = None, + grid_size: tuple[int | None, int | None] | None = None, + single_tile_size: tuple[int, int] | None = None, tile_scaling: Literal["min", "max", "avg"] = "avg", - tile_padding_color: Union[tuple[int, int, int], Color] = Color.from_hex("#D9D9D9"), + tile_padding_color: tuple[int, int, int] | Color = Color.from_hex("#D9D9D9"), tile_margin: int = 10, - tile_margin_color: Union[tuple[int, int, int], Color] = Color.from_hex("#BFBEBD"), + tile_margin_color: tuple[int, int, int] | Color = Color.from_hex("#BFBEBD"), return_type: Literal["auto", "cv2", "pillow"] = "auto", - titles: Optional[list[Optional[str]]] = None, - titles_anchors: Optional[Union[Point, list[Optional[Point]]]] = None, - titles_color: Union[tuple[int, int, int], Color] = Color.from_hex("#262523"), - titles_scale: Optional[float] = None, + titles: list[str | None] | None = None, + titles_anchors: Point | list[Point | None] | None = None, + titles_color: tuple[int, int, int] | Color = Color.from_hex("#262523"), + titles_scale: float | None = None, titles_thickness: int = 1, titles_padding: int = 10, titles_text_font: int = cv2.FONT_HERSHEY_SIMPLEX, - titles_background_color: Union[tuple[int, int, int], Color] = Color.from_hex( - "#D9D9D9" - ), + titles_background_color: tuple[int, int, int] | Color = Color.from_hex("#D9D9D9"), default_title_placement: RelativePosition = "top", ) -> ImageType: """ @@ -613,7 +614,7 @@ def _aggregate_images_shape( def _establish_grid_size( - images: list[np.ndarray], grid_size: Optional[tuple[Optional[int], Optional[int]]] + images: list[np.ndarray], grid_size: tuple[int | None, int | None] | None ) -> tuple[int, int]: if grid_size is None or all(e is None for e in grid_size): return _negotiate_grid_size(images=images) @@ -642,10 +643,10 @@ def _generate_tiles( tile_padding_color: tuple[int, int, int], tile_margin: int, tile_margin_color: tuple[int, int, int], - titles: Optional[list[Optional[str]]], - titles_anchors: list[Optional[Point]], + titles: list[str | None] | None, + titles_anchors: list[Point | None], titles_color: tuple[int, int, int], - titles_scale: Optional[float], + titles_scale: float | None, titles_thickness: int, titles_padding: int, titles_text_font: int, @@ -686,10 +687,10 @@ def _generate_tiles( def _draw_texts( images: list[np.ndarray], - titles: Optional[list[Optional[str]]], - titles_anchors: list[Optional[Point]], + titles: list[str | None] | None, + titles_anchors: list[Point | None], titles_color: tuple[int, int, int], - titles_scale: Optional[float], + titles_scale: float | None, titles_thickness: int, titles_padding: int, titles_text_font: int, @@ -730,7 +731,7 @@ def _draw_texts( def _prepare_default_titles_anchors( images: list[np.ndarray], - titles_anchors: list[Optional[Point]], + titles_anchors: list[Point | None], default_title_placement: RelativePosition, ) -> list[Point]: result = [] diff --git a/supervision/utils/internal.py b/supervision/utils/internal.py index 94b5465c..242a4172 100644 --- a/supervision/utils/internal.py +++ b/supervision/utils/internal.py @@ -1,8 +1,11 @@ +from __future__ import annotations + import functools import inspect import os import warnings -from typing import Any, Callable, Generic, Optional, TypeVar +from collections.abc import Callable +from typing import Any, Generic, TypeVar class SupervisionWarnings(Warning): @@ -144,7 +147,7 @@ class classproperty(Generic[T]): """ self.fget = fget - def __get__(self, owner_self: Any, owner_cls: Optional[type] = None) -> T: + def __get__(self, owner_self: Any, owner_cls: type | None = None) -> T: """ Override the __get__ method to return the result of the function call. diff --git a/supervision/utils/notebook.py b/supervision/utils/notebook.py index 5e7091f8..9262f12b 100644 --- a/supervision/utils/notebook.py +++ b/supervision/utils/notebook.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations import cv2 import matplotlib.pyplot as plt @@ -9,7 +9,7 @@ from supervision.utils.conversion import pillow_to_cv2 def plot_image( - image: ImageType, size: tuple[int, int] = (12, 12), cmap: Optional[str] = "gray" + image: ImageType, size: tuple[int, int] = (12, 12), cmap: str | None = "gray" ) -> None: """ Plots image using matplotlib. @@ -48,9 +48,9 @@ def plot_image( def plot_images_grid( images: list[ImageType], grid_size: tuple[int, int], - titles: Optional[list[str]] = None, + titles: list[str] | None = None, size: tuple[int, int] = (12, 12), - cmap: Optional[str] = "gray", + cmap: str | None = "gray", ) -> None: """ Plots images in a grid using matplotlib. diff --git a/supervision/utils/video.py b/supervision/utils/video.py index 32d1879b..3b281b4e 100644 --- a/supervision/utils/video.py +++ b/supervision/utils/video.py @@ -2,9 +2,8 @@ from __future__ import annotations import time from collections import deque -from collections.abc import Generator +from collections.abc import Callable, Generator from dataclasses import dataclass -from typing import Callable import cv2 import numpy as np diff --git a/test/annotators/test_utils.py b/test/annotators/test_utils.py index 588f0242..2fdccec1 100644 --- a/test/annotators/test_utils.py +++ b/test/annotators/test_utils.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import numpy as np import pytest @@ -97,7 +98,7 @@ def test_resolve_color_idx( detections: Detections, detection_idx: int, color_lookup: ColorLookup, - expected_result: Optional[int], + expected_result: int | None, exception: Exception, ) -> None: with exception: diff --git a/test/classification/test_core.py b/test/classification/test_core.py index 9ab04e63..958ed901 100644 --- a/test/classification/test_core.py +++ b/test/classification/test_core.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import numpy as np import pytest @@ -49,9 +50,9 @@ from supervision.classification.core import Classifications ) def test_top_k( class_id: np.ndarray, - confidence: Optional[np.ndarray], + confidence: np.ndarray | None, k: int, - expected_result: Optional[tuple[np.ndarray, np.ndarray]], + expected_result: tuple[np.ndarray, np.ndarray] | None, exception: Exception, ) -> None: with exception: diff --git a/test/dataset/formats/test_coco.py b/test/dataset/formats/test_coco.py index d9748cf3..bde8566c 100644 --- a/test/dataset/formats/test_coco.py +++ b/test/dataset/formats/test_coco.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional, Union import numpy as np import pytest @@ -21,7 +22,7 @@ def mock_coco_annotation( category_id: int = 0, bbox: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 0.0), area: float = 0.0, - segmentation: Optional[Union[list[list], dict]] = None, + segmentation: list[list] | dict | None = None, iscrowd: bool = False, ) -> dict: if not segmentation: diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py index 3006e227..27600c50 100644 --- a/test/dataset/formats/test_pascal_voc.py +++ b/test/dataset/formats/test_pascal_voc.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import numpy as np import pytest @@ -60,7 +61,7 @@ def are_xml_elements_equal(elem1, elem2): def test_object_to_pascal_voc( xyxy: np.ndarray, name: str, - polygon: Optional[np.ndarray], + polygon: np.ndarray | None, expected_result, exception: Exception, ): diff --git a/test/dataset/formats/test_yolo.py b/test/dataset/formats/test_yolo.py index 62774a5d..4b032603 100644 --- a/test/dataset/formats/test_yolo.py +++ b/test/dataset/formats/test_yolo.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import numpy as np import pytest @@ -57,7 +58,7 @@ def _arrays_almost_equal( ], ) def test_with_mask( - lines: list[str], expected_result: Optional[bool], exception: Exception + lines: list[str], expected_result: bool | None, exception: Exception ) -> None: with exception: result = _with_mask(lines=lines) @@ -174,7 +175,7 @@ def test_yolo_annotations_to_detections( lines: list[str], resolution_wh: tuple[int, int], with_masks: bool, - expected_result: Optional[Detections], + expected_result: Detections | None, exception: Exception, ) -> None: with exception: @@ -202,7 +203,7 @@ def test_yolo_annotations_to_detections( ], ) def test_image_name_to_annotation_name( - image_name: str, expected_result: Optional[str], exception: Exception + image_name: str, expected_result: str | None, exception: Exception ) -> None: with exception: result = _image_name_to_annotation_name(image_name=image_name) @@ -268,8 +269,8 @@ def test_object_to_yolo( xyxy: np.ndarray, class_id: int, image_shape: tuple[int, int, int], - polygon: Optional[np.ndarray], - expected_result: Optional[str], + polygon: np.ndarray | None, + expected_result: str | None, exception: Exception, ) -> None: with exception: diff --git a/test/dataset/test_core.py b/test/dataset/test_core.py index 3cc19a80..5ad3a3ef 100644 --- a/test/dataset/test_core.py +++ b/test/dataset/test_core.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import pytest @@ -159,7 +160,7 @@ from test.test_utils import mock_detections ) def test_dataset_merge( dataset_list: list[DetectionDataset], - expected_result: Optional[DetectionDataset], + expected_result: DetectionDataset | None, exception: Exception, ) -> None: with exception: diff --git a/test/dataset/test_utils.py b/test/dataset/test_utils.py index 758e6d4c..0b5a79f2 100644 --- a/test/dataset/test_utils.py +++ b/test/dataset/test_utils.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional, TypeVar +from typing import TypeVar import numpy as np import numpy.typing as npt @@ -78,7 +80,7 @@ def test_train_test_split( train_ratio: float, random_state: int, shuffle: bool, - expected_result: Optional[tuple[list[T], list[T]]], + expected_result: tuple[list[T], list[T]] | None, exception: Exception, ) -> None: with exception: @@ -165,7 +167,7 @@ def test_merge_class_maps( def test_build_class_index_mapping( source_classes: list[str], target_classes: list[str], - expected_result: Optional[dict[int, int]], + expected_result: dict[int, int] | None, exception: Exception, ) -> None: with exception: @@ -225,7 +227,7 @@ def test_build_class_index_mapping( def test_map_detections_class_id( source_to_target_mapping: dict[int, int], detections: Detections, - expected_result: Optional[Detections], + expected_result: Detections | None, exception: Exception, ) -> None: with exception: diff --git a/test/detection/test_core.py b/test/detection/test_core.py index c0270f70..c57bea40 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional, Union import numpy as np import pytest @@ -233,8 +234,8 @@ TEST_DET_DIFFERENT_METADATA = Detections( ) def test_getitem( detections: Detections, - index: Union[int, slice, list[int], np.ndarray], - expected_result: Optional[Detections], + index: int | slice | list[int] | np.ndarray, + expected_result: Detections | None, exception: Exception, ) -> None: with exception: @@ -506,7 +507,7 @@ def test_getitem( ) def test_merge( detections_list: list[Detections], - expected_result: Optional[Detections], + expected_result: Detections | None, exception: Exception, ) -> None: with exception: @@ -808,7 +809,7 @@ def test_equal( def test_merge_inner_detection_object_pair( detection_1: Detections, detection_2: Detections, - expected_result: Optional[Detections], + expected_result: Detections | None, exception: Exception, ): with exception: diff --git a/test/detection/test_line_counter.py b/test/detection/test_line_counter.py index 7291a1c5..f7227fa6 100644 --- a/test/detection/test_line_counter.py +++ b/test/detection/test_line_counter.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import pytest @@ -65,7 +66,7 @@ from test.test_utils import mock_detections ) def test_calculate_region_of_interest_limits( vector: Vector, - expected_result: Optional[tuple[Vector, Vector]], + expected_result: tuple[Vector, Vector] | None, exception: Exception, ) -> None: with exception: @@ -741,7 +742,7 @@ def test_line_zone_one_detection_long_horizon( ) def test_line_zone_long_horizon_disappearing_detections( vector: Vector, - xyxy_sequence: list[list[Optional[list[float]]]], + xyxy_sequence: list[list[list[float] | None]], anchors: list[Position], minimum_crossing_threshold: int, expected_crossed_in: list[list[bool]], diff --git a/test/detection/test_overlap_filter.py b/test/detection/test_overlap_filter.py index f655f0e9..d870f897 100644 --- a/test/detection/test_overlap_filter.py +++ b/test/detection/test_overlap_filter.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import numpy as np import pytest @@ -234,7 +235,7 @@ def test_group_overlapping_boxes( def test_box_non_max_suppression( predictions: np.ndarray, iou_threshold: float, - expected_result: Optional[np.ndarray], + expected_result: np.ndarray | None, exception: Exception, ) -> None: with exception: @@ -440,7 +441,7 @@ def test_mask_non_max_suppression( predictions: np.ndarray, masks: np.ndarray, iou_threshold: float, - expected_result: Optional[np.ndarray], + expected_result: np.ndarray | None, exception: Exception, ) -> None: with exception: diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index 99762c91..eb76b996 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Any, Optional +from typing import Any import numpy as np import numpy.typing as npt @@ -153,8 +155,8 @@ def test_clip_boxes( ) def test_filter_polygons_by_area( polygons: list[np.ndarray], - min_area: Optional[float], - max_area: Optional[float], + min_area: float | None, + max_area: float | None, expected_result: list[np.ndarray], exception: Exception, ) -> None: @@ -373,7 +375,7 @@ def test_filter_polygons_by_area( def test_process_roboflow_result( roboflow_result: dict, expected_result: tuple[ - np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], np.ndarray + np.ndarray, np.ndarray, np.ndarray, np.ndarray | None, np.ndarray ], exception: Exception, ) -> None: @@ -1033,7 +1035,7 @@ def test_calculate_masks_centroids( ) def test_merge_data( data_list: list[dict[str, Any]], - expected_result: Optional[dict[str, Any]], + expected_result: dict[str, Any] | None, exception: Exception, ): with exception: @@ -1209,7 +1211,7 @@ def test_merge_data( def test_get_data_item( data: dict[str, Any], index: Any, - expected_result: Optional[dict[str, Any]], + expected_result: dict[str, Any] | None, exception: Exception, ): with exception: diff --git a/test/detection/test_vlm.py b/test/detection/test_vlm.py index 93e030a6..1b4c2f18 100644 --- a/test/detection/test_vlm.py +++ b/test/detection/test_vlm.py @@ -1,6 +1,7 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise from contextlib import nullcontext as does_not_raise -from typing import Optional import numpy as np import pytest @@ -185,8 +186,8 @@ def test_from_paligemma( exception, result: str, resolution_wh: tuple[int, int], - classes: Optional[list[str]], - expected_results: tuple[np.ndarray, Optional[np.ndarray], np.ndarray], + classes: list[str] | None, + expected_results: tuple[np.ndarray, np.ndarray | None, np.ndarray], ) -> None: with exception: result = from_paligemma( @@ -347,7 +348,7 @@ def test_from_qwen_2_5_vl( result: str, input_wh: tuple[int, int], resolution_wh: tuple[int, int], - classes: Optional[list[str]], + classes: list[str] | None, expected_results, ) -> None: with exception: @@ -489,8 +490,8 @@ def test_from_google_gemini( exception, result: str, resolution_wh: tuple[int, int], - classes: Optional[list[str]], - expected_results: tuple[np.ndarray, Optional[np.ndarray], np.ndarray], + classes: list[str] | None, + expected_results: tuple[np.ndarray, np.ndarray | None, np.ndarray], ) -> None: with exception: xyxy, class_id, class_name = from_google_gemini_2_0( @@ -865,7 +866,7 @@ def test_florence_2( florence_result: dict, resolution_wh: tuple[int, int], expected_results: tuple[ - np.ndarray, Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray] + np.ndarray, np.ndarray | None, np.ndarray | None, np.ndarray | None ], exception: Exception, ) -> None: @@ -1081,10 +1082,9 @@ def test_from_google_gemini_2_5( exception, result: str, resolution_wh: tuple[int, int], - classes: Optional[list[str]], - expected_results: Optional[ - tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray] - ], + classes: list[str] | None, + expected_results: None + | (tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]), ): with exception: ( diff --git a/test/detection/tools/test_inference_slicer.py b/test/detection/tools/test_inference_slicer.py index 84f3b803..af29723a 100644 --- a/test/detection/tools/test_inference_slicer.py +++ b/test/detection/tools/test_inference_slicer.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import numpy as np import pytest @@ -47,9 +48,9 @@ def mock_callback(): def test_inference_slicer_overlap( mock_callback, slice_wh: tuple[int, int], - overlap_ratio_wh: Optional[tuple[float, float]], - overlap_wh: Optional[tuple[int, int]], - expected_overlap: Optional[tuple[int, int]], + overlap_ratio_wh: tuple[float, float] | None, + overlap_wh: tuple[int, int] | None, + expected_overlap: tuple[int, int] | None, exception: Exception, ) -> None: with exception: @@ -169,7 +170,7 @@ def test_inference_slicer_overlap( def test_generate_offset( resolution_wh: tuple[int, int], slice_wh: tuple[int, int], - overlap_wh: Optional[tuple[int, int]], + overlap_wh: tuple[int, int] | None, expected_offsets: np.ndarray, ) -> None: offsets = InferenceSlicer._generate_offset( diff --git a/test/draw/test_color.py b/test/draw/test_color.py index b9dbc440..05ad6862 100644 --- a/test/draw/test_color.py +++ b/test/draw/test_color.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional import pytest @@ -25,7 +26,7 @@ from supervision.draw.color import Color ], ) def test_color_from_hex( - color_hex, expected_result: Optional[Color], exception: Exception + color_hex, expected_result: Color | None, exception: Exception ) -> None: with exception: result = Color.from_hex(color_hex=color_hex) @@ -44,7 +45,7 @@ def test_color_from_hex( ], ) def test_color_as_hex( - color: Color, expected_result: Optional[str], exception: Exception + color: Color, expected_result: str | None, exception: Exception ) -> None: with exception: result = color.as_hex() diff --git a/test/metrics/test_detection.py b/test/metrics/test_detection.py index b2313bdd..f5be7ac6 100644 --- a/test/metrics/test_detection.py +++ b/test/metrics/test_detection.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from contextlib import ExitStack as DoesNotRaise -from typing import Optional, Union import numpy as np import pytest @@ -103,9 +104,7 @@ def update_ideal_conf_matrix(conf_matrix: np.ndarray, class_ids: np.ndarray): return conf_matrix -def worsen_ideal_conf_matrix( - conf_matrix: np.ndarray, class_ids: Union[np.ndarray, list] -): +def worsen_ideal_conf_matrix(conf_matrix: np.ndarray, class_ids: np.ndarray | list): for class_id in class_ids: class_id = int(class_id) conf_matrix[class_id, class_id] -= 1 @@ -177,7 +176,7 @@ BAD_CONF_MATRIX = worsen_ideal_conf_matrix( def test_detections_to_tensor( detections: Detections, with_confidence: bool, - expected_result: Optional[np.ndarray], + expected_result: np.ndarray | None, exception: Exception, ): with exception: @@ -343,7 +342,7 @@ def test_from_tensors( classes, conf_threshold, iou_threshold, - expected_result: Optional[np.ndarray], + expected_result: np.ndarray | None, exception: Exception, ): with exception: @@ -380,7 +379,7 @@ def test_evaluate_detection_batch( num_classes, conf_threshold, iou_threshold, - expected_result: Optional[np.ndarray], + expected_result: np.ndarray | None, exception: Exception, ): with exception: @@ -408,7 +407,7 @@ def test_evaluate_detection_batch( ) def test_drop_extra_matches( matches, - expected_result: Optional[np.ndarray], + expected_result: np.ndarray | None, exception: Exception, ): with exception: diff --git a/test/test_utils.py b/test/test_utils.py index 8e5ca016..19fffad5 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,4 +1,6 @@ -from typing import Any, Optional +from __future__ import annotations + +from typing import Any import numpy as np import numpy.typing as npt @@ -9,11 +11,11 @@ from supervision.keypoint.core import KeyPoints def mock_detections( xyxy: npt.NDArray[np.float32], - mask: Optional[list[np.ndarray]] = None, - confidence: Optional[list[float]] = None, - class_id: Optional[list[int]] = None, - tracker_id: Optional[list[int]] = None, - data: Optional[dict[str, list[Any]]] = None, + mask: list[np.ndarray] | None = None, + confidence: list[float] | None = None, + class_id: list[int] | None = None, + tracker_id: list[int] | None = None, + data: dict[str, list[Any]] | None = None, ) -> Detections: def convert_data(data: dict[str, list[Any]]): return {k: np.array(v) for k, v in data.items()} @@ -34,9 +36,9 @@ def mock_detections( def mock_keypoints( xy: npt.NDArray[np.float32], - confidence: Optional[list[float]] = None, - class_id: Optional[list[int]] = None, - data: Optional[dict[str, list[Any]]] = None, + confidence: list[float] | None = None, + class_id: list[int] | None = None, + data: dict[str, list[Any]] | None = None, ) -> KeyPoints: def convert_data(data: dict[str, list[Any]]): return {k: np.array(v) for k, v in data.items()} diff --git a/test/utils/test_file.py b/test/utils/test_file.py index 9a4a4d33..71f5bdeb 100644 --- a/test/utils/test_file.py +++ b/test/utils/test_file.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import os from contextlib import ExitStack as DoesNotRaise -from typing import Optional import pytest @@ -56,7 +57,7 @@ def setup_and_teardown_files(): def test_read_txt_file( file_name: str, skip_empty: bool, - expected_result: Optional[list[str]], + expected_result: list[str] | None, exception: Exception, ): with exception: