refactor: 🧪 type hints to use the new union syntax for optional types

- 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 <thunderbirdtr@gmail.com>
This commit is contained in:
Onuralp SEZER committed 2025-07-15 16:36:01 +03:00
1 parent 7fde67ef75
commit 212e69b5a4
42 files changed
+320 -280

No files matched your search

+1 -1
View File
@@ -58,6 +58,6 @@ repos:
rev: v3.20.0
hooks:
- id: pyupgrade
args: ["--py39-plus"]
args: ["--py310-plus"]
additional_dependencies:
- tomli
@@ -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()
+5 -3
View File
@@ -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
@@ -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:
@@ -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:
+76 -75
View File
@@ -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
+9 -8
View File
@@ -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:
+3 -2
View File
@@ -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.
+3 -2
View File
@@ -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")
+5 -3
View File
@@ -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,
+6 -6
View File
@@ -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.
+7 -5
View File
@@ -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.
@@ -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(
+3 -2
View File
@@ -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.
+3 -2
View File
@@ -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
+11 -9
View File
@@ -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
+17 -19
View File
@@ -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:
+4 -3
View File
@@ -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.
+10 -9
View File
@@ -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:
+1 -1
View File
@@ -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
+9 -8
View File
@@ -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.
+24 -23
View File
@@ -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 = []
+5 -2
View File
@@ -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.
+4 -4
View File
@@ -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.
+1 -2
View File
@@ -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
+3 -2
View File
@@ -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:
+4 -3
View File
@@ -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:
+3 -2
View File
@@ -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:
+3 -2
View File
@@ -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,
):
+7 -6
View File
@@ -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:
+3 -2
View File
@@ -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:
+6 -4
View File
@@ -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:
+6 -5
View File
@@ -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:
+4 -3
View File
@@ -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]],
+4 -3
View File
@@ -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:
+8 -6
View File
@@ -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:
+11 -11
View File
@@ -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:
(
@@ -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(
+4 -3
View File
@@ -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()
+7 -8
View File
@@ -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:
+11 -9
View File
@@ -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()}
+3 -2
View File
@@ -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: